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 <sgunter@utexas.edu>
This commit is contained in:
@@ -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<T extends Function>(callback: T) {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deregisters an event listener callback from an event.
|
||||||
|
* @param callback Listener that shall be unregistered.
|
||||||
|
*/
|
||||||
|
removeListener<T extends Function>(callback: T) {},
|
||||||
|
},
|
||||||
|
},
|
||||||
} as typeof chrome;
|
} as typeof chrome;
|
||||||
|
|
||||||
export default preview;
|
export default preview;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
"@hello-pangea/dnd": "^16.5.0",
|
"@hello-pangea/dnd": "^16.5.0",
|
||||||
"@unocss/vite": "^0.58.6",
|
"@unocss/vite": "^0.58.6",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"chrome-extension-toolkit": "^0.0.51",
|
"chrome-extension-toolkit": "^0.0.54",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"highcharts": "^11.3.0",
|
"highcharts": "^11.3.0",
|
||||||
"highcharts-react-official": "^3.2.1",
|
"highcharts-react-official": "^3.2.1",
|
||||||
@@ -120,4 +120,4 @@
|
|||||||
"es-module-lexer": "^1.4.1"
|
"es-module-lexer": "^1.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -29,8 +29,8 @@ dependencies:
|
|||||||
specifier: ^4.2.1
|
specifier: ^4.2.1
|
||||||
version: 4.2.1(vite@5.1.4)
|
version: 4.2.1(vite@5.1.4)
|
||||||
chrome-extension-toolkit:
|
chrome-extension-toolkit:
|
||||||
specifier: ^0.0.51
|
specifier: ^0.0.54
|
||||||
version: 0.0.51
|
version: 0.0.54
|
||||||
clsx:
|
clsx:
|
||||||
specifier: ^2.1.0
|
specifier: ^2.1.0
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
@@ -6283,8 +6283,8 @@ packages:
|
|||||||
optional: true
|
optional: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/chrome-extension-toolkit@0.0.51:
|
/chrome-extension-toolkit@0.0.54:
|
||||||
resolution: {integrity: sha512-XzOOE2+/aYG43bJOwuJT4oWcn80jBJr5mwGyrSzKKFoqALixT15AsPcfZId/UOoc4pIavu2XcHeJga6ng0m1jQ==}
|
resolution: {integrity: sha512-ux8v/PfWQIvO+EBbF+kDYq2z8Rnp5YZ7GwJxYX7R2a9owIEHJxiCUSJ82tOsiMQINF/31+t6QLG9equKNZUOlA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import onInstall from './events/onInstall';
|
|||||||
import onServiceWorkerAlive from './events/onServiceWorkerAlive';
|
import onServiceWorkerAlive from './events/onServiceWorkerAlive';
|
||||||
import onUpdate from './events/onUpdate';
|
import onUpdate from './events/onUpdate';
|
||||||
import browserActionHandler from './handler/browserActionHandler';
|
import browserActionHandler from './handler/browserActionHandler';
|
||||||
|
import calendarBackgroundHandler from './handler/calendarBackgroundHandler';
|
||||||
import CESHandler from './handler/CESHandler';
|
import CESHandler from './handler/CESHandler';
|
||||||
import tabManagementHandler from './handler/tabManagementHandler';
|
import tabManagementHandler from './handler/tabManagementHandler';
|
||||||
import userScheduleHandler from './handler/userScheduleHandler';
|
import userScheduleHandler from './handler/userScheduleHandler';
|
||||||
@@ -36,6 +37,7 @@ const messageListener = new MessageListener<BACKGROUND_MESSAGES>({
|
|||||||
...tabManagementHandler,
|
...tabManagementHandler,
|
||||||
...userScheduleHandler,
|
...userScheduleHandler,
|
||||||
...CESHandler,
|
...CESHandler,
|
||||||
|
...calendarBackgroundHandler,
|
||||||
});
|
});
|
||||||
|
|
||||||
messageListener.listen();
|
messageListener.listen();
|
||||||
|
|||||||
44
src/pages/background/handler/calendarBackgroundHandler.ts
Normal file
44
src/pages/background/handler/calendarBackgroundHandler.ts
Normal file
@@ -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<CalendarBackgroundMessages> = {
|
||||||
|
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;
|
||||||
19
src/shared/messages/CalendarMessages.ts
Normal file
19
src/shared/messages/CalendarMessages.ts
Normal file
@@ -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 };
|
||||||
15
src/shared/messages/TabInfoMessages.ts
Normal file
15
src/shared/messages/TabInfoMessages.ts
Normal file
@@ -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;
|
||||||
@@ -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 {}
|
|
||||||
@@ -1,15 +1,25 @@
|
|||||||
import { createMessenger } from 'chrome-extension-toolkit';
|
import { createMessenger } from 'chrome-extension-toolkit';
|
||||||
|
|
||||||
import type BrowserActionMessages from './BrowserActionMessages';
|
import type BrowserActionMessages from './BrowserActionMessages';
|
||||||
|
import type { CalendarBackgroundMessages, CalendarTabMessages } from './CalendarMessages';
|
||||||
import type CESMessage from './CESMessage';
|
import type CESMessage from './CESMessage';
|
||||||
|
import type TabInfoMessages from './TabInfoMessages';
|
||||||
import type TabManagementMessages from './TabManagementMessages';
|
import type TabManagementMessages from './TabManagementMessages';
|
||||||
import type TAB_MESSAGES from './TabMessages';
|
|
||||||
import type { UserScheduleMessages } from './UserScheduleMessages';
|
import type { UserScheduleMessages } from './UserScheduleMessages';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a type with all the message definitions that can be sent TO the background script
|
* 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
|
* 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_MESSAGES>('background');
|
|||||||
/**
|
/**
|
||||||
* A utility object that can be used to send type-safe messages to specific tabs
|
* A utility object that can be used to send type-safe messages to specific tabs
|
||||||
*/
|
*/
|
||||||
export const tabs = createMessenger<TAB_MESSAGES>('tab');
|
export const tabs = createMessenger<TAB_MESSAGES>('foreground');
|
||||||
|
|||||||
@@ -103,9 +103,9 @@ export const Variants: Story = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AllColors: Story = {
|
export const AllColors: Story = {
|
||||||
render: props => (
|
render: () => (
|
||||||
<div className='grid grid-flow-col grid-cols-2 grid-rows-9 max-w-2xl w-90vw gap-x-4 gap-y-2'>
|
<div className='grid grid-flow-col grid-cols-2 grid-rows-9 max-w-2xl w-90vw gap-x-4 gap-y-2'>
|
||||||
{tailwindColorways.map((color, i) => (
|
{tailwindColorways.map(color => (
|
||||||
<PopupCourseBlock key={color.primaryColor} course={ExampleCourse} colors={color} />
|
<PopupCourseBlock key={color.primaryColor} course={ExampleCourse} colors={color} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import { background } from '@shared/messages';
|
||||||
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
||||||
import { tailwindColorways } from '@shared/util/storybook';
|
import { tailwindColorways } from '@shared/util/storybook';
|
||||||
import Divider from '@views/components/common/Divider/Divider';
|
import Divider from '@views/components/common/Divider/Divider';
|
||||||
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
||||||
import List from '@views/components/common/List/List';
|
import List from '@views/components/common/List/List';
|
||||||
import Text from '@views/components/common/Text/Text';
|
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 useSchedules, { getActiveSchedule, replaceSchedule, switchSchedule } from '@views/hooks/useSchedules';
|
||||||
import { getUpdatedAtDateTimeString } from '@views/lib/getUpdatedAtDateTimeString';
|
import { getUpdatedAtDateTimeString } from '@views/lib/getUpdatedAtDateTimeString';
|
||||||
import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript';
|
import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript';
|
||||||
@@ -34,6 +34,11 @@ export default function PopupMain(): JSX.Element {
|
|||||||
await openTabFromContentScript(url);
|
await openTabFromContentScript(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCalendarOpenOnClick = async () => {
|
||||||
|
await background.switchToCalendarTab({});
|
||||||
|
window.close();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExtensionRoot>
|
<ExtensionRoot>
|
||||||
<div className='h-screen max-h-full flex flex-col bg-white'>
|
<div className='h-screen max-h-full flex flex-col bg-white'>
|
||||||
@@ -50,7 +55,7 @@ export default function PopupMain(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2.5'>
|
<div className='flex items-center gap-2.5'>
|
||||||
<button className='bg-ut-burntorange px-2 py-1.25 btn' onClick={handleOpenCalendar}>
|
<button className='bg-ut-burntorange px-2 py-1.25 btn' onClick={handleCalendarOpenOnClick}>
|
||||||
<CalendarIcon className='size-6 text-white' />
|
<CalendarIcon className='size-6 text-white' />
|
||||||
</button>
|
</button>
|
||||||
<button className='bg-transparent px-2 py-1.25 btn' onClick={handleOpenOptions}>
|
<button className='bg-transparent px-2 py-1.25 btn' onClick={handleOpenOptions}>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { CalendarTabMessages } from '@shared/messages/CalendarMessages';
|
||||||
import type { Course } from '@shared/types/Course';
|
import type { Course } from '@shared/types/Course';
|
||||||
import CalendarBottomBar from '@views/components/calendar/CalendarBottomBar/CalendarBottomBar';
|
import CalendarBottomBar from '@views/components/calendar/CalendarBottomBar/CalendarBottomBar';
|
||||||
import CalendarGrid from '@views/components/calendar/CalendarGrid/CalendarGrid';
|
import CalendarGrid from '@views/components/calendar/CalendarGrid/CalendarGrid';
|
||||||
@@ -7,6 +8,7 @@ import ImportantLinks from '@views/components/calendar/ImportantLinks';
|
|||||||
import Divider from '@views/components/common/Divider/Divider';
|
import Divider from '@views/components/common/Divider/Divider';
|
||||||
import CourseCatalogInjectedPopup from '@views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup';
|
import CourseCatalogInjectedPopup from '@views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup';
|
||||||
import { useFlattenedCourseSchedule } from '@views/hooks/useFlattenedCourseSchedule';
|
import { useFlattenedCourseSchedule } from '@views/hooks/useFlattenedCourseSchedule';
|
||||||
|
import { MessageListener } from 'chrome-extension-toolkit';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import styles from './Calendar.module.scss';
|
import styles from './Calendar.module.scss';
|
||||||
@@ -17,11 +19,39 @@ import styles from './Calendar.module.scss';
|
|||||||
export default function Calendar(): JSX.Element {
|
export default function Calendar(): JSX.Element {
|
||||||
const calendarRef = useRef<HTMLDivElement>(null);
|
const calendarRef = useRef<HTMLDivElement>(null);
|
||||||
const { courseCells, activeSchedule } = useFlattenedCourseSchedule();
|
const { courseCells, activeSchedule } = useFlattenedCourseSchedule();
|
||||||
const [course, setCourse] = useState<Course | null>(null);
|
const [course, setCourse] = useState<Course | null>((): Course | null => {
|
||||||
const [showPopup, setShowPopup] = useState(false);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const uniqueIdRaw = urlParams.get('uniqueId');
|
||||||
|
if (uniqueIdRaw === null) return null;
|
||||||
|
const uniqueId = Number(uniqueIdRaw);
|
||||||
|
const course = activeSchedule.courses.find(course => course.uniqueId === uniqueId);
|
||||||
|
if (course === undefined) return null;
|
||||||
|
urlParams.delete('uniqueId');
|
||||||
|
const newUrl = `${window.location.pathname}?${urlParams}`.replace(/\?$/, '');
|
||||||
|
window.history.replaceState({}, '', newUrl);
|
||||||
|
return course;
|
||||||
|
});
|
||||||
|
|
||||||
|
const [showPopup, setShowPopup] = useState<boolean>(course !== null);
|
||||||
const [sidebarWidth, setSidebarWidth] = useState('20%');
|
const [sidebarWidth, setSidebarWidth] = useState('20%');
|
||||||
const [scale, setScale] = useState(1);
|
const [scale, setScale] = useState(1);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const listener = new MessageListener<CalendarTabMessages>({
|
||||||
|
async openCoursePopup({ data, sendResponse }) {
|
||||||
|
const course = activeSchedule.courses.find(course => course.uniqueId === data.uniqueId);
|
||||||
|
if (course === undefined) return;
|
||||||
|
setCourse(course);
|
||||||
|
setShowPopup(true);
|
||||||
|
sendResponse(await chrome.tabs.getCurrent());
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
listener.listen();
|
||||||
|
|
||||||
|
return () => listener.unlisten();
|
||||||
|
}, [activeSchedule.courses]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const adjustLayout = () => {
|
const adjustLayout = () => {
|
||||||
const windowHeight = window.innerHeight;
|
const windowHeight = window.innerHeight;
|
||||||
|
|||||||
@@ -1,17 +1,35 @@
|
|||||||
// import '@unocss/reset/tailwind-compat.css';
|
// import '@unocss/reset/tailwind-compat.css';
|
||||||
import 'uno.css';
|
import 'uno.css';
|
||||||
|
|
||||||
import React from 'react';
|
import type TabInfoMessages from '@shared/messages/TabInfoMessages';
|
||||||
|
import { MessageListener } from 'chrome-extension-toolkit';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
import styles from './ExtensionRoot.module.scss';
|
import styles from './ExtensionRoot.module.scss';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
testId?: string;
|
testId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper component for the extension elements that adds some basic styling to them
|
* A wrapper component for the extension elements that adds some basic styling to them
|
||||||
*/
|
*/
|
||||||
export default function ExtensionRoot(props: React.PropsWithChildren<Props>): JSX.Element {
|
export default function ExtensionRoot(props: React.PropsWithChildren<Props>): JSX.Element {
|
||||||
|
useEffect(() => {
|
||||||
|
const tabInfoListener = new MessageListener<TabInfoMessages>({
|
||||||
|
getTabInfo: ({ sendResponse }) => {
|
||||||
|
sendResponse({
|
||||||
|
url: window.location.href,
|
||||||
|
title: document.title,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
tabInfoListener.listen();
|
||||||
|
|
||||||
|
return () => tabInfoListener.unlisten();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.extensionRoot} data-testid={props.testId}>
|
<div className={styles.extensionRoot} data-testid={props.testId}>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { background } from '@shared/messages';
|
||||||
import type { Course } from '@shared/types/Course';
|
import type { Course } from '@shared/types/Course';
|
||||||
import { Status } from '@shared/types/Course';
|
import { Status } from '@shared/types/Course';
|
||||||
import type { CourseColors } from '@shared/util/colors';
|
import type { CourseColors } from '@shared/util/colors';
|
||||||
@@ -34,12 +35,21 @@ export default function PopupCourseBlock({
|
|||||||
const fontColor = pickFontColor(colors.primaryColor);
|
const fontColor = pickFontColor(colors.primaryColor);
|
||||||
const formattedUniqueId = course.uniqueId.toString().padStart(5, '0');
|
const formattedUniqueId = course.uniqueId.toString().padStart(5, '0');
|
||||||
|
|
||||||
|
const handleClick = async () => {
|
||||||
|
await background.switchToCalendarTab({ uniqueId: course.uniqueId });
|
||||||
|
window.close();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<button
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: colors.primaryColor,
|
backgroundColor: colors.primaryColor,
|
||||||
}}
|
}}
|
||||||
className={clsx('h-full w-full inline-flex items-center justify-center gap-1 rounded pr-3', className)}
|
className={clsx(
|
||||||
|
'h-full w-full inline-flex items-center justify-center gap-1 rounded pr-3 cursor-pointer focusable text-left',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -64,6 +74,6 @@ export default function PopupCourseBlock({
|
|||||||
<StatusIcon status={course.status} className='h-5 w-5' />
|
<StatusIcon status={course.status} className='h-5 w-5' />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,15 +29,6 @@ interface HeadingAndActionProps {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the calendar in a new tab.
|
|
||||||
* @returns {Promise<void>} A promise that resolves when the tab is opened.
|
|
||||||
*/
|
|
||||||
export const handleOpenCalendar = async (): Promise<void> => {
|
|
||||||
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.
|
* 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
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='my-3 flex flex-wrap items-center gap-x-3.75 gap-y-2.5'>
|
<div className='my-3 flex flex-wrap items-center gap-x-3.75 gap-y-2.5'>
|
||||||
<Button variant='filled' color='ut-burntorange' icon={CalendarMonth} onClick={handleOpenCalendar} />
|
<Button
|
||||||
|
variant='filled'
|
||||||
|
color='ut-burntorange'
|
||||||
|
icon={CalendarMonth}
|
||||||
|
onClick={() => background.switchToCalendarTab({})}
|
||||||
|
/>
|
||||||
<Divider size='1.75rem' orientation='vertical' />
|
<Divider size='1.75rem' orientation='vertical' />
|
||||||
<Button variant='outline' color='ut-blue' icon={Reviews} onClick={handleOpenRateMyProf}>
|
<Button variant='outline' color='ut-blue' icon={Reviews} onClick={handleOpenRateMyProf}>
|
||||||
RateMyProf
|
RateMyProf
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type TAB_MESSAGES from '@shared/messages/TabMessages';
|
import type { TAB_MESSAGES } from '@shared/messages';
|
||||||
import { createUseMessage } from 'chrome-extension-toolkit';
|
import { createUseMessage } from 'chrome-extension-toolkit';
|
||||||
|
|
||||||
export const useTabMessage = createUseMessage<TAB_MESSAGES>();
|
export const useTabMessage = createUseMessage<TAB_MESSAGES>();
|
||||||
|
|||||||
@@ -46,8 +46,5 @@ onContextInvalidated(() => {
|
|||||||
div.id = 'context-invalidated-container';
|
div.id = 'context-invalidated-container';
|
||||||
document.body.appendChild(div);
|
document.body.appendChild(div);
|
||||||
|
|
||||||
render(
|
render(<ContextInvalidated className='bg-ut-burntorange text-white font-mono' />, div);
|
||||||
<ContextInvalidated fontFamily='monospace' color={colors.white} backgroundColor={colors.burnt_orange} />,
|
|
||||||
div
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user