From e9c420a87358411bce7fcad4e7e4fa60812309d1 Mon Sep 17 00:00:00 2001 From: Sriram Hariharan Date: Fri, 3 Mar 2023 21:57:00 -0600 Subject: [PATCH] injecting into table, created table header, and buttons for each row --- src/shared/types/Course.ts | 17 ++++ src/views/components/CourseCatalogMain.tsx | 84 +++++++++++++++++-- src/views/index.tsx | 9 +- .../index.ts | 0 .../populateSearchInputs.ts | 0 src/views/lib/getSiteSupport.ts | 15 ++-- src/views/styles/colors.module.scss.d.ts | 4 + 7 files changed, 111 insertions(+), 18 deletions(-) create mode 100644 src/shared/types/Course.ts rename src/views/lib/{courseSchedule => courseCatalog}/index.ts (100%) rename src/views/lib/{courseSchedule => courseCatalog}/populateSearchInputs.ts (100%) diff --git a/src/shared/types/Course.ts b/src/shared/types/Course.ts new file mode 100644 index 00000000..588f8da4 --- /dev/null +++ b/src/shared/types/Course.ts @@ -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) { + Object.assign(this, course); + } +} diff --git a/src/views/components/CourseCatalogMain.tsx b/src/views/components/CourseCatalogMain.tsx index 5755c6b2..c80ee0b9 100644 --- a/src/views/components/CourseCatalogMain.tsx +++ b/src/views/components/CourseCatalogMain.tsx @@ -1,16 +1,88 @@ import React, { useEffect, useMemo } from 'react'; +import ReactDOM from 'react-dom'; 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 { Button } from './common/Button/Button'; interface Props { - support: SiteSupport[]; + support: SiteSupport.COURSE_CATALOG_DETAILS | SiteSupport.COURSE_CATALOG_LIST; } -export default function CourseCatalogMain(props: Props) { - const openGoogle = () => { - bMessenger.openNewTab({ url: 'https://google.com' }); - }; +/** + * This is the top level react component orchestrating the course catalog page. + */ +export default function CourseCatalogMain({ support }: Props) { + const [rows, setRows] = React.useState([]); + const [selectedCourse, setSelectedCourse] = React.useState(null); - return ; + useEffect(() => { + populateSearchInputs(); + }, []); + + useEffect(() => { + const rows = scrapeRowsFromCourseTable(); + setRows(rows); + }, []); + + return ( +
+ + {rows.map(row => ( + + ))} +
+ ); +} + +const TableRow: (props: { row: HTMLTableRowElement }) => JSX.Element | null = ({ row }) => { + const [portalContainer, setPortalContainer] = React.useState(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(, portalContainer); +}; + +const TableHead = () => { + const [portalContainer, setPortalContainer] = React.useState(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(Plus, 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; + }); } diff --git a/src/views/index.tsx b/src/views/index.tsx index ee88f117..61cca788 100644 --- a/src/views/index.tsx +++ b/src/views/index.tsx @@ -7,22 +7,25 @@ import getSiteSupport, { SiteSupport } from './lib/getSiteSupport'; import PopupMain from './components/PopupMain'; 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()) { render(, 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'); render(, shadowDom.shadowRoot); shadowDom.addStyle('static/css/content.css'); } -if (support.includes(SiteSupport.WAITLIST)) { +if (support === SiteSupport.WAITLIST) { // TODO: Implement waitlist support } -if (support.includes(SiteSupport.UT_PLANNER)) { +if (support === SiteSupport.UT_PLANNER) { // TODO: Implement ut planner support } diff --git a/src/views/lib/courseSchedule/index.ts b/src/views/lib/courseCatalog/index.ts similarity index 100% rename from src/views/lib/courseSchedule/index.ts rename to src/views/lib/courseCatalog/index.ts diff --git a/src/views/lib/courseSchedule/populateSearchInputs.ts b/src/views/lib/courseCatalog/populateSearchInputs.ts similarity index 100% rename from src/views/lib/courseSchedule/populateSearchInputs.ts rename to src/views/lib/courseCatalog/populateSearchInputs.ts diff --git a/src/views/lib/getSiteSupport.ts b/src/views/lib/getSiteSupport.ts index bafe2ad2..44792455 100644 --- a/src/views/lib/getSiteSupport.ts +++ b/src/views/lib/getSiteSupport.ts @@ -3,7 +3,6 @@ * 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', @@ -15,22 +14,20 @@ export enum SiteSupport { * @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[] { +export default function getSiteSupport(url: string): SiteSupport | null { if (url.includes('utexas.collegescheduler.com')) { - return [SiteSupport.UT_PLANNER]; + 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); + return SiteSupport.COURSE_CATALOG_LIST; } 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'))) { - return [SiteSupport.WAITLIST]; + return SiteSupport.WAITLIST; } - return []; + return null; } diff --git a/src/views/styles/colors.module.scss.d.ts b/src/views/styles/colors.module.scss.d.ts index cfda76a9..6c0f096a 100644 --- a/src/views/styles/colors.module.scss.d.ts +++ b/src/views/styles/colors.module.scss.d.ts @@ -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 { BURNT_ORANGE: string; CHARCOAL: string;