refactoring, using different pattern for page injection and reusing same pattern for both popup and content scripts

This commit is contained in:
Sriram Hariharan
2023-03-03 21:13:43 -06:00
parent 4ed52a3c9f
commit beb61176c1
18 changed files with 120 additions and 95 deletions

11
package-lock.json generated
View File

@@ -9,6 +9,7 @@
"version": "0.0.0",
"dependencies": {
"chrome-extension-toolkit": "^0.0.22",
"classnames": "^2.3.2",
"clean-webpack-plugin": "^4.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -4702,6 +4703,11 @@
"node": ">=6.0"
}
},
"node_modules/classnames": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
},
"node_modules/clean-css": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz",
@@ -20674,6 +20680,11 @@
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
"integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg=="
},
"classnames": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
},
"clean-css": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz",

View File

@@ -14,6 +14,7 @@
},
"dependencies": {
"chrome-extension-toolkit": "^0.0.22",
"classnames": "^2.3.2",
"clean-webpack-plugin": "^4.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View File

@@ -0,0 +1,16 @@
import React, { useEffect, useMemo } from 'react';
import { bMessenger } from 'src/shared/messages';
import { SiteSupport } from '../lib/getSiteSupport';
import { Button } from './common/Button/Button';
interface Props {
support: SiteSupport[];
}
export default function CourseCatalogMain(props: Props) {
const openGoogle = () => {
bMessenger.openNewTab({ url: 'https://google.com' });
};
return <Button onClick={openGoogle}>{props.support.join(',')}</Button>;
}

View File

@@ -0,0 +1,14 @@
import React from 'react';
import styles from './Button.module.scss';
interface Props {
onClick?: () => void;
}
export function Button(props: React.PropsWithChildren<Props>): JSX.Element {
return (
<button className={styles.button} onClick={props.onClick}>
{props.children}
</button>
);
}

View File

@@ -1,5 +0,0 @@
import React from 'react';
export default function ContentMain() {
return <div>content</div>;
}

View File

@@ -1,15 +0,0 @@
import React from 'react';
import { bMessenger } from 'src/shared/messages';
import styles from './Button.module.scss';
export function Button(): JSX.Element {
const handleOpenUrl = (url: string) => () => {
bMessenger.openNewTab({ url });
};
return (
<button className={styles.button} onClick={handleOpenUrl('https://www.google.com')}>
Click me
</button>
);
}

View File

@@ -1,29 +0,0 @@
import React from 'react';
import { render } from 'react-dom';
import { ContextInvalidated, createShadowDOM, onContextInvalidated } from 'chrome-extension-toolkit';
import ContentMain from './ContentMain';
import colors from '../styles/colors.module.scss';
import getPageTypes, { PageType } from './lib/getPageTypes';
import { populateSearchInputs } from './lib/courseSchedule/populateSearchInputs';
const pageTypes = getPageTypes(window.location.href);
console.log('pageTypes:', pageTypes);
if (pageTypes.includes(PageType.COURSE_SCHEDULE)) {
if (pageTypes.includes(PageType.COURSE_SCHEDULE_LIST)) {
populateSearchInputs();
}
const shadowDom = createShadowDOM('ut-registration-plus-dom-container');
render(<ContentMain />, shadowDom.shadowRoot);
shadowDom.addStyle('static/css/content.css');
}
onContextInvalidated(() => {
const div = document.createElement('div');
div.id = 'context-invalidated-container';
document.body.appendChild(div);
render(
<ContextInvalidated fontFamily='monospace' color={colors.WHITE} backgroundColor={colors.BURNT_ORANGE} />,
div
);
});

View File

@@ -1,36 +0,0 @@
export enum PageType {
EXTENSION_POPUP,
COURSE_SCHEDULE,
COURSE_SCHEDULE_LIST,
COURSE_DETAILS,
UT_PLANNER,
WAITLIST,
}
/**
* 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 getPageTypes(url: string): PageType[] {
if (url.includes('chrome-extension://')) {
return [PageType.EXTENSION_POPUP];
}
if (url.includes('utexas.collegescheduler.com')) {
return [PageType.UT_PLANNER];
}
if (url.includes('utdirect.utexas.edu/apps/registrar/course_schedule')) {
const types = [PageType.COURSE_SCHEDULE];
if (url.includes('results')) {
types.push(PageType.COURSE_SCHEDULE_LIST);
}
if (document.querySelector('#details')) {
types.push(PageType.COURSE_DETAILS);
}
return types;
}
if (url.includes('utdirect.utexas.edu') && (url.includes('waitlist') || url.includes('classlist'))) {
return [PageType.WAITLIST];
}
return [];
}

37
src/views/index.tsx Normal file
View File

@@ -0,0 +1,37 @@
import React from 'react';
import { render } from 'react-dom';
import { ContextInvalidated, createShadowDOM, isExtensionPopup, onContextInvalidated } from 'chrome-extension-toolkit';
import CourseCatalogMain from './components/CourseCatalogMain';
import colors from './styles/colors.module.scss';
import getSiteSupport, { SiteSupport } from './lib/getSiteSupport';
import PopupMain from './components/PopupMain';
const support = getSiteSupport(window.location.href);
if (isExtensionPopup()) {
render(<PopupMain />, document.getElementById('root'));
}
if (support.includes(SiteSupport.COURSE_CATALOG)) {
const shadowDom = createShadowDOM('ut-registration-plus-dom-container');
render(<CourseCatalogMain support={support} />, shadowDom.shadowRoot);
shadowDom.addStyle('static/css/content.css');
}
if (support.includes(SiteSupport.WAITLIST)) {
// TODO: Implement waitlist support
}
if (support.includes(SiteSupport.UT_PLANNER)) {
// TODO: Implement ut planner support
}
onContextInvalidated(() => {
const div = document.createElement('div');
div.id = 'context-invalidated-container';
document.body.appendChild(div);
render(
<ContextInvalidated fontFamily='monospace' color={colors.WHITE} backgroundColor={colors.BURNT_ORANGE} />,
div
);
});

View File

@@ -0,0 +1,36 @@
/**
* 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 = 'COURSE_CATALOG',
COURSE_CATALOG_LIST = 'COURSE_CATALOG_LIST',
COURSE_CATALOG_DETAILS = 'COURSE_CATALOG_DETAILS',
UT_PLANNER = 'UT_PLANNER',
WAITLIST = 'WAITLIST',
}
/**
* 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[] {
if (url.includes('utexas.collegescheduler.com')) {
return [SiteSupport.UT_PLANNER];
}
if (url.includes('utdirect.utexas.edu/apps/registrar/course_schedule')) {
const types = [SiteSupport.COURSE_CATALOG];
if (url.includes('results')) {
types.push(SiteSupport.COURSE_CATALOG_LIST);
}
if (document.querySelector('#details')) {
types.push(SiteSupport.COURSE_CATALOG_DETAILS);
}
return types;
}
if (url.includes('utdirect.utexas.edu') && (url.includes('waitlist') || url.includes('classlist'))) {
return [SiteSupport.WAITLIST];
}
return [];
}

View File

@@ -1,5 +0,0 @@
import React from 'react';
import { render } from 'react-dom';
import PopupMain from './PopupMain';
render(<PopupMain />, document.getElementById('root'));

View File

@@ -26,9 +26,9 @@ export default function config(mode: Environment, manifest: chrome.runtime.Manif
// the entry points for the extension (the files that webpack will start bundling from)
const entry: Entries = {
content: [path.resolve('src', 'views', 'content', 'content')],
content: [path.resolve('src', 'views')],
popup: [path.resolve('src', 'views')],
background: [path.resolve('src', 'background', 'background')],
popup: [path.resolve('src', 'views', 'popup', 'popup')],
};
// the entries that need an html file to be generated
@@ -39,8 +39,8 @@ export default function config(mode: Environment, manifest: chrome.runtime.Manif
entry.debug = [path.resolve('src', 'debug')];
// we need to import react-devtools before the react code in development
entry.content = [path.resolve('src', 'views', 'reactDevtools'), ...entry.content];
entry.popup = [path.resolve('src', 'views', 'reactDevtools'), ...entry.popup];
entry.content = [path.resolve('src', 'debug', 'reactDevtools'), ...entry.content];
entry.popup = [path.resolve('src', 'debug', 'reactDevtools'), ...entry.popup];
}
/** @see https://webpack.js.org/configuration for documentation */