-
-
Loading Next Page...
+ {status !== AutoLoadStatus.ERROR && (
+
+ {/*
+
Loading Next Page...
*/}
)}
{status === AutoLoadStatus.ERROR && (
diff --git a/src/views/components/injected/CoursePopup/CourseDescription/CourseDescription.module.scss b/src/views/components/injected/CoursePopup/CourseDescription/CourseDescription.module.scss
index b560a21a..07f9ca67 100644
--- a/src/views/components/injected/CoursePopup/CourseDescription/CourseDescription.module.scss
+++ b/src/views/components/injected/CoursePopup/CourseDescription/CourseDescription.module.scss
@@ -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;
}
}
}
diff --git a/src/views/components/injected/CoursePopup/CourseDescription/CourseDescription.tsx b/src/views/components/injected/CoursePopup/CourseDescription/CourseDescription.tsx
index 1c7a77b1..a130948a 100644
--- a/src/views/components/injected/CoursePopup/CourseDescription/CourseDescription.tsx
+++ b/src/views/components/injected/CoursePopup/CourseDescription/CourseDescription.tsx
@@ -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
([]);
const [status, setStatus] = useState(LoadStatus.LOADING);
diff --git a/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.tsx b/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.tsx
index 5b5d1889..d89c6dde 100644
--- a/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.tsx
+++ b/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.tsx
@@ -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 = {
diff --git a/src/views/components/injected/CoursePopup/CourseHeader/CourseHeader.tsx b/src/views/components/injected/CoursePopup/CourseHeader/CourseHeader.tsx
index 8576972f..123b3863 100644
--- a/src/views/components/injected/CoursePopup/CourseHeader/CourseHeader.tsx
+++ b/src/views/components/injected/CoursePopup/CourseHeader/CourseHeader.tsx
@@ -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';
diff --git a/src/views/components/injected/CoursePopup/CoursePopup.tsx b/src/views/components/injected/CoursePopup/CoursePopup.tsx
index fe11e443..8cc99978 100644
--- a/src/views/components/injected/CoursePopup/CoursePopup.tsx
+++ b/src/views/components/injected/CoursePopup/CoursePopup.tsx
@@ -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';
diff --git a/src/views/components/injected/CoursePopup/GradeDistribution/GradeDistribution.module.scss b/src/views/components/injected/CoursePopup/GradeDistribution/GradeDistribution.module.scss
index a1a909a5..04bef21e 100644
--- a/src/views/components/injected/CoursePopup/GradeDistribution/GradeDistribution.module.scss
+++ b/src/views/components/injected/CoursePopup/GradeDistribution/GradeDistribution.module.scss
@@ -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;
}
}
diff --git a/src/views/components/injected/CoursePopup/GradeDistribution/GradeDistribution.tsx b/src/views/components/injected/CoursePopup/GradeDistribution/GradeDistribution.tsx
index 1f52f6b3..af695d85 100644
--- a/src/views/components/injected/CoursePopup/GradeDistribution/GradeDistribution.tsx
+++ b/src/views/components/injected/CoursePopup/GradeDistribution/GradeDistribution.tsx
@@ -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 {
diff --git a/src/views/components/injected/RecruitmentBanner/RecruitmentBanner.module.scss b/src/views/components/injected/RecruitmentBanner/RecruitmentBanner.module.scss
index 892129a9..fe4ca6cb 100644
--- a/src/views/components/injected/RecruitmentBanner/RecruitmentBanner.module.scss
+++ b/src/views/components/injected/RecruitmentBanner/RecruitmentBanner.module.scss
@@ -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;
diff --git a/src/views/components/injected/TableHead.tsx b/src/views/components/injected/TableHead.tsx
index dcd4796e..e187597f 100644
--- a/src/views/components/injected/TableHead.tsx
+++ b/src/views/components/injected/TableHead.tsx
@@ -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) {
diff --git a/src/views/components/injected/TableRow/TableRow.module.scss b/src/views/components/injected/TableRow/TableRow.module.scss
index 0ba2551c..690fef02 100644
--- a/src/views/components/injected/TableRow/TableRow.module.scss
+++ b/src/views/components/injected/TableRow/TableRow.module.scss
@@ -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;
+ }
}
diff --git a/src/views/components/injected/TableRow/TableRow.tsx b/src/views/components/injected/TableRow/TableRow.tsx
index 11b87d6e..34f04ab6 100644
--- a/src/views/components/injected/TableRow/TableRow.tsx
+++ b/src/views/components/injected/TableRow/TableRow.tsx
@@ -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);
diff --git a/src/views/components/injected/TableSubheading/TableSubheading.module.scss b/src/views/components/injected/TableSubheading/TableSubheading.module.scss
new file mode 100644
index 00000000..691f8dcc
--- /dev/null
+++ b/src/views/components/injected/TableSubheading/TableSubheading.module.scss
@@ -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;
+ }
+}
diff --git a/src/views/components/injected/TableSubheading/TableSubheading.tsx b/src/views/components/injected/TableSubheading/TableSubheading.tsx
new file mode 100644
index 00000000..e7a11bc1
--- /dev/null
+++ b/src/views/components/injected/TableSubheading/TableSubheading.tsx
@@ -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;
+}
diff --git a/src/views/hooks/useInfiniteScroll.ts b/src/views/hooks/useInfiniteScroll.ts
index b54d9ccd..d143dee3 100644
--- a/src/views/hooks/useInfiniteScroll.ts
+++ b/src/views/hooks/useInfiniteScroll.ts
@@ -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,
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);
}
diff --git a/src/views/hooks/useSchedules.ts b/src/views/hooks/useSchedules.ts
index 9f3e4839..89f6fde0 100644
--- a/src/views/hooks/useSchedules.ts
+++ b/src/views/hooks/useSchedules.ts
@@ -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([]);
diff --git a/src/views/hooks/useTabMessage.ts b/src/views/hooks/useTabMessage.ts
index 43ef300f..cca95893 100644
--- a/src/views/hooks/useTabMessage.ts
+++ b/src/views/hooks/useTabMessage.ts
@@ -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();
\ No newline at end of file
+export const useTabMessage = createUseMessage();
diff --git a/src/views/hooks/useVersion.ts b/src/views/hooks/useVersion.ts
index 7b24654e..eb5a1292 100644
--- a/src/views/hooks/useVersion.ts
+++ b/src/views/hooks/useVersion.ts
@@ -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('');
@@ -17,4 +16,3 @@ export default function useVersion(): string {
return version;
}
-
\ No newline at end of file
diff --git a/src/views/index.tsx b/src/views/index.tsx
index 8bd664e3..549cb930 100644
--- a/src/views/index.tsx
+++ b/src/views/index.tsx
@@ -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);
diff --git a/src/views/lib/CourseCatalogScraper.ts b/src/views/lib/CourseCatalogScraper.ts
index 1e4987be..7409db96 100644
--- a/src/views/lib/CourseCatalogScraper.ts
+++ b/src/views/lib/CourseCatalogScraper.ts
@@ -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)
diff --git a/src/views/lib/database/initializeDB.ts b/src/views/lib/database/initializeDB.ts
index c6e094cd..c6e2388c 100644
--- a/src/views/lib/database/initializeDB.ts
+++ b/src/views/lib/database/initializeDB.ts
@@ -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
diff --git a/src/views/lib/database/queryDistribution.ts b/src/views/lib/database/queryDistribution.ts
index 48eb10d3..4be79a93 100644
--- a/src/views/lib/database/queryDistribution.ts
+++ b/src/views/lib/database/queryDistribution.ts
@@ -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';
/**
diff --git a/src/views/lib/loadNextCourseCatalogPage.ts b/src/views/lib/loadNextCourseCatalogPage.ts
index 2faf5bab..ca91e4fa 100644
--- a/src/views/lib/loadNextCourseCatalogPage.ts
+++ b/src/views/lib/loadNextCourseCatalogPage.ts
@@ -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);
diff --git a/src/views/styles/base.module.scss b/src/views/styles/base.module.scss
index b9e73c7d..eadaa4b0 100644
--- a/src/views/styles/base.module.scss
+++ b/src/views/styles/base.module.scss
@@ -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';
diff --git a/src/views/styles/fonts.module.scss b/src/views/styles/fonts.module.scss
index 857c3fac..5e609a2f 100644
--- a/src/views/styles/fonts.module.scss
+++ b/src/views/styles/fonts.module.scss
@@ -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;
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 00000000..11f02fe2
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/tsconfig.json b/tsconfig.json
index cef01079..28d7bcc8 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,55 +1,57 @@
{
"compilerOptions": {
- "target": "es2021",
- "outDir": "./",
+ "baseUrl": ".",
+ "rootDir": ".",
+ "target": "esnext",
+ "module": "esnext",
"noEmit": true,
+ "jsx": "react",
"typeRoots": [
"./node_modules/@types",
"./@types/"
],
- "rootDir": "./",
- "allowSyntheticDefaultImports": true,
- "esModuleInterop": true,
- "module": "ES2022",
- "moduleResolution": "node",
- "allowJs": true,
- "sourceMap": true,
- "resolveJsonModule": true,
- "incremental": true,
- "lib": [
- "DOM",
- "es2021"
- ],
- "jsx": "react",
"skipLibCheck": true,
- "strictBindCallApply": true,
- "pretty": true,
- "noImplicitReturns": false,
- "baseUrl": "./",
+ "esModuleInterop": true,
+ "resolveJsonModule": true,
+ "moduleResolution": "node",
+ "types": [
+ "vite/client",
+ "node"
+ ],
+ "noFallthroughCasesInSwitch": true,
+ "allowSyntheticDefaultImports": true,
"paths": {
"src/*": [
- "./src/*"
+ "src/*"
],
- "webpack/*": [
- "./webpack/*"
+ "@assets/*": [
+ "src/assets/*"
],
- },
- "noImplicitThis": true,
- "noImplicitAny": false,
- "strictNullChecks": true,
- "forceConsistentCasingInFileNames": true,
+ "@pages/*": [
+ "src/pages/*"
+ ],
+ "@public/*": [
+ "public/*"
+ ],
+ "@shared/*": [
+ "src/shared/*"
+ ],
+ "@background/*": [
+ "src/pages/background/*"
+ ],
+ "@views/*": [
+ "src/views/*"
+ ],
+ }
},
"include": [
- "src/**/*",
- "webpack/**/*",
- "@types/**/*",
- "./package.json",
- "./release.config.js",
- "webpack/plugins/custom/.ts"
- ],
- "exclude": [
- "node_modules",
- "**/.*/",
- "build",
- ],
-}
\ No newline at end of file
+ "src",
+ "utils",
+ "vite.config.ts",
+ "node_modules/@types",
+ "src/manifest.ts",
+ "package.json",
+ ".eslintrc",
+ "postcss.config.cjs"
+ ]
+}
diff --git a/utils/log.ts b/utils/log.ts
new file mode 100644
index 00000000..4e648803
--- /dev/null
+++ b/utils/log.ts
@@ -0,0 +1,48 @@
+type ColorType = 'success' | 'info' | 'error' | 'warning' | keyof typeof COLORS;
+
+export default function colorLog(message: string, type?: ColorType) {
+ let color: string = type || COLORS.FgBlack;
+
+ switch (type) {
+ case 'success':
+ color = COLORS.FgGreen;
+ break;
+ case 'info':
+ color = COLORS.FgBlue;
+ break;
+ case 'error':
+ color = COLORS.FgRed;
+ break;
+ case 'warning':
+ color = COLORS.FgYellow;
+ break;
+ }
+
+ console.log(color, message);
+}
+
+const COLORS = {
+ Reset: '\x1b[0m',
+ Bright: '\x1b[1m',
+ Dim: '\x1b[2m',
+ Underscore: '\x1b[4m',
+ Blink: '\x1b[5m',
+ Reverse: '\x1b[7m',
+ Hidden: '\x1b[8m',
+ FgBlack: '\x1b[30m',
+ FgRed: '\x1b[31m',
+ FgGreen: '\x1b[32m',
+ FgYellow: '\x1b[33m',
+ FgBlue: '\x1b[34m',
+ FgMagenta: '\x1b[35m',
+ FgCyan: '\x1b[36m',
+ FgWhite: '\x1b[37m',
+ BgBlack: '\x1b[40m',
+ BgRed: '\x1b[41m',
+ BgGreen: '\x1b[42m',
+ BgYellow: '\x1b[43m',
+ BgBlue: '\x1b[44m',
+ BgMagenta: '\x1b[45m',
+ BgCyan: '\x1b[46m',
+ BgWhite: '\x1b[47m',
+} as const;
diff --git a/utils/plugins/make-manifest.ts b/utils/plugins/make-manifest.ts
new file mode 100644
index 00000000..92199194
--- /dev/null
+++ b/utils/plugins/make-manifest.ts
@@ -0,0 +1,26 @@
+import * as fs from 'fs';
+import * as path from 'path';
+import { PluginOption } from 'vite';
+import manifest from '../../src/manifest';
+import colorLog from '../log';
+
+const { resolve } = path;
+
+const outDir = resolve(__dirname, '..', '..', 'public');
+
+export default function makeManifest(): PluginOption {
+ return {
+ name: 'make-manifest',
+ buildEnd() {
+ if (!fs.existsSync(outDir)) {
+ fs.mkdirSync(outDir);
+ }
+
+ const manifestPath = resolve(outDir, 'manifest.json');
+
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
+
+ colorLog(`Manifest file copy complete: ${manifestPath}`, 'success');
+ },
+ };
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 00000000..d82e3f2d
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,148 @@
+import { crx } from '@crxjs/vite-plugin';
+import react from '@vitejs/plugin-react-swc';
+import { resolve } from 'path';
+import { Plugin, ResolvedConfig, ViteDevServer, defineConfig } from 'vite';
+import inspect from 'vite-plugin-inspect';
+import manifest from './src/manifest';
+
+const root = resolve(__dirname, 'src');
+const pagesDir = resolve(root, 'pages');
+const assetsDir = resolve(root, 'assets');
+const outDir = resolve(__dirname, 'dist');
+const publicDir = resolve(__dirname, 'public');
+
+const isDev = process.env.NODE_ENV === 'development';
+
+export const preambleCode = `
+import RefreshRuntime from "__BASE__@react-refresh"
+RefreshRuntime.injectIntoGlobalHook(window)
+window.$RefreshReg$ = () => {}
+window.$RefreshSig$ = () => (type) => type
+window.__vite_plugin_react_preamble_installed__ = true
+`;
+
+const renameFile = (source: string, destination: string): Plugin => {
+ if (typeof source !== 'string' || typeof destination !== 'string') {
+ return;
+ }
+
+ return {
+ name: 'crx:rename-file',
+ apply: 'build',
+ enforce: 'post',
+ generateBundle(options, bundle) {
+ if (!bundle[source]) return;
+ bundle[source].fileName = destination;
+ },
+ };
+};
+
+let config: ResolvedConfig;
+let server: ViteDevServer;
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [
+ react(),
+ // crx({ manifest, contentScripts: { preambleCode } }),
+ crx({ manifest }),
+ inspect(),
+ {
+ name: 'public-transform',
+ apply: 'serve',
+ transform(code, id) {
+ if (id.endsWith('.tsx') || id.endsWith('.ts') || id.endsWith('?url')) {
+ return code.replace(
+ /(['"])(\/public\/.*?)(['"])/g,
+ (_, quote1, path, quote2) => `chrome.runtime.getURL(${quote1}${path}${quote2})`
+ );
+ }
+ },
+ },
+ {
+ name: 'public-transform',
+ apply: 'build',
+ transform(code, id) {
+ if (id.endsWith('.tsx') || id.endsWith('.ts') || id.endsWith('?url')) {
+ return code.replace(
+ /(['"])(__VITE_ASSET__.*?__)(['"])/g,
+ (_, quote1, path, quote2) => `chrome.runtime.getURL(${quote1}${path}${quote2})`
+ );
+ }
+ },
+ },
+ {
+ name: 'public-css-dev-transform',
+ apply: 'serve',
+ enforce: 'post',
+ transform(code, id) {
+ if (process.env.NODE_ENV === 'development' && (id.endsWith('.css') || id.endsWith('.scss'))) {
+ return code.replace(
+ /url\((.*?)\)/g,
+ (_, path) =>
+ `url(\\"" + chrome.runtime.getURL(${path
+ .replaceAll(`\\"`, `"`)
+ .replace(/public\//, '')}) + "\\")`
+ );
+ }
+ },
+ },
+ {
+ name: 'public-transform2',
+ // enforce: 'post',
+ transform(code, id) {
+ if (id.replace(/\?used$/, '').endsWith('.scss')) {
+ const transformedCode = code.replace(
+ /(__VITE_ASSET__.*?__)/g,
+ (_, path) => `chrome-extension://__MSG_@@extension_id__${path}`
+ );
+ return transformedCode;
+ }
+ return code;
+ },
+ },
+ // renameFile('src/pages/debug/index.html', 'debug.html'),
+ renameFile('src/pages/calendar/index.html', 'calendar.html'),
+ ],
+ resolve: {
+ alias: {
+ src: root,
+ '@assets': assetsDir,
+ '@pages': pagesDir,
+ '@public': publicDir,
+ '@shared': resolve(root, 'shared'),
+ '@background': resolve(pagesDir, 'background'),
+ '@views': resolve(root, 'views'),
+ },
+ },
+ server: {
+ strictPort: true,
+ port: 5173,
+ hmr: {
+ clientPort: 5173,
+ },
+ proxy: {
+ '/debug.html': {
+ target: 'http://localhost:5173',
+ rewrite: path => path.replace('debug', 'src/pages/debug/index'),
+ },
+ '/calendar.html': {
+ target: 'http://localhost:5173',
+ rewrite: path => path.replace('calendar', 'src/pages/calendar/index'),
+ },
+ },
+ },
+ build: {
+ rollupOptions: {
+ input: {
+ debug: 'src/pages/debug/index.html',
+ calendar: 'src/pages/calendar/index.html',
+ },
+ // output: {
+ // entryFileNames: `[name].js`, // otherwise it will add the hash
+ // chunkFileNames: `[name].js`,
+ // },
+ // external: ['/@react-refresh'],
+ },
+ },
+});
diff --git a/webpack/development.ts b/webpack/development.ts
deleted file mode 100644
index 4a48cad9..00000000
--- a/webpack/development.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import webpack from 'webpack';
-import WebpackDevServer from 'webpack-dev-server';
-import path from 'path';
-import { Server } from 'socket.io';
-import config from './webpack.config';
-import { version } from '../package.json';
-import { getManifest } from './manifest.config';
-import { initializeHotReloading } from './plugins/custom/hotReloadServer';
-
-const HOT_RELOAD_PORT = 9090;
-const MODE: Environment = 'development';
-
-const manifest = getManifest(MODE, version);
-const compiler = webpack(config(MODE, manifest));
-
-initializeHotReloading(HOT_RELOAD_PORT, compiler);
-
-const server = new WebpackDevServer(
- {
- https: false,
- hot: false,
- client: false,
- host: 'localhost',
- static: {
- directory: path.resolve('build'),
- },
- devMiddleware: {
- writeToDisk: true,
- },
- headers: {
- 'Access-Control-Allow-Origin': '*',
- },
- allowedHosts: 'all',
- watchFiles: {
- paths: ['src/**/*.{ts,tsx,js,jsx,html,css,scss,json,md,png,jpg,jpeg,gif,svg}', 'public/**/*'],
- },
- },
- compiler
-);
-
-await server.start();
diff --git a/webpack/loaders/index.ts b/webpack/loaders/index.ts
deleted file mode 100644
index e3fde9a6..00000000
--- a/webpack/loaders/index.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { RuleSetRule } from 'webpack';
-import * as styleLoaders from './styleLoaders';
-
-/** using esbuild-loader for ⚡ fast builds */
-const typescriptLoader: RuleSetRule = {
- test: /\.tsx?$/,
- loader: 'esbuild-loader',
- options: {
- loader: 'tsx',
- target: 'es2021',
- },
-};
-
-/** convert svgs to react components automatically */
-const svgLoader: RuleSetRule = {
- test: /\.svg$/,
- issuer: /\.tsx?$/,
- loader: '@svgr/webpack',
-};
-
-/** these are files that we want to be able to be loaded into the extension folder instead of imported */
-const urlLoader: RuleSetRule = {
- test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.mp3$/],
- loader: 'url-loader',
- options: {
- limit: '10000',
- name: 'static/media/[name].[ext]',
- },
-};
-
-/** these loaders will allow us to use raw css imports, css modules, raw sass imports, and sass modules */
-const { cssLoader, cssModuleLoader, sassLoader, sassModuleLoader } = styleLoaders;
-
-// this is the default file loader, it will be used for any file that doesn't match the other loaders
-const fileLoader: RuleSetRule = {
- loader: 'file-loader',
- exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/, /\.mp3$/],
- options: {
- name: 'static/media/[name].[ext]',
- },
-};
-
-/** the assembled list of loaders in the order that we want webpack to attempt to use them on modules */
-const loaders: RuleSetRule[] = [
- typescriptLoader,
- {
- // IMPORTANT: if you are adding a new loader, it must come before the file loader
- oneOf: [svgLoader, urlLoader, cssLoader, cssModuleLoader, sassLoader, sassModuleLoader, fileLoader],
- },
-];
-
-export default loaders;
diff --git a/webpack/loaders/styleLoaders.ts b/webpack/loaders/styleLoaders.ts
deleted file mode 100644
index c32569cb..00000000
--- a/webpack/loaders/styleLoaders.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { RuleSetRule, RuleSetUseItem } from 'webpack';
-import MiniCssExtractPlugin from 'mini-css-extract-plugin';
-import getCSSModuleLocalIdent from 'react-dev-utils/getCSSModuleLocalIdent';
-
-const cssRegex = /\.css$/;
-const cssModuleRegex = /\.module\.css$/;
-
-const sassRegex = /\.(scss|sass)$/;
-const sassModuleRegex = /\.module\.(scss|sass)$/;
-
-function buildStyleLoaders(cssLoaderOptions: Record): RuleSetUseItem[] {
- const loaders = [
- {
- loader: MiniCssExtractPlugin.loader,
- },
- {
- loader: 'css-loader',
- options: { ...cssLoaderOptions, sourceMap: false },
- },
- ];
- return loaders;
-}
-
-export const cssLoader: RuleSetRule = {
- test: cssRegex,
- exclude: cssModuleRegex,
- sideEffects: true,
- use: [
- ...buildStyleLoaders({
- importLoaders: 1,
- esModule: false,
- }),
- ],
-};
-
-export const cssModuleLoader: RuleSetRule = {
- test: cssModuleRegex,
- use: [
- ...buildStyleLoaders({
- importLoaders: 1,
- modules: {
- getLocalIdent: getCSSModuleLocalIdent,
- },
- }),
- ],
-};
-
-export const sassLoader: RuleSetRule = {
- test: sassRegex,
- exclude: sassModuleRegex,
- sideEffects: true,
- use: [
- ...buildStyleLoaders({
- importLoaders: 2,
- }),
- {
- loader: 'sass-loader',
- },
- ],
-};
-
-export const sassModuleLoader: RuleSetRule = {
- test: sassModuleRegex,
- use: [
- ...buildStyleLoaders({
- importLoaders: 2,
- modules: {
- getLocalIdent: getCSSModuleLocalIdent,
- },
- }),
- {
- loader: 'sass-loader',
- },
- ],
-};
diff --git a/webpack/manifest.config.ts b/webpack/manifest.config.ts
deleted file mode 100644
index 76521657..00000000
--- a/webpack/manifest.config.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-const NAME = 'UT Registration Plus';
-const SHORT_NAME = 'ut-registration-plus';
-const DESCRIPTION = 'Improves the course registration process at the University of Texas at Austin!';
-
-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/*',
-];
-
-/**
- * Creates a chrome extension manifest from the given version, mode, and
- * @param mode the build mode (development or production)
- * @param version a chrome extension version (not a semantic version)
- * @returns a chrome extension manifest
- */
-export function getManifest(mode: Environment, version: string): chrome.runtime.ManifestV3 {
- let name = mode === 'development' ? `${NAME} (dev)` : NAME;
-
- if (mode === 'development') {
- HOST_PERMISSIONS.push('http://localhost:9090/*');
- }
-
- const manifest = {
- name,
- short_name: SHORT_NAME,
- description: DESCRIPTION,
- version,
- manifest_version: 3,
- // hardcode the key for development builds
- key: process.env.MANIFEST_KEY,
- host_permissions: HOST_PERMISSIONS,
- permissions: ['storage', 'unlimitedStorage', 'background'],
- background: {
- service_worker: 'static/js/background.js',
- },
- content_scripts: [
- {
- matches: HOST_PERMISSIONS,
- css: ['/static/css/content.css'],
- js: ['/static/js/content.js'],
- },
- ],
- web_accessible_resources: [
- {
- resources: ['static/media/*', '*'],
- matches: [''],
- },
- ],
- icons: {
- 16: `icons/icon_${mode}_16.png`,
- 48: `icons/icon_${mode}_48.png`,
- 128: `icons/icon_${mode}_128.png`,
- },
- action: {
- default_popup: 'popup.html',
- },
- } satisfies chrome.runtime.ManifestV3;
- return manifest;
-}
diff --git a/webpack/plugins/buildProcessPlugins.ts b/webpack/plugins/buildProcessPlugins.ts
deleted file mode 100644
index f5bdf132..00000000
--- a/webpack/plugins/buildProcessPlugins.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-import path from 'path';
-import dotenv from 'dotenv';
-import webpack, { WebpackPluginInstance } from 'webpack';
-import { EntryId } from 'webpack/webpack.config';
-import CreateFileWebpack from 'create-file-webpack';
-import CopyWebpackPlugin from 'copy-webpack-plugin';
-import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
-import WebpackBuildNotifierPlugin from 'webpack-build-notifier';
-import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
-import HTMLWebpackPlugin from 'html-webpack-plugin';
-import MiniCssExtractPlugin from 'mini-css-extract-plugin';
-import { CleanWebpackPlugin } from 'clean-webpack-plugin';
-import TypeErrorNotifierPlugin from './custom/TypeErrorNotifierPlugin';
-
-/**
- * Gets the plugins that are used in the build process
- * @param mode the environment that the build is running in
- * @param htmlEntries the entry points that need an html file
- * @param manifest the manifest.json file
- * @returns an array of webpack plugins
- */
-export function getBuildPlugins(mode: Environment, htmlEntries: EntryId[], manifest: chrome.runtime.ManifestV3) {
- let plugins: WebpackPluginInstance[] = [];
-
- // show the progress of the build
- plugins.push(new webpack.ProgressPlugin());
-
- // make sure that the paths are case sensitive
- plugins.push(new CaseSensitivePathsPlugin());
-
- plugins.push(new CleanWebpackPlugin());
-
- // specify how the outputed css files should be named
- plugins.push(
- new MiniCssExtractPlugin({
- filename: 'static/css/[name].css',
- chunkFilename: 'static/css/[name].chunk.css',
- })
- );
-
- // create an html file for each entry point that needs one
- for (const entryId of htmlEntries) {
- // if (!entries[entryId]) return;
- plugins.push(
- new HTMLWebpackPlugin({
- hash: false,
- filename: `${entryId}.html`,
- chunks: [entryId],
- title: `${entryId} `,
- template: path.resolve('webpack', 'plugins', 'template.html'),
- })
- );
- }
-
- // write the manifest.json file to the build directory
- plugins.push(
- new CreateFileWebpack({
- path: path.resolve('build'),
- fileName: 'manifest.json',
- content: JSON.stringify(manifest, null, 2),
- })
- );
-
- // copy the public directory to the build directory, but only copy the icons for the current mode
- plugins.push(
- new CopyWebpackPlugin({
- patterns: [
- {
- from: path.resolve('public'),
- filter: path => (path.includes('icons/icon') ? path.includes(mode) : true),
- },
- ],
- })
- );
-
- // run the typescript checker in a separate process
- plugins.push(
- new ForkTsCheckerWebpackPlugin({
- async: false,
- })
- );
-
- // notify the developer of build events when in development mode
- if (mode === 'development') {
- plugins.push(
- new WebpackBuildNotifierPlugin({
- title: `${manifest.short_name} v${manifest.version} ${mode}`,
- logo: path.resolve('public', 'icons', 'icon_production_128.png'),
- failureSound: 'Ping',
- successSound: false,
- showDuration: true,
- suppressWarning: true,
- })
- );
- }
-
- // notify the developer of type errors
- plugins.push(new TypeErrorNotifierPlugin());
-
- // define the environment variables that are available within the extension code
- plugins.push(
- new webpack.DefinePlugin({
- 'process.env': JSON.stringify({
- SEMANTIC_VERSION: process.env.SEMANTIC_VERSION,
- NODE_ENV: mode,
- ...dotenv.config({ path: `.env.${mode}` }).parsed,
- } satisfies typeof process.env),
- })
- );
-
- // provide some global nodejs variables so that nodejs libraries can be used
- plugins.push(
- new webpack.ProvidePlugin({
- Buffer: ['buffer', 'Buffer'],
- process: 'process/browser',
- })
- );
-
- return plugins;
-}
diff --git a/webpack/plugins/custom/TypeErrorNotifierPlugin.ts b/webpack/plugins/custom/TypeErrorNotifierPlugin.ts
deleted file mode 100644
index f8398e9c..00000000
--- a/webpack/plugins/custom/TypeErrorNotifierPlugin.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import { Compiler } from 'webpack';
-import path from 'path';
-import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
-import WebpackBuildNotifierPlugin from 'webpack-build-notifier';
-import { Issue, IssueLocation } from 'fork-ts-checker-webpack-plugin/lib/issue';
-
-interface Resource {
- path: string;
- location: IssueLocation;
-}
-
-/**
- * This plugin hooks into the fork-ts-checker-webpack-plugin and
- * notifies the developer of type errors using the webpack-build-notifier plugin.
- */
-export default class TypeErrorNotifierPlugin {
- apply(compiler: Compiler) {
- // hook into the fork-ts-checker-webpack-plugin
- const hooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(compiler);
- hooks.issues.tap('MyPlugin', issues => {
- const errors = issues.filter(issue => issue.severity === 'error');
- if (!errors?.[0]?.message) {
- return errors;
- }
-
- let error = errors[0];
- let resource = getErrorResource(error);
-
- try {
- notifyTypeError(resource, error.message, errors);
- } catch (e) {
- console.error(e);
- }
- return errors;
- });
- }
-}
-
-function notifyTypeError(resource: Resource, message: string, errors: Issue[]) {
- const { line, column } = resource.location.start;
-
- const buildNotifier = new WebpackBuildNotifierPlugin({
- logo: path.resolve('public', 'icons', 'icon_production_128.png'),
- compilationSound: 'Pop',
- failureSound: 'Sosumi',
- title: `TS: ${errors.length} errors`,
- notifyOptions: {
- open: `vscode://file/${resource.path}:${line}:${column}`,
- },
- });
-
- const fakeInput = {
- hasErrors: () => true,
- compilation: {
- children: null,
- errors: [
- {
- message,
- module: {
- resource: resource.path,
- },
- },
- ],
- },
- };
- // @ts-ignore - private method
- buildNotifier.onCompilationDone(fakeInput);
-}
-
-function getErrorResource(error: Issue): Resource {
- return {
- path: error.file ?? '',
- location: error.location ?? {
- end: {
- column: 0,
- line: 0,
- },
- start: {
- column: 0,
- line: 0,
- },
- },
- };
-}
diff --git a/webpack/plugins/custom/hotReloadServer.ts b/webpack/plugins/custom/hotReloadServer.ts
deleted file mode 100644
index a87fe6d4..00000000
--- a/webpack/plugins/custom/hotReloadServer.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Server } from 'socket.io';
-import { Compiler } from 'webpack';
-import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
-
-// Name of the plugin
-const PLUGIN_NAME = 'HotReloadServer';
-
-// How long to wait before reloading the browser after a successful build
-const RELOAD_LOCKOUT = 2000;
-
-// we want to cache all the "reload requests" here so we don't have to keep re-compiling the app while typing
-const reloads: NodeJS.Timeout[] = [];
-
-let io: Server;
-
-export function initializeHotReloading(port: number, compiler: Compiler) {
- io = new Server(port);
-
- const hooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(compiler);
- hooks.issues.tap(PLUGIN_NAME, (issues, compilation) => {
- const typeErrors = issues.filter(issue => issue.severity === 'error');
- const buildErrors = compilation?.errors ?? [];
- // if no errors (thus successful build), lets queue up a reload for the browser
- if (typeErrors.length === 0 && buildErrors.length === 0) {
- reloads.push(setTimeout(() => io.emit('reload'), RELOAD_LOCKOUT));
- }
- return typeErrors;
- });
-
- // if a recompile is triggered, we want to clear out all the queue'd reloads
- // (so we don't spam-reload the extension while we are still typing
- compiler.hooks.compile.tap(PLUGIN_NAME, () => {
- reloads.forEach(reload => clearTimeout(reload));
- reloads.length = 0;
- });
-}
diff --git a/webpack/plugins/moduleResolutionPlugins.ts b/webpack/plugins/moduleResolutionPlugins.ts
deleted file mode 100644
index 72378881..00000000
--- a/webpack/plugins/moduleResolutionPlugins.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
-import path from 'path';
-import ModuleScopePlugin from 'react-dev-utils/ModuleScopePlugin';
-
-export const moduleResolutionPlugins = [
- // this will make sure that webpack uses the tsconfig path aliases
- new TsconfigPathsPlugin({
- configFile: path.resolve('src', 'tsconfig.json'),
- }),
- // this will make sure that we don't import anything outside of the src directory from the src directory
- new ModuleScopePlugin(path.resolve('src'), path.resolve('package.json')),
-];
diff --git a/webpack/plugins/template.html b/webpack/plugins/template.html
deleted file mode 100644
index 9a94a611..00000000
--- a/webpack/plugins/template.html
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
- <%= htmlWebpackPlugin.options.title %>
-
-
-
-
-
-
-
diff --git a/webpack/production.ts b/webpack/production.ts
deleted file mode 100644
index 3893f593..00000000
--- a/webpack/production.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import webpack from 'webpack';
-import formatWebpackMessages from 'react-dev-utils/formatWebpackMessages';
-import config from './webpack.config';
-import { info, success } from './utils/chalk';
-import { getManifest } from './manifest.config';
-import { version } from '../package.json';
-import { convertSemver } from './utils/convertSemver';
-import printError from './utils/printError';
-import { zipProductionBuild } from './utils/zipProductionBuild';
-
-const MODE: Environment = 'production';
-
-// generate the manifest.json file
-const semanticVersion = process.env.SEMANTIC_VERSION || version;
-const manifestVersion = convertSemver(semanticVersion);
-const manifest = getManifest(MODE, manifestVersion);
-
-console.log(info(`${manifest.short_name} v${manifest.version} ${MODE} build starting...`));
-
-// kick off the webpack build
-webpack(config(MODE, manifest), async error => {
- if (!error) {
- await onBuildSuccess();
- process.exit(0);
- }
- await onBuildFailure(error);
- process.exit(1);
-});
-
-async function onBuildSuccess(): Promise {
- // zip the output directory and put it in the artifacts directory
- const fileName = `${manifest.short_name} v${manifestVersion}`;
- await zipProductionBuild(fileName);
- console.log(success(`${fileName} built and zipped into build/artifacts/${fileName}.zip!`));
-}
-
-function onBuildFailure(error: Error): void {
- if (!error.message) {
- return printError(error);
- }
- const messages = formatWebpackMessages({ errors: [error.message], warnings: [] });
- if (messages.errors.length > 1) {
- messages.errors.length = 1;
- }
- return printError(new Error(messages.errors.join('\n\n')));
-}
diff --git a/webpack/release.ts b/webpack/release.ts
deleted file mode 100644
index 92610eab..00000000
--- a/webpack/release.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { simpleGit } from 'simple-git';
-import prompts from 'prompts';
-import { error, info } from './utils/chalk';
-import { getSourceRef } from './utils/git/getSourceRef';
-
-const git = simpleGit();
-const status = await git.status();
-
-if (status.files.length) {
- console.log(error('Working directory is not clean, please commit or stash changes before releasing.'));
- process.exit(1);
-}
-
-const { destinationBranch } = await prompts({
- type: 'select',
- name: 'destinationBranch',
- message: 'What kind of release do you want to create?',
- choices: ['preview', 'production'].map(releaseType => ({
- title: releaseType,
- value: releaseType,
- })),
-});
-const sourceRef = await getSourceRef(destinationBranch);
-
-const { confirm } = await prompts({
- type: 'confirm',
- name: 'confirm',
- message: `Are you sure you want to create a ${destinationBranch} release from ${sourceRef}?`,
-});
-
-if (!confirm) {
- console.log(error('Aborting release.'));
- process.exit(1);
-}
-
-// we fetch the latest changes from the remote
-await git.fetch();
-
-// we checkout the source ref, pull the latest changes and then checkout the destination branch
-console.info(`Checking out and updating ${sourceRef}...`);
-await git.checkout(sourceRef);
-await git.pull('origin', sourceRef);
-console.info(`Checking out and updating ${destinationBranch}...`);
-await git.checkout(destinationBranch);
-await git.pull('origin', destinationBranch);
-
-// we trigger the release github action by merging the source ref into the destination branch
-console.info(`Merging ${sourceRef} into ${destinationBranch}...`);
-await git.merge([sourceRef, '--ff-only']);
-await git.push('origin', destinationBranch);
-
-console.info(info(`Release to ${destinationBranch} created! Check github for status`));
diff --git a/webpack/tsconfig.json b/webpack/tsconfig.json
deleted file mode 100644
index 9008410c..00000000
--- a/webpack/tsconfig.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "extends": "../tsconfig.json",
- "compilerOptions": {
- "module": "esnext",
- "esModuleInterop": true,
- "composite": true,
- "lib": [
- "es2021"
- ],
- "types": [
- "chrome",
- "node"
- ],
- },
-}
\ No newline at end of file
diff --git a/webpack/utils/chalk.ts b/webpack/utils/chalk.ts
deleted file mode 100644
index d2dd66ab..00000000
--- a/webpack/utils/chalk.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import chalk from 'chalk';
-
-export const error = chalk.bold.red;
-export const { bold } = chalk;
-export const info = chalk.bgHex('#673AB7').rgb(255, 255, 255);
-export const warning = chalk.bgHex('#FF9800').rgb(255, 255, 255);
-export const success = chalk.bgHex('#4CAF50').rgb(255, 255, 255);
diff --git a/webpack/utils/convertSemver.ts b/webpack/utils/convertSemver.ts
deleted file mode 100644
index a1ef5890..00000000
--- a/webpack/utils/convertSemver.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { parse } from 'semver';
-
-/**
- * Converts npm semver-style strings (including pre-releases) to a release version compatible
- * with the extension stores.
- *
- * @example
- * semverVersionTo('1.0.0-beta.1`) returns 1.0.0.100
- */
-export function convertSemver(version: string): string {
- const semver = parse(version);
- if (!semver) {
- throw new Error(`Couldn't parse ${version}!`);
- }
-
- const { major, minor, patch, prerelease } = semver;
- let manifestVersion = `${major}.${minor}.${patch}`;
- if (prerelease.length) {
- manifestVersion += `.${prerelease[1]}00`;
- }
- return manifestVersion;
-}
diff --git a/webpack/utils/git/getSourceRef.ts b/webpack/utils/git/getSourceRef.ts
deleted file mode 100644
index 7ed0529d..00000000
--- a/webpack/utils/git/getSourceRef.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import prompts from 'prompts';
-import { simpleGit } from 'simple-git';
-import { error } from '../chalk';
-
-const git = simpleGit();
-
-export async function getSourceRef(destinationBranch: 'preview' | 'production'): Promise {
- if (destinationBranch === 'preview') {
- return 'main';
- }
-
- const tags = await git.tags(['--sort=-committerdate']);
- const alphaTags = tags.all.filter((tag: string) => tag.includes('alpha'));
-
- if (!alphaTags.length) {
- console.log(error('No preview builds found, please create one before releasing a production build.'));
- process.exit(1);
- }
-
- const { sourceTag } = await prompts({
- message: 'Which preview tag do you want to create a production build from?',
- type: 'select',
- name: 'sourceTag',
- choices: alphaTags.map(tag => ({ title: tag, value: tag })),
- });
-
- return sourceTag;
-}
diff --git a/webpack/utils/printError.ts b/webpack/utils/printError.ts
deleted file mode 100644
index b26eb4c4..00000000
--- a/webpack/utils/printError.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import printBuildError from 'react-dev-utils/printBuildError';
-import { error } from './chalk';
-/**
- * Print Errors that we got back from webpack
- * @param e the error provided by webpacxk
- */
-export default function printError(e: Error) {
- console.log('printBuildError -> e', e);
- if (process.env.TSC_COMPILE_ON_ERROR === 'true') {
- printBuildError(e);
- } else {
- // console.log(error('Failed to compile.\n'));
- printBuildError(e);
- process.exit(1);
- }
-}
diff --git a/webpack/utils/zipProductionBuild.ts b/webpack/utils/zipProductionBuild.ts
deleted file mode 100644
index ed95810f..00000000
--- a/webpack/utils/zipProductionBuild.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import fs, { mkdirSync } from 'fs';
-import archiver from 'archiver';
-import chalk from 'chalk';
-import path from 'path';
-
-/**
- * Creates a zip file from the given source directory
- * @param fileName the name of the zip file to create
- * @param outDir the directory to zip up
- * @param globOptions the glob options to use when finding files to zip
- * @returns
- */
-export async function zipProductionBuild(fileName: string) {
- const outDirectory = path.resolve('build');
- const artifactsDir = path.join(outDirectory, 'artifacts');
-
- mkdirSync(artifactsDir, { recursive: true });
-
- const output = fs.createWriteStream(`${artifactsDir}/${fileName}.zip`);
- const archive = archiver('zip', {
- zlib: { level: 9 },
- });
- archive.pipe(output);
-
- const promise = new Promise((resolve, reject) => {
- output.on('close', resolve);
- archive.on('warning', warn => console.log(chalk.red(warn)));
- archive.on('error', err => reject(err));
- });
-
- archive.glob('**/*', { cwd: outDirectory, ignore: ['*.zip', 'artifacts'] });
- // eslint-disable-next-line no-void
- void archive.finalize(); // The promise returned is what's `await-ed`, not the call to `finalize()`
- return promise;
-}
diff --git a/webpack/webpack.config.ts b/webpack/webpack.config.ts
deleted file mode 100644
index 90cf5bed..00000000
--- a/webpack/webpack.config.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import { Configuration, EntryObject } from 'webpack';
-import path from 'path';
-import TerserPlugin from 'terser-webpack-plugin';
-import { moduleResolutionPlugins } from './plugins/moduleResolutionPlugins';
-import loaders from './loaders';
-import { getBuildPlugins } from './plugins/buildProcessPlugins';
-
-export interface Entries {
- content: string[];
- background: string[];
- popup: string[];
- my_calendar: string[];
- // only used in development
- debug?: string[];
-}
-
-export type EntryId = keyof Entries;
-
-/**
- * This function will generate the webpack configuration for the extension
- * @param mode the mode that webpack is running in (development or production)
- * * @param manifest the manifest.json object for the extension
- * @returns the webpack configuration
- */
-export default function config(mode: Environment, manifest: chrome.runtime.ManifestV3): Configuration {
- const outDirectory = path.resolve('build');
-
- // the entry points for the extension (the files that webpack will start bundling from)
- const entry: Entries = {
- content: [path.resolve('src', 'views')],
- popup: [path.resolve('src', 'views')],
- my_calendar: [path.resolve('src', 'views')],
- background: [path.resolve('src', 'background', 'background')],
- };
-
- // the entries that need an html file to be generated
- const htmlEntries: EntryId[] = ['popup', 'my_calendar'];
-
- if (mode === 'development') {
- // create an html file for the debug entry
- htmlEntries.push('debug');
- // TODO: add hot reloading script to the debug entry
- entry.debug = [path.resolve('src', 'debug')];
-
- // we need to import react-devtools before the react code in development so that it can hook into react
- entry.content = [path.resolve('src', 'debug', 'reactDevtools'), ...entry.content];
- entry.popup = [path.resolve('src', 'debug', 'reactDevtools'), ...entry.popup];
- entry.my_calendar = [path.resolve('src', 'debug', 'reactDevtools'), ...entry.my_calendar];
- }
-
- /** @see https://webpack.js.org/configuration for documentation */
- const config: Configuration = {
- mode,
- devtool: mode === 'development' ? 'cheap-module-source-map' : undefined,
- bail: true,
- cache: true,
- // entry and resolve is what webpack uses for figuring out where to start bundling and how to resolve modules
- entry: entry as unknown as EntryObject,
- resolve: {
- modules: ['node_modules'],
- extensions: ['.js', '.jsx', '.ts', '.tsx'],
- plugins: moduleResolutionPlugins,
- // this is to polyfill some node-only modules
- fallback: {
- crypto: 'crypto-browserify',
- stream: 'stream-browserify',
- buffer: 'buffer',
- fs: false,
- },
- },
- // this is where we define the loaders for different file types
- module: {
- strictExportPresence: true,
- rules: loaders,
- },
- output: {
- clean: true,
- path: outDirectory,
- pathinfo: mode === 'development',
- filename: 'static/js/[name].js',
- publicPath: '/',
- // this is for windows support (which uses backslashes in paths)
- devtoolModuleFilenameTemplate: info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'),
- // this is to make sure that the global chunk loading function name is unique
- chunkLoadingGlobal: `webpackJsonp${manifest.short_name}`,
- globalObject: 'this',
- },
- stats: {
- errorDetails: true,
- errorsCount: true,
- },
- // this is where we define the plugins that webpack will use
- plugins: getBuildPlugins(mode, htmlEntries, manifest),
- };
-
- if (mode === 'production') {
- config.optimization = {
- minimize: true,
- minimizer: [
- new TerserPlugin({
- extractComments: false,
- parallel: false,
- terserOptions: {
- compress: {
- ecma: 2020,
- drop_console: true,
- drop_debugger: true,
- comparisons: false,
- inline: 2,
- },
- keep_classnames: false,
- keep_fnames: false,
- output: {
- ecma: 2020,
- comments: false,
- ascii_only: true,
- },
- },
- }),
- ],
- };
- }
-
- return config;
-}