injecting into table, created table header, and buttons for each row
This commit is contained in:
17
src/shared/types/Course.ts
Normal file
17
src/shared/types/Course.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Serialized } from 'chrome-extension-toolkit';
|
||||||
|
|
||||||
|
type CourseSchedule = {};
|
||||||
|
|
||||||
|
export class Course {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
professor: string;
|
||||||
|
schedule: CourseSchedule;
|
||||||
|
currentStatus: string;
|
||||||
|
url: string;
|
||||||
|
registerURL?: string;
|
||||||
|
|
||||||
|
constructor(course: Course | Serialized<Course>) {
|
||||||
|
Object.assign(this, course);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,88 @@
|
|||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import { bMessenger } from 'src/shared/messages';
|
import { bMessenger } from 'src/shared/messages';
|
||||||
|
import { Course } from 'src/shared/types/Course';
|
||||||
|
import { populateSearchInputs } from '../lib/courseCatalog/populateSearchInputs';
|
||||||
import { SiteSupport } from '../lib/getSiteSupport';
|
import { SiteSupport } from '../lib/getSiteSupport';
|
||||||
import { Button } from './common/Button/Button';
|
import { Button } from './common/Button/Button';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
support: SiteSupport[];
|
support: SiteSupport.COURSE_CATALOG_DETAILS | SiteSupport.COURSE_CATALOG_LIST;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CourseCatalogMain(props: Props) {
|
/**
|
||||||
const openGoogle = () => {
|
* This is the top level react component orchestrating the course catalog page.
|
||||||
bMessenger.openNewTab({ url: 'https://google.com' });
|
*/
|
||||||
|
export default function CourseCatalogMain({ support }: Props) {
|
||||||
|
const [rows, setRows] = React.useState<HTMLTableRowElement[]>([]);
|
||||||
|
const [selectedCourse, setSelectedCourse] = React.useState<Course | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
populateSearchInputs();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const rows = scrapeRowsFromCourseTable();
|
||||||
|
setRows(rows);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<TableHead />
|
||||||
|
{rows.map(row => (
|
||||||
|
<TableRow key={row.id} row={row} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TableRow: (props: { row: HTMLTableRowElement }) => JSX.Element | null = ({ row }) => {
|
||||||
|
const [portalContainer, setPortalContainer] = React.useState<HTMLTableCellElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const portalContainer = document.createElement('td');
|
||||||
|
portalContainer.setAttribute('id', 'ut-registration-plus-table-row-portal');
|
||||||
|
const lastTableCell = row.querySelector('td:last-child');
|
||||||
|
lastTableCell!.after(portalContainer);
|
||||||
|
setPortalContainer(portalContainer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!portalContainer) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReactDOM.createPortal(<Button>Plus</Button>, portalContainer);
|
||||||
};
|
};
|
||||||
|
|
||||||
return <Button onClick={openGoogle}>{props.support.join(',')}</Button>;
|
const TableHead = () => {
|
||||||
|
const [portalContainer, setPortalContainer] = React.useState<HTMLTableHeaderCellElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const portalContainer = document.createElement('th');
|
||||||
|
portalContainer.setAttribute('scope', 'col');
|
||||||
|
portalContainer.setAttribute('id', 'ut-registration-plus-table-head-portal');
|
||||||
|
const lastTableHeadCell = document.querySelector('table thead th:last-child');
|
||||||
|
lastTableHeadCell!.after(portalContainer);
|
||||||
|
setPortalContainer(portalContainer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!portalContainer) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReactDOM.createPortal(<span>Plus</span>, portalContainer);
|
||||||
|
};
|
||||||
|
|
||||||
|
function scrapeRowsFromCourseTable(): HTMLTableRowElement[] {
|
||||||
|
const rows = Array.from(document.querySelectorAll('table tbody tr')) as HTMLTableRowElement[];
|
||||||
|
|
||||||
|
return Array.from(rows).filter(row => {
|
||||||
|
if (row.querySelector('th')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (row.querySelector('td.course_header')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,22 +7,25 @@ import getSiteSupport, { SiteSupport } from './lib/getSiteSupport';
|
|||||||
import PopupMain from './components/PopupMain';
|
import PopupMain from './components/PopupMain';
|
||||||
|
|
||||||
const support = getSiteSupport(window.location.href);
|
const support = getSiteSupport(window.location.href);
|
||||||
|
if (!support) {
|
||||||
|
throw new Error('UT Registration Plus does not support this page, even though it should...');
|
||||||
|
}
|
||||||
|
|
||||||
if (isExtensionPopup()) {
|
if (isExtensionPopup()) {
|
||||||
render(<PopupMain />, document.getElementById('root'));
|
render(<PopupMain />, document.getElementById('root'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (support.includes(SiteSupport.COURSE_CATALOG)) {
|
if (support === SiteSupport.COURSE_CATALOG_DETAILS || support === SiteSupport.COURSE_CATALOG_LIST) {
|
||||||
const shadowDom = createShadowDOM('ut-registration-plus-dom-container');
|
const shadowDom = createShadowDOM('ut-registration-plus-dom-container');
|
||||||
render(<CourseCatalogMain support={support} />, shadowDom.shadowRoot);
|
render(<CourseCatalogMain support={support} />, shadowDom.shadowRoot);
|
||||||
shadowDom.addStyle('static/css/content.css');
|
shadowDom.addStyle('static/css/content.css');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (support.includes(SiteSupport.WAITLIST)) {
|
if (support === SiteSupport.WAITLIST) {
|
||||||
// TODO: Implement waitlist support
|
// TODO: Implement waitlist support
|
||||||
}
|
}
|
||||||
|
|
||||||
if (support.includes(SiteSupport.UT_PLANNER)) {
|
if (support === SiteSupport.UT_PLANNER) {
|
||||||
// TODO: Implement ut planner support
|
// TODO: Implement ut planner support
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
* a given url/page can correspond to many of these enum values
|
* a given url/page can correspond to many of these enum values
|
||||||
*/
|
*/
|
||||||
export enum SiteSupport {
|
export enum SiteSupport {
|
||||||
COURSE_CATALOG = 'COURSE_CATALOG',
|
|
||||||
COURSE_CATALOG_LIST = 'COURSE_CATALOG_LIST',
|
COURSE_CATALOG_LIST = 'COURSE_CATALOG_LIST',
|
||||||
COURSE_CATALOG_DETAILS = 'COURSE_CATALOG_DETAILS',
|
COURSE_CATALOG_DETAILS = 'COURSE_CATALOG_DETAILS',
|
||||||
UT_PLANNER = 'UT_PLANNER',
|
UT_PLANNER = 'UT_PLANNER',
|
||||||
@@ -15,22 +14,20 @@ export enum SiteSupport {
|
|||||||
* @param url the url of the current page
|
* @param url the url of the current page
|
||||||
* @returns a list of page types that the current page is
|
* @returns a list of page types that the current page is
|
||||||
*/
|
*/
|
||||||
export default function getSiteSupport(url: string): SiteSupport[] {
|
export default function getSiteSupport(url: string): SiteSupport | null {
|
||||||
if (url.includes('utexas.collegescheduler.com')) {
|
if (url.includes('utexas.collegescheduler.com')) {
|
||||||
return [SiteSupport.UT_PLANNER];
|
return SiteSupport.UT_PLANNER;
|
||||||
}
|
}
|
||||||
if (url.includes('utdirect.utexas.edu/apps/registrar/course_schedule')) {
|
if (url.includes('utdirect.utexas.edu/apps/registrar/course_schedule')) {
|
||||||
const types = [SiteSupport.COURSE_CATALOG];
|
|
||||||
if (url.includes('results')) {
|
if (url.includes('results')) {
|
||||||
types.push(SiteSupport.COURSE_CATALOG_LIST);
|
return SiteSupport.COURSE_CATALOG_LIST;
|
||||||
}
|
}
|
||||||
if (document.querySelector('#details')) {
|
if (document.querySelector('#details')) {
|
||||||
types.push(SiteSupport.COURSE_CATALOG_DETAILS);
|
return SiteSupport.COURSE_CATALOG_DETAILS;
|
||||||
}
|
}
|
||||||
return types;
|
|
||||||
}
|
}
|
||||||
if (url.includes('utdirect.utexas.edu') && (url.includes('waitlist') || url.includes('classlist'))) {
|
if (url.includes('utdirect.utexas.edu') && (url.includes('waitlist') || url.includes('classlist'))) {
|
||||||
return [SiteSupport.WAITLIST];
|
return SiteSupport.WAITLIST;
|
||||||
}
|
}
|
||||||
return [];
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
4
src/views/styles/colors.module.scss.d.ts
vendored
4
src/views/styles/colors.module.scss.d.ts
vendored
@@ -1,3 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* This is a file that we need to create to tell typescript what the shape of the css modules is
|
||||||
|
* when we import them into ts/tsx files
|
||||||
|
*/
|
||||||
export interface ISassColors {
|
export interface ISassColors {
|
||||||
BURNT_ORANGE: string;
|
BURNT_ORANGE: string;
|
||||||
CHARCOAL: string;
|
CHARCOAL: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user