refactor: Replace Webpack with Vite (#53)

This commit is contained in:
Razboy20
2024-01-24 19:40:30 -06:00
committed by GitHub
parent 1629c85818
commit 0560a01a55
112 changed files with 7322 additions and 32180 deletions

View File

@@ -1,221 +0,0 @@
[
"ACC",
"ADV",
"ASE",
"AFR",
"AFS",
"ASL",
"AMS",
"AHC",
"ANT",
"ALD",
"ARA",
"ARE",
"ARI",
"ARC",
"AED",
"ARH",
"ART",
"AET",
"AAS",
"ANS",
"AST",
"BSN",
"BEN",
"BCH",
"BIO",
"BME",
"BDP",
"B A",
"BAX",
"BGS",
"CHE",
"CH",
"CHI",
"C E",
"CLA",
"C C",
"CGS",
"COM",
"CLD",
"CMS",
"CRP",
"C L",
"COE",
"CSE",
"C S",
"CON",
"CTI",
"CRW",
"CDI",
"EDC",
"CZ",
"DAN",
"DSC",
"D S",
"DES",
"DEV",
"D B",
"DRS",
"DCH",
"ECO",
"ELP",
"EDP",
"E E",
"ECE",
"EER",
"EMA",
"ENM",
"E M",
"E S",
"E",
"ESL",
"ENS",
"EVE",
"EVS",
"EUP",
"EUS",
"FIN",
"F A",
"FLU",
"FR",
"F H",
"G E",
"GRG",
"GEO",
"GER",
"GSD",
"GOV",
"GRS",
"GK",
"GUI",
"HAR",
"H S",
"HCT",
"HED",
"HEB",
"HIN",
"HIS",
"HDF",
"HDO",
"H E",
"HMN",
"ILA",
"I",
"ISP",
"INF",
"ITD",
"I B",
"IRG",
"ISL",
"ITL",
"ITC",
"JPN",
"J S",
"J",
"KIN",
"KOR",
"LAR",
"LTC",
"LAT",
"LAL",
"LAS",
"LAW",
"LEB",
"L A",
"LAH",
"LIN",
"MAL",
"MAN",
"MIS",
"MFG",
"MNS",
"MKT",
"MSE",
"M",
"M E",
"MDV",
"MAS",
"MEL",
"MES",
"M S",
"MOL",
"MUS",
"NSC",
"N S",
"NEU",
"NOR",
"N",
"NTR",
"OBO",
"OPR",
"O M",
"ORI",
"ORG",
"PER",
"PRS",
"PGE",
"PGS",
"PHM",
"PHL",
"PED",
"P S",
"PHY",
"PIA",
"POL",
"POR",
"PRC",
"PSY",
"P A",
"PBH",
"P R",
"RIM",
"RTF",
"R E",
"R S",
"RHE",
"R M",
"RUS",
"REE",
"SAN",
"SAX",
"STC",
"STM",
"S C",
"SEL",
"S S",
"S W",
"SOC",
"SPN",
"SPC",
"SED",
"SLH",
"STA",
"SDS",
"SUS",
"SWE",
"TAM",
"TXA",
"T D",
"TRO",
"TRU",
"TBA",
"TUR",
"T C",
"UKR",
"UGS",
"UDN",
"URB",
"URD",
"UTS",
"UTL",
"VIA",
"VIO",
"V C",
"VAS",
"VOI",
"WGS",
"WRT",
"YID",
"YOR"
]

3
src/assets/robots.txt Normal file
View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@@ -1,33 +0,0 @@
import io from 'socket.io-client';
import { background } from 'src/shared/messages';
const socket = io('http://localhost:9090');
let reBuilding = false;
socket.on('disconnect', async reason => {
reBuilding = reason.includes('transport') && !reason.includes('client');
});
socket.onAny(args => {
console.log(args);
});
socket.on('connect', async () => {
if (!reBuilding) {
console.log('%c[hot-reloading] listening for changes...', 'color:white; background-color: orange;');
} else {
console.log(
'%c[hot-reloading] changes detected, rebuilding and refreshing...',
'color:white; background-color: orange;'
);
}
});
socket.on('reload', async () => {
console.log('%c[hot-reloading] reloading...', 'color:white; background-color: orange;');
chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
if (tabs?.[0]?.id) {
background.reloadExtension();
}
});
});

View File

@@ -1,7 +1,6 @@
import './hotReload';
import { DevStore } from '@shared/storage/DevStore';
import React, { useEffect } from 'react';
import { DevStore } from 'src/shared/storage/DevStore';
import render from 'src/views/lib/react';
import { createRoot } from 'react-dom/client';
const manifest = chrome.runtime.getManifest();
@@ -146,4 +145,4 @@ function DevDashboard() {
);
}
render(<DevDashboard />, document.getElementById('root'));
createRoot(document.getElementById('root')).render(<DevDashboard />);

14
src/global.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
declare module '*.jpg' {
const content: string;
export default content;
}
declare module '*.png' {
const content: string;
export default content;
}
declare module '*.json' {
const content: string;
export default content;
}

54
src/manifest.ts Normal file
View File

@@ -0,0 +1,54 @@
import { defineManifest } from '@crxjs/vite-plugin';
import packageJson from '../package.json';
// Convert from Semver (example: 0.1.0-beta6)
const [major, minor, patch, label = '0'] = packageJson.version
// can only contain digits, dots, or dash
.replace(/[^\d.-]+/g, '')
// split into version parts
.split(/[.-]/);
const mode = process.env.NODE_ENV;
const HOST_PERMISSIONS: string[] = [
'*://*.utdirect.utexas.edu/apps/registrar/course_schedule/*',
'*://*.utexas.collegescheduler.com/*',
'*://*.catalog.utexas.edu/ribbit/',
'*://*.registrar.utexas.edu/schedules/*',
'*://*.login.utexas.edu/login/*',
];
const manifest = defineManifest(async () => ({
manifest_version: 3,
name: `${packageJson.displayName ?? packageJson.name}${mode === 'development' ? ' (dev)' : ''}`,
version: `${major}.${minor}.${patch}.${label}`,
description: packageJson.description,
options_page: 'src/pages/options/index.html',
background: { service_worker: 'src/pages/background/background.ts' },
permissions: ['storage', 'unlimitedStorage', 'background'],
host_permissions: process.env.MODE === 'development' ? [...HOST_PERMISSIONS, '<all_urls>'] : HOST_PERMISSIONS,
action: {
default_popup: 'src/pages/popup/index.html',
default_icon: `icons/icon_${mode}_32.png`,
},
icons: {
'16': `icons/icon_${mode}_16.png`,
'32': `icons/icon_${mode}_32.png`,
'48': `icons/icon_${mode}_48.png`,
'128': `icons/icon_${mode}_128.png`,
},
content_scripts: [
{
matches: HOST_PERMISSIONS,
js: ['src/pages/content/index.tsx'],
},
],
web_accessible_resources: [
{
resources: ['assets/js/*.js', 'assets/css/*.css', 'assets/img/*'],
matches: ['*://*/*'],
},
],
}));
export default manifest;

View File

@@ -1,10 +1,9 @@
import { BACKGROUND_MESSAGES } from '@shared/messages';
import { MessageListener } from 'chrome-extension-toolkit';
import { BACKGROUND_MESSAGES } from 'src/shared/messages';
import onInstall from './events/onInstall';
import onServiceWorkerAlive from './events/onServiceWorkerAlive';
import onUpdate from './events/onUpdate';
import browserActionHandler from './handler/browserActionHandler';
import hotReloadingHandler from './handler/hotReloadingHandler';
import tabManagementHandler from './handler/tabManagementHandler';
import userScheduleHandler from './handler/userScheduleHandler';
@@ -30,7 +29,6 @@ chrome.runtime.onInstalled.addListener(details => {
// initialize the message listener that will listen for messages from the content script
const messageListener = new MessageListener<BACKGROUND_MESSAGES>({
...browserActionHandler,
...hotReloadingHandler,
...tabManagementHandler,
...userScheduleHandler,
});

View File

@@ -1,4 +1,4 @@
import { ExtensionStore } from '../../shared/storage/ExtensionStore';
import { ExtensionStore } from '@shared/storage/ExtensionStore';
/**
* Called when the extension is first installed or synced onto a new machine

View File

@@ -1,5 +1,4 @@
import { hotReloadTab } from 'src/background/util/hotReloadTab';
import { ExtensionStore } from '../../shared/storage/ExtensionStore';
import { ExtensionStore } from '@shared/storage/ExtensionStore';
/**
* Called when the extension is updated (or when the extension is reloaded in development mode)
@@ -9,8 +8,4 @@ export default async function onUpdate() {
version: chrome.runtime.getManifest().version,
lastUpdate: Date.now(),
});
if (process.env.NODE_ENV === 'development') {
hotReloadTab();
}
}

View File

@@ -1,5 +1,5 @@
import BrowserActionMessages from '@shared/messages/BrowserActionMessages';
import { MessageHandler } from 'chrome-extension-toolkit';
import BrowserActionMessages from 'src/shared/messages/BrowserActionMessages';
const browserActionHandler: MessageHandler<BrowserActionMessages> = {
disableBrowserAction({ sender, sendResponse }) {

View File

@@ -1,6 +1,6 @@
import HotReloadingMessages from 'src/shared/messages/HotReloadingMessages';
import HotReloadingMessages from '@shared/messages/HotReloadingMessages';
import { DevStore } from '@shared/storage/DevStore';
import { MessageHandler } from 'chrome-extension-toolkit';
import { DevStore } from 'src/shared/storage/DevStore';
const hotReloadingHandler: MessageHandler<HotReloadingMessages> = {
async reloadExtension({ sendResponse }) {

View File

@@ -1,5 +1,5 @@
import TabManagementMessages from '@shared/messages/TabManagementMessages';
import { MessageHandler } from 'chrome-extension-toolkit';
import TabManagementMessages from 'src/shared/messages/TabManagementMessages';
import openNewTab from '../util/openNewTab';
const tabManagementHandler: MessageHandler<TabManagementMessages> = {

View File

@@ -1,6 +1,6 @@
import { UserScheduleMessages } from '@shared/messages/UserScheduleMessages';
import { Course } from '@shared/types/Course';
import { MessageHandler } from 'chrome-extension-toolkit';
import { UserScheduleMessages } from 'src/shared/messages/UserScheduleMessages';
import { Course } from 'src/shared/types/Course';
import addCourse from '../lib/addCourse';
import clearCourses from '../lib/clearCourses';
import createSchedule from '../lib/createSchedule';

View File

@@ -1,5 +1,5 @@
import { UserScheduleStore } from 'src/shared/storage/UserScheduleStore';
import { Course } from 'src/shared/types/Course';
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
import { Course } from '@shared/types/Course';
/**
*

View File

@@ -1,4 +1,4 @@
import { UserScheduleStore } from 'src/shared/storage/UserScheduleStore';
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
export default async function clearCourses(scheduleName: string): Promise<void> {
const schedules = await UserScheduleStore.get('schedules');

View File

@@ -1,4 +1,4 @@
import { UserScheduleStore } from 'src/shared/storage/UserScheduleStore';
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
/**
* Creates a new schedule with the given name

View File

@@ -1,4 +1,4 @@
import { UserScheduleStore } from 'src/shared/storage/UserScheduleStore';
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
export default async function deleteSchedule(scheduleName: string): Promise<string | undefined> {
const [schedules, activeIndex] = await Promise.all([

View File

@@ -1,5 +1,5 @@
import { UserScheduleStore } from 'src/shared/storage/UserScheduleStore';
import { Course } from 'src/shared/types/Course';
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
import { Course } from '@shared/types/Course';
/**
*

View File

@@ -1,4 +1,4 @@
import { UserScheduleStore } from 'src/shared/storage/UserScheduleStore';
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
export default async function renameSchedule(scheduleName: string, newName: string): Promise<string | undefined> {
const schedules = await UserScheduleStore.get('schedules');

View File

@@ -1,4 +1,4 @@
import { UserScheduleStore } from 'src/shared/storage/UserScheduleStore';
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
export default async function switchSchedule(scheduleName: string): Promise<void> {
const schedules = await UserScheduleStore.get('schedules');

View File

@@ -1,4 +1,4 @@
import { DevStore } from 'src/shared/storage/DevStore';
import { DevStore } from '@shared/storage/DevStore';
/**
* A list of websites that we don't want to reload when the extension reloads (becuase it'd be hella annoying lmao)
@@ -40,5 +40,3 @@ export async function hotReloadTab(): Promise<void> {
}
});
}

View File

@@ -1,4 +1,4 @@
import { DevStore } from 'src/shared/storage/DevStore';
import { DevStore } from '@shared/storage/DevStore';
/**
* Open the debug tab as the first tab

View File

@@ -0,0 +1,10 @@
import React from 'react';
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
export default function CalendarMain() {
return (
<ExtensionRoot>
<div>Calendar Placeholder</div>
</ExtensionRoot>
);
}

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Calendar</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./index.tsx" type="module"></script>
</body>
</html>

View File

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

View File

@@ -0,0 +1,18 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import CourseCatalogMain from '@views/components/CourseCatalogMain';
import getSiteSupport, { SiteSupport } from '@views/lib/getSiteSupport';
const support = getSiteSupport(window.location.href);
if (support === SiteSupport.COURSE_CATALOG_DETAILS || support === SiteSupport.COURSE_CATALOG_LIST) {
const container = document.createElement('div');
container.id = 'extension-root';
document.body.appendChild(container);
createRoot(container).render(
<React.StrictMode>
<CourseCatalogMain support={support} />
</React.StrictMode>
);
}

13
src/pages/debug/App.tsx Normal file
View File

@@ -0,0 +1,13 @@
import React from 'react';
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
/**
*
*/
export default function App() {
return (
<ExtensionRoot>
<div>hello how are you doing today.</div>
</ExtensionRoot>
);
}

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Debug</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./index.tsx" type="module"></script>
</body>
</html>

View File

@@ -0,0 +1 @@
import 'src/debug';

13
src/pages/options/App.tsx Normal file
View File

@@ -0,0 +1,13 @@
import React from 'react';
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
/**
*
*/
export default function App() {
return (
<ExtensionRoot>
<div>hello how are you doing today.</div>
</ExtensionRoot>
);
}

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Popup</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./index.tsx" type="module"></script>
</body>
</html>

View File

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

View File

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Popup</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./index.tsx" type="module"></script>
</body>
</html>

View File

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

View File

@@ -1,17 +1,13 @@
import { createMessenger } from 'chrome-extension-toolkit';
import TAB_MESSAGES from './TabMessages';
import BrowserActionMessages from './BrowserActionMessages';
import HotReloadingMessages from './HotReloadingMessages';
import TabManagementMessages from './TabManagementMessages';
import TAB_MESSAGES from './TabMessages';
import { 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 &
HotReloadingMessages &
UserScheduleMessages;
export type BACKGROUND_MESSAGES = BrowserActionMessages & TabManagementMessages & UserScheduleMessages;
/**
* A utility object that can be used to send type-safe messages to the background script

View File

@@ -24,10 +24,4 @@ export const DevStore = createLocalStore<IDevStore>({
reloadTabId: undefined,
});
debugStore({ devStore: DevStore });

View File

@@ -9,7 +9,7 @@ interface IOptionsStore {
/** whether we should automatically scroll to load more courses on the course schedule page (without having to click next) */
shouldScrollToLoad: boolean;
url: URL;
// url: URL;
}
export const OptionsStore = createSyncStore<IOptionsStore>({

View File

@@ -1,5 +1,5 @@
import { UserSchedule } from '@shared/types/UserSchedule';
import { createLocalStore, debugStore } from 'chrome-extension-toolkit';
import { UserSchedule } from 'src/shared/types/UserSchedule';
interface IUserScheduleStore {
schedules: UserSchedule[];

View File

@@ -11,7 +11,4 @@
"node"
],
},
"exclude": [
"../webpack"
]
}
}

View File

@@ -1,5 +1,5 @@
import { Course, ScrapedRow } from '@shared/types/Course';
import React, { useEffect, useState } from 'react';
import { Course, ScrapedRow } from 'src/shared/types/Course';
import { useKeyPress } from '../hooks/useKeyPress';
import useSchedules from '../hooks/useSchedules';
import { CourseCatalogScraper } from '../lib/CourseCatalogScraper';
@@ -7,13 +7,12 @@ import getCourseTableRows from '../lib/getCourseTableRows';
import { SiteSupport } from '../lib/getSiteSupport';
import { populateSearchInputs } from '../lib/populateSearchInputs';
import ExtensionRoot from './common/ExtensionRoot/ExtensionRoot';
import Icon from './common/Icon/Icon';
import Text from './common/Text/Text';
import AutoLoad from './injected/AutoLoad/AutoLoad';
import CoursePopup from './injected/CoursePopup/CoursePopup';
import RecruitmentBanner from './injected/RecruitmentBanner/RecruitmentBanner';
import TableHead from './injected/TableHead';
import TableRow from './injected/TableRow/TableRow';
import TableSubheading from './injected/TableSubheading/TableSubheading';
interface Props {
support: SiteSupport.COURSE_CATALOG_DETAILS | SiteSupport.COURSE_CATALOG_LIST;
@@ -33,7 +32,7 @@ export default function CourseCatalogMain({ support }: Props) {
useEffect(() => {
const tableRows = getCourseTableRows(document);
const ccs = new CourseCatalogScraper(support);
const scrapedRows = ccs.scrape(tableRows);
const scrapedRows = ccs.scrape(tableRows, true);
setRows(scrapedRows);
}, [support]);
@@ -64,10 +63,10 @@ export default function CourseCatalogMain({ support }: Props) {
<ExtensionRoot>
<RecruitmentBanner />
<TableHead>Plus</TableHead>
{rows.map(row => {
{rows.map((row, i) => {
if (!row.course) {
// TODO: handle the course section headers
return null;
return <TableSubheading key={row.element.innerText + i.toString()} row={row} />;
}
return (
<TableRow

View File

@@ -1,6 +0,0 @@
import React from 'react';
import ExtensionRoot from './common/ExtensionRoot/ExtensionRoot';
export default function MyCalendarMain() {
return <ExtensionRoot>MyCalendarMain</ExtensionRoot>;
}

View File

@@ -1,5 +1,5 @@
import { background } from '@shared/messages';
import React from 'react';
import { background } from 'src/shared/messages';
import useSchedules from '../hooks/useSchedules';
import { Button } from './common/Button/Button';
import ExtensionRoot from './common/ExtensionRoot/ExtensionRoot';

View File

@@ -1,4 +1,5 @@
@import 'src/views/styles/base.module.scss';
@use 'sass:color';
@use 'src/views/styles/colors.module.scss';
.button {
background-color: #000;
@@ -7,9 +8,8 @@
margin: 10px;
border-radius: 8px;
border: none;
box-shadow: rgba(0, 0, 0, 0.4) 2px 2px 4px;
cursor: pointer;
transition: all 0.2s ease-in-out;
transition: all 0.1s ease-in-out;
font-family: 'Inter';
display: flex;
@@ -22,7 +22,7 @@
}
&:active {
animation: click_animation 0.2s ease-in-out;
transform: scale(0.96);
}
&.disabled {
@@ -30,20 +30,20 @@
opacity: 0.5 !important;
&:active {
animation: none !important;
transform: unset;
}
}
@each $color,
$value
in (
primary: $burnt_orange,
secondary: $charcoal,
tertiary: $bluebonnet,
danger: $speedway_brick,
warning: $tangerine,
success: $turtle_pond,
info: $turquoise
primary: colors.$burnt_orange,
secondary: colors.$charcoal,
tertiary: colors.$bluebonnet,
danger: colors.$speedway_brick,
warning: colors.$tangerine,
success: colors.$turtle_pond,
info: colors.$turquoise
)
{
&.#{$color} {
@@ -51,12 +51,12 @@
color: #fff;
&:hover {
background-color: lighten($value, 10%);
background-color: color.adjust($value, $lightness: 10%);
}
&:focus,
&:focus-visible,
&:active {
background-color: darken($value, 10%);
background-color: color.adjust($value, $lightness: -10%);
}
&.disabled {
@@ -65,15 +65,3 @@
}
}
}
@keyframes click_animation {
0% {
transform: scale(1);
}
50% {
transform: scale(0.9);
}
100% {
transform: scale(1);
}
}

View File

@@ -1,7 +1,7 @@
@import 'src/views/styles/base.module.scss';
@use 'src/views/styles/colors.module.scss';
.card {
background: $white;
background: colors.$white;
border: 0.5px dotted #c3cee0;
box-sizing: border-box;

View File

@@ -1,5 +1,5 @@
@import 'src/views/styles/base.module.scss';
@use 'src/views/styles/colors.module.scss';
hr {
border: 1px solid $limestone;
border: 1px solid colors.$limestone;
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import classnames from 'classnames';
import { Color } from 'src/views/styles/colors.module.scss';
import React from 'react';
import { Color } from '@views/styles/colors.module.scss';
import styles from './Divider.module.scss';
export type Props = {

View File

@@ -1,10 +1,10 @@
@import 'src/views/styles/fonts.module.scss';
@use 'src/views/styles/fonts.module.scss';
.icon {
font-family: 'Material Icons Round';
font-weight: $normal_weight;
font-weight: fonts.$normal_weight;
font-style: normal;
font-size: $medium_size;
font-size: fonts.$medium_size;
line-height: 1;
letter-spacing: normal;
text-transform: none;

View File

@@ -1,10 +1,13 @@
import classNames from 'classnames';
import React from 'react';
import colors, { Color } from 'src/views/styles/colors.module.scss';
import fonts, { Size, Weight } from 'src/views/styles/fonts.module.scss';
import colors, { Color } from '@views/styles/colors.module.scss';
import fonts, { Size } from '@views/styles/fonts.module.scss';
import styles from './Icon.module.scss';
import { MaterialIconCode } from './MaterialIcons';
/**
*
*/
export type Props = {
name: MaterialIconCode;
className?: string;

View File

@@ -1,6 +1,6 @@
import { background } from '@shared/messages';
import classNames from 'classnames';
import React, { PropsWithChildren } from 'react';
import { background } from 'src/shared/messages';
import Text, { TextProps } from '../Text/Text';
import styles from './Link.module.scss';

View File

@@ -1,4 +1,4 @@
@import 'src/views/styles/base.module.scss';
@use 'src/views/styles/colors.module.scss';
.container {
width: 100%;
@@ -18,7 +18,7 @@
.body {
overflow-y: auto;
z-index: 2147483647;
background-color: $white;
background-color: colors.$white;
box-shadow: 0px 12px 30px 0px #323e5f29;
transition: box-shadow 0.15s;
}

View File

@@ -1,11 +1,11 @@
@import 'src/views/styles/base.module.scss';
@use 'src/views/styles/colors.module.scss';
$spinner-border-width: 10px;
.spinner {
border: 1px solid $charcoal;
border: 1px solid colors.$charcoal;
border-width: $spinner-border-width;
border-top: $spinner-border-width solid $tangerine;
border-top: $spinner-border-width solid colors.$tangerine;
margin: 0 auto 15px auto;
border-radius: 50%;
width: 60px;

View File

@@ -1,59 +1,60 @@
@import 'src/views/styles/base.module.scss';
@use 'src/views/styles/colors.module.scss';
@use 'src/views/styles/fonts.module.scss';
.text {
font-family: 'Inter', sans-serif;
color: $charcoal;
color: colors.$charcoal;
line-height: initial;
}
.light_weight {
font-weight: $light_weight;
font-weight: fonts.$light_weight;
}
.regular_weight {
font-weight: $regular_weight;
font-weight: fonts.$regular_weight;
}
.normal_weight {
font-weight: $normal_weight;
font-weight: fonts.$normal_weight;
}
.semi_bold_weight {
font-weight: $semi_bold_weight;
font-weight: fonts.$semi_bold_weight;
}
.bold_weight {
font-weight: $bold_weight;
font-weight: fonts.$bold_weight;
}
.black_weight {
font-weight: $black_weight;
font-weight: fonts.$black_weight;
}
.x_small_size {
font-size: $x_small_size;
font-size: fonts.$x_small_size;
}
.xx_small_size {
font-size: $xx_small_size;
font-size: fonts.$xx_small_size;
}
.small_size {
font-size: $small_size;
font-size: fonts.$small_size;
}
.medium_size {
font-size: $medium_size;
font-size: fonts.$medium_size;
}
.large_size {
font-size: $large_size;
font-size: fonts.$large_size;
}
.x_large_size {
font-size: $x_large_size;
font-size: fonts.$x_large_size;
}
.xx_large_size {
font-size: $xx_large_size;
font-size: fonts.$xx_large_size;
}

View File

@@ -1,9 +1,12 @@
import classNames from 'classnames';
import React, { PropsWithChildren } from 'react';
import colors, { Color } from 'src/views/styles/colors.module.scss';
import fonts, { Size, Weight } from 'src/views/styles/fonts.module.scss';
import colors, { Color } from '@views/styles/colors.module.scss';
import { Size, Weight } from '@views/styles/fonts.module.scss';
import styles from './Text.module.scss';
/**
*
*/
export type TextProps = {
color?: Color;
weight?: Weight;

View File

@@ -1,15 +1,14 @@
import { ScrapedRow } from '@shared/types/Course';
import React, { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { ScrapedRow } from 'src/shared/types/Course';
import useInfiniteScroll from 'src/views/hooks/useInfiniteScroll';
import { CourseCatalogScraper } from 'src/views/lib/CourseCatalogScraper';
import { SiteSupport } from 'src/views/lib/getSiteSupport';
import useInfiniteScroll from '@views/hooks/useInfiniteScroll';
import { CourseCatalogScraper } from '@views/lib/CourseCatalogScraper';
import { SiteSupport } from '@views/lib/getSiteSupport';
import {
loadNextCourseCatalogPage,
AutoLoadStatus,
loadNextCourseCatalogPage,
removePaginationButtons,
} from 'src/views/lib/loadNextCourseCatalogPage';
import Spinner from '../../common/Spinner/Spinner';
} from '@views/lib/loadNextCourseCatalogPage';
import styles from './AutoLoad.module.scss';
type Props = {
@@ -53,16 +52,21 @@ export default function AutoLoad({ addRows }: Props) {
addRows(scrapedRows);
}, [addRows]);
if (!container || status === AutoLoadStatus.IDLE) {
if (!container || status === AutoLoadStatus.DONE) {
return null;
}
return createPortal(
<div>
{status === AutoLoadStatus.LOADING && (
<div>
<Spinner />
<h2>Loading Next Page...</h2>
{status !== AutoLoadStatus.ERROR && (
<div
style={{
height: '500px',
backgroundColor: '#f4f4f4',
}}
>
{/* <Spinner />
<h2>Loading Next Page...</h2> */}
</div>
)}
{status === AutoLoadStatus.ERROR && (

View File

@@ -1,4 +1,4 @@
@import 'src/views/styles/base.module.scss';
@use 'src/views/styles/colors.module.scss';
.container {
margin: 20px;
@@ -22,7 +22,7 @@
}
.restriction {
color: $speedway_brick;
color: colors.$speedway_brick;
}
}
}

View File

@@ -1,10 +1,10 @@
import { Course } from '@shared/types/Course';
import classNames from 'classnames';
import React, { useEffect, useState } from 'react';
import { Course } from 'src/shared/types/Course';
import Spinner from 'src/views/components/common/Spinner/Spinner';
import Text from 'src/views/components/common/Text/Text';
import { CourseCatalogScraper } from 'src/views/lib/CourseCatalogScraper';
import { SiteSupport } from 'src/views/lib/getSiteSupport';
import Spinner from '@views/components/common/Spinner/Spinner';
import Text from '@views/components/common/Text/Text';
import { CourseCatalogScraper } from '@views/lib/CourseCatalogScraper';
import { SiteSupport } from '@views/lib/getSiteSupport';
import Card from '../../../common/Card/Card';
import styles from './CourseDescription.module.scss';
@@ -18,6 +18,9 @@ enum LoadStatus {
ERROR = 'ERROR',
}
/**
*
*/
export default function CourseDescription({ course }: Props) {
const [description, setDescription] = useState<string[]>([]);
const [status, setStatus] = useState<LoadStatus>(LoadStatus.LOADING);

View File

@@ -1,11 +1,11 @@
import { background } from '@shared/messages';
import { Course } from '@shared/types/Course';
import { UserSchedule } from '@shared/types/UserSchedule';
import React from 'react';
import { background } from 'src/shared/messages';
import { Course } from 'src/shared/types/Course';
import { UserSchedule } from 'src/shared/types/UserSchedule';
import { Button } from 'src/views/components/common/Button/Button';
import Card from 'src/views/components/common/Card/Card';
import Icon from 'src/views/components/common/Icon/Icon';
import Text from 'src/views/components/common/Text/Text';
import { Button } from '@views/components/common/Button/Button';
import Card from '@views/components/common/Card/Card';
import Icon from '@views/components/common/Icon/Icon';
import Text from '@views/components/common/Text/Text';
import styles from './CourseButtons.module.scss';
type Props = {

View File

@@ -1,10 +1,10 @@
import { Course } from '@shared/types/Course';
import { UserSchedule } from '@shared/types/UserSchedule';
import React from 'react';
import { Course } from 'src/shared/types/Course';
import { UserSchedule } from 'src/shared/types/UserSchedule';
import Card from 'src/views/components/common/Card/Card';
import Icon from 'src/views/components/common/Icon/Icon';
import Link from 'src/views/components/common/Link/Link';
import Text from 'src/views/components/common/Text/Text';
import Card from '@views/components/common/Card/Card';
import Icon from '@views/components/common/Icon/Icon';
import Link from '@views/components/common/Link/Link';
import Text from '@views/components/common/Text/Text';
import CourseButtons from './CourseButtons/CourseButtons';
import styles from './CourseHeader.module.scss';

View File

@@ -1,6 +1,6 @@
import { Course } from '@shared/types/Course';
import { UserSchedule } from '@shared/types/UserSchedule';
import React from 'react';
import { Course } from 'src/shared/types/Course';
import { UserSchedule } from 'src/shared/types/UserSchedule';
import Popup from '../../common/Popup/Popup';
import CourseDescription from './CourseDescription/CourseDescription';
import CourseHeader from './CourseHeader/CourseHeader';

View File

@@ -1,4 +1,5 @@
@import 'src/views/styles/base.module.scss';
@use 'src/views/styles/colors.module.scss';
@use 'src/views/styles/elevation.module.scss';
.chartContainer {
height: 250px;
@@ -14,11 +15,11 @@
justify-content: center;
select {
z-index: $MAX_Z_INDEX;
z-index: elevation.$MAX_Z_INDEX;
padding: 4px;
font-family: 'Inter';
border-radius: 8px;
border-color: $charcoal;
border-color: colors.$charcoal;
}
}

View File

@@ -1,19 +1,19 @@
/* eslint-disable no-nested-ternary */
import React, { useEffect, useRef, useState } from 'react';
import HighchartsReact from 'highcharts-react-official';
import { Course, Semester } from '@shared/types/Course';
import { Distribution, LetterGrade } from '@shared/types/Distribution';
import Highcharts from 'highcharts';
import Card from 'src/views/components/common/Card/Card';
import { Course, Semester } from 'src/shared/types/Course';
import colors from 'src/views/styles/colors.module.scss';
import Spinner from 'src/views/components/common/Spinner/Spinner';
import Text from 'src/views/components/common/Text/Text';
import Icon from 'src/views/components/common/Icon/Icon';
import { Distribution, LetterGrade } from 'src/shared/types/Distribution';
import HighchartsReact from 'highcharts-react-official';
import React, { useEffect, useRef, useState } from 'react';
import Card from '@views/components/common/Card/Card';
import Icon from '@views/components/common/Icon/Icon';
import Spinner from '@views/components/common/Spinner/Spinner';
import Text from '@views/components/common/Text/Text';
import {
NoDataError,
queryAggregateDistribution,
querySemesterDistribution,
} from 'src/views/lib/database/queryDistribution';
} from '@views/lib/database/queryDistribution';
import colors from '@views/styles/colors.module.scss';
import styles from './GradeDistribution.module.scss';
enum DataStatus {

View File

@@ -1,7 +1,7 @@
@import 'src/views/styles/base.module.scss';
@use 'src/views/styles/colors.module.scss';
.container {
background-color: $burnt_orange;
background-color: colors.$burnt_orange;
color: white;
text-align: center;
padding: 12px;

View File

@@ -15,6 +15,9 @@ export default function TableHead({ children }: PropsWithChildren) {
const lastTableHeadCell = document.querySelector('table thead th:last-child');
lastTableHeadCell!.after(container);
setContainer(container);
return () => {
container.remove();
};
}, []);
if (!container) {

View File

@@ -1,33 +1,21 @@
@import 'src/views/styles/base.module.scss';
.rowButton {
margin: 0px;
}
.selectedRow {
* {
background: $burnt_orange !important;
color: white !important;
box-shadow: none !important;
}
}
.inActiveSchedule {
* {
color: $turtle_pond !important;
font-weight: bold !important;
}
}
.isConflict {
* {
color: $speedway_brick !important;
font-weight: normal !important;
text-decoration: line-through !important;
}
}
@use 'src/views/styles/colors.module.scss';
.row {
> td:first-child {
padding-left: 12px !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
> td:last-child {
padding-right: 12px !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
> * {
transition: background-color 0.1s ease-in-out;
}
.conflictTooltip {
position: relative;
display: none;
@@ -54,4 +42,46 @@
display: initial;
}
}
// :global(ul.flag) li {
// transform: scale(1.5); // omg the flags are on ONE LONG GIF FILE AND SHIFTED BY Y COORDINATES
// }
}
.rowButton {
margin: 0.25rem 0;
&:active {
transform: scale(0.92);
}
width: 32px;
height: 32px;
place-items: center;
display: inline-flex;
}
.selectedRow {
> * {
background: colors.$burnt_orange !important;
color: white !important;
box-shadow: none !important;
}
.rowButton {
background: colors.$burnt_orange !important;
box-shadow: none !important;
}
}
.inActiveSchedule {
* {
color: colors.$turtle_pond !important;
font-weight: bold !important;
}
}
.isConflict {
* {
color: colors.$speedway_brick !important;
font-weight: normal !important;
text-decoration: line-through !important;
}
}

View File

@@ -1,7 +1,7 @@
import { Course, ScrapedRow } from '@shared/types/Course';
import { UserSchedule } from '@shared/types/UserSchedule';
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
import { Course, ScrapedRow } from 'src/shared/types/Course';
import { UserSchedule } from 'src/shared/types/UserSchedule';
import { Button } from '../../common/Button/Button';
import Icon from '../../common/Icon/Icon';
import Text from '../../common/Text/Text';
@@ -29,6 +29,7 @@ export default function TableRow({ row, isSelected, activeSchedule, onClick }: P
useEffect(() => {
element.classList.add(styles.row);
const portalContainer = document.createElement('td');
portalContainer.style.textAlign = 'right';
const lastTableCell = element.querySelector('td:last-child');
lastTableCell!.after(portalContainer);
setContainer(portalContainer);

View File

@@ -0,0 +1,9 @@
@use 'src/views/styles/fonts.module.scss';
.subheader {
h2 {
font-size: fonts.$medium_size;
margin-top: 1.25rem;
margin-bottom: 0.25rem;
}
}

View File

@@ -0,0 +1,25 @@
import { ScrapedRow } from '@shared/types/Course';
import { useEffect } from 'react';
import styles from './TableSubheading.module.scss';
interface Props {
row: ScrapedRow;
}
/**
* This component is injected into each row of the course catalog table.
* @returns a react portal to the new td in the column or null if the column has not been created yet.
*/
export default function TableSubheading({ row }: Props) {
const { element } = row;
useEffect(() => {
element.classList.add(styles.subheader);
return () => {
element.classList.remove(styles.subheader);
};
}, [element]);
return null;
}

View File

@@ -6,6 +6,9 @@ import { useEffect } from 'react';
* @returns isLoading boolean to indicate if the callback is currently being executed
*/
/**
*
*/
export default function useInfiniteScroll(
callback: () => Promise<void> | void,
deps?: React.DependencyList | undefined
@@ -13,13 +16,15 @@ export default function useInfiniteScroll(
const isScrolling = () => {
const { innerHeight } = window;
const { scrollTop, offsetHeight } = document.documentElement;
if (innerHeight + scrollTop >= offsetHeight - 100) {
if (innerHeight + scrollTop >= offsetHeight - 650) {
callback();
}
};
useEffect(() => {
window.addEventListener('scroll', isScrolling);
window.addEventListener('scroll', isScrolling, {
passive: true,
});
return () => window.removeEventListener('scroll', isScrolling);
}, deps);
}

View File

@@ -1,7 +1,6 @@
import { Serialized } from 'chrome-extension-toolkit';
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
import { UserSchedule } from '@shared/types/UserSchedule';
import { useEffect, useState } from 'react';
import { UserScheduleStore } from 'src/shared/storage/UserScheduleStore';
import { UserSchedule } from 'src/shared/types/UserSchedule';
export default function useSchedules(): [active: UserSchedule | null, schedules: UserSchedule[]] {
const [schedules, setSchedules] = useState<UserSchedule[]>([]);

View File

@@ -1,4 +1,4 @@
import TAB_MESSAGES from '@shared/messages/TabMessages';
import { createUseMessage } from 'chrome-extension-toolkit';
import TAB_MESSAGES from 'src/shared/messages/TabMessages';
export const useTabMessage = createUseMessage<TAB_MESSAGES>();
export const useTabMessage = createUseMessage<TAB_MESSAGES>();

View File

@@ -1,6 +1,5 @@
import { Serialized } from 'chrome-extension-toolkit';
import { ExtensionStore } from '@shared/storage/ExtensionStore';
import { useEffect, useState } from 'react';
import { ExtensionStore } from 'src/shared/storage/ExtensionStore';
export default function useVersion(): string {
const [version, setVersion] = useState<string>('');
@@ -17,4 +16,3 @@ export default function useVersion(): string {
return version;
}

View File

@@ -1,12 +1,11 @@
import { ContextInvalidated, createShadowDOM, onContextInvalidated } from 'chrome-extension-toolkit';
import React from 'react';
import { background } from 'src/shared/messages';
import render from './lib/react';
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';
import getSiteSupport, { SiteSupport } from './lib/getSiteSupport';
import colors from './styles/colors.module.scss';
const support = getSiteSupport(window.location.href);
console.log('support:', support);

View File

@@ -1,8 +1,7 @@
import { Serialized } from 'chrome-extension-toolkit';
import { Course, Status, InstructionMode, ScrapedRow, Semester } from 'src/shared/types/Course';
import { CourseSchedule } from 'src/shared/types/CourseSchedule';
import Instructor from 'src/shared/types/Instructor';
import { SiteSupport } from 'src/views/lib/getSiteSupport';
import { Course, InstructionMode, ScrapedRow, Semester, Status } from '@shared/types/Course';
import { CourseSchedule } from '@shared/types/CourseSchedule';
import Instructor from '@shared/types/Instructor';
import { SiteSupport } from '@views/lib/getSiteSupport';
/**
* The selectors that we use to scrape the course catalog list table (https://utdirect.utexas.edu/apps/registrar/course_schedule/20239/results/?fos_fl=C+S&level=U&search_type_main=FIELD)

View File

@@ -1,7 +1,8 @@
import initSqlJs from 'sql.js/dist/sql-wasm';
const WASM_FILE_URL = chrome.runtime.getURL('database/sql-wasm.wasm');
const DB_FILE_URL = chrome.runtime.getURL('database/grades.db');
import DB_FILE_URL from '@public/database/grades.db?url';
import WASM_FILE_URL from 'sql.js/dist/sql-wasm.wasm?url';
// import WASM_FILE_URL from '../../../../public/database/sql-wasm.wasm?url';
/**
* A utility type for the SQL.js Database type

View File

@@ -1,5 +1,5 @@
import { Course, Semester } from 'src/shared/types/Course';
import { CourseSQLRow, Distribution } from 'src/shared/types/Distribution';
import { Course, Semester } from '@shared/types/Course';
import { CourseSQLRow, Distribution } from '@shared/types/Distribution';
import { initializeDB } from './initializeDB';
/**

View File

@@ -9,6 +9,7 @@ export enum AutoLoadStatus {
LOADING = 'LOADING',
IDLE = 'IDLE',
ERROR = 'ERROR',
DONE = 'DONE',
}
let isLoading = false;
@@ -24,7 +25,7 @@ let nextPageURL = getNextButton(document)?.href;
export async function loadNextCourseCatalogPage(): Promise<[AutoLoadStatus, HTMLTableRowElement[]]> {
// if there is no more nextPageURL, then we have reached the end of the course catalog, so we can stop
if (!nextPageURL) {
return [AutoLoadStatus.IDLE, []];
return [AutoLoadStatus.DONE, []];
}
// remove the next button so that we don't load the same page twice
removePaginationButtons(document);

View File

@@ -1,4 +1,4 @@
@import './colors.module.scss';
@import './fonts.module.scss';
@import './elevation.module.scss';
@import './utils.module.scss';
@use 'colors.module.scss';
@use 'fonts.module.scss';
@use 'elevation.module.scss';
@use 'utils.module.scss';

View File

@@ -1,7 +1,7 @@
@each $weights in '100' '200' '300' '400' '500' '600' '700' '800' '900' {
@font-face {
font-family: 'Inter';
src: url('chrome-extension://__MSG_@@extension_id__/fonts/inter-#{$weights}.woff2') format('woff2');
src: url('@public/fonts/inter-#{$weights}.woff2') format('woff2');
font-display: auto;
font-style: normal;
font-weight: #{$weights};
@@ -13,7 +13,7 @@
font-style: normal;
font-display: block;
font-weight: 400;
src: url('chrome-extension://__MSG_@@extension_id__/fonts/material-icons.woff2') format('woff2');
src: url('@public/fonts/material-icons.woff2') format('woff2');
}
$light_weight: 300;

1
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />