refactor: Replace Webpack with Vite (#53)
This commit is contained in:
@@ -6,6 +6,10 @@
|
|||||||
"node": true,
|
"node": true,
|
||||||
"webextensions": true
|
"webextensions": true
|
||||||
},
|
},
|
||||||
|
"ignorePatterns": [
|
||||||
|
"*.html",
|
||||||
|
"tsconfig.json"
|
||||||
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:react/recommended",
|
"plugin:react/recommended",
|
||||||
@@ -140,7 +144,7 @@
|
|||||||
"jsdoc/require-jsdoc": [
|
"jsdoc/require-jsdoc": [
|
||||||
"warn",
|
"warn",
|
||||||
{
|
{
|
||||||
"enableFixer": true,
|
"enableFixer": false,
|
||||||
"publicOnly": true,
|
"publicOnly": true,
|
||||||
"checkConstructors": false,
|
"checkConstructors": false,
|
||||||
"require": {
|
"require": {
|
||||||
@@ -205,4 +209,4 @@
|
|||||||
"WithStatement"
|
"WithStatement"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
README.md
23
README.md
@@ -1,19 +1,18 @@
|
|||||||
# UT Registration Plus
|
# UT Registration Plus
|
||||||
|
|
||||||
## Built Using:
|
## Built Using
|
||||||
|
|
||||||
- React 18
|
- React 18
|
||||||
- TypeScript
|
- TypeScript
|
||||||
- Webpack 5 (esbuild-loader)
|
- Vite 5
|
||||||
- ESLint
|
- ESLint
|
||||||
- Prettier
|
- Prettier
|
||||||
- Semantic-Release
|
- Semantic-Release
|
||||||
- Custom Messaging & Storage Wrappers
|
- Custom Messaging & Storage Wrappers
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
1. Clone this repo
|
1. Clone this repo
|
||||||
2. Run `npm install`
|
2. Run `pnpm install` to install and patch all the required dependencies
|
||||||
3. Run `npm start` to start the development server
|
3. Run `pnpm run dev` to start the development server
|
||||||
4. Run `npm run build` to build the extension for production
|
4. Run `pnpm build` to build the extension for production
|
||||||
5. Run `npm run release` to release a new version of the extension in CI (either preview or production)
|
|
||||||
|
|||||||
30935
package-lock.json
generated
30935
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
115
package.json
115
package.json
@@ -1,87 +1,76 @@
|
|||||||
{
|
{
|
||||||
"name": "ut-registration-plus",
|
"name": "ut-registration-plus",
|
||||||
"version": "0.0.0",
|
"displayName": "UT Registration Plus",
|
||||||
|
"version": "0.0.1",
|
||||||
"description": "The UT Registration Plus extension is a Chrome extension that allows students to easily register for classes at The University of Texas at Austin.",
|
"description": "The UT Registration Plus extension is a Chrome extension that allows students to easily register for classes at The University of Texas at Austin.",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "sriramhariharan.com",
|
"homepage": "sriramhariharan.com",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "NODE_ENV=development tsx webpack/development.ts",
|
"dev": "vite",
|
||||||
"build": "NODE_ENV=production tsx webpack/production.ts",
|
"build": "tsc && vite build",
|
||||||
"release": "tsx webpack/release.ts",
|
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
|
"preview": "vite preview",
|
||||||
"devtools": "react-devtools",
|
"devtools": "react-devtools",
|
||||||
"lint": "eslint ./ --ext .ts,.tsx"
|
"preinstall": "npx only-allow pnpm"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/sql.js": "^1.4.4",
|
"@types/sql.js": "^1.4.9",
|
||||||
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"chrome-extension-toolkit": "^0.0.51",
|
"chrome-extension-toolkit": "^0.0.51",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"clean-webpack-plugin": "^4.0.0",
|
"highcharts": "^11.2.0",
|
||||||
"highcharts": "^10.3.3",
|
"highcharts-react-official": "^3.2.1",
|
||||||
"highcharts-react-official": "^3.2.0",
|
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-devtools-core": "^5.0.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"sass": "^1.57.1",
|
"sass": "^1.69.5",
|
||||||
"sql.js": "1.8.0",
|
"sql.js": "1.9.0",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@semantic-release/exec": "^6.0.3",
|
"@crxjs/vite-plugin": "2.0.0-beta.21",
|
||||||
"@svgr/webpack": "^6.5.1",
|
"@types/chrome": "^0.0.254",
|
||||||
"@types/chrome": "^0.0.204",
|
"@types/node": "^20.10.5",
|
||||||
"@types/node": "^18.11.17",
|
"@types/prompts": "^2.4.9",
|
||||||
"@types/prompts": "^2.4.2",
|
"@types/react": "^18.2.45",
|
||||||
"@types/react": "^18.0.26",
|
"@types/react-dom": "^18.2.18",
|
||||||
"@types/react-dom": "^18.0.9",
|
"@types/semver": "^7.5.6",
|
||||||
"@types/semver": "^7.3.13",
|
"@types/uuid": "^9.0.7",
|
||||||
"@types/uuid": "^9.0.1",
|
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.47.0",
|
"@typescript-eslint/parser": "^6.15.0",
|
||||||
"@typescript-eslint/parser": "^5.47.0",
|
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||||
"archiver": "^5.3.1",
|
"cssnano": "^6.0.2",
|
||||||
"case-sensitive-paths-webpack-plugin": "^2.4.0",
|
"cssnano-preset-advanced": "^6.0.2",
|
||||||
"chalk": "^5.2.0",
|
|
||||||
"conventional-changelog-conventionalcommits": "^5.0.0",
|
|
||||||
"copy-webpack-plugin": "^11.0.0",
|
|
||||||
"create-file-webpack": "^1.0.2",
|
|
||||||
"crypto-browserify": "^3.12.0",
|
|
||||||
"css-loader": "^6.7.3",
|
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"esbuild-loader": "^2.20.0",
|
"es-module-lexer": "^1.4.1",
|
||||||
"eslint": "^8.30.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-import-resolver-typescript": "^3.5.2",
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-jsdoc": "^39.6.4",
|
"eslint-plugin-jsdoc": "^46.9.1",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^5.1.1",
|
||||||
"eslint-plugin-react": "^7.31.11",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-react-prefer-function-component": "^3.1.0",
|
"eslint-plugin-react-prefer-function-component": "^3.3.0",
|
||||||
"file-loader": "^6.2.0",
|
"eslint-plugin-react-refresh": "^0.4.5",
|
||||||
"fork-ts-checker-webpack-plugin": "^7.2.14",
|
|
||||||
"html-webpack-plugin": "^5.5.0",
|
|
||||||
"mini-css-extract-plugin": "^2.7.2",
|
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"prettier": "^2.8.1",
|
"postcss": "^8.4.32",
|
||||||
"prompts": "^2.4.2",
|
"prettier": "^3.1.1",
|
||||||
"react-dev-utils": "^12.0.1",
|
"react-dev-utils": "^12.0.1",
|
||||||
"react-devtools": "^4.27.1",
|
"react-devtools": "^4.27.1",
|
||||||
"sass-loader": "^13.2.0",
|
"typescript": "^5.3.3",
|
||||||
"semantic-release": "^19.0.5",
|
"vite": "^5.0.10",
|
||||||
"semver": "^7.3.8",
|
"vite-plugin-inspect": "^0.8.1"
|
||||||
"simple-git": "^3.15.1",
|
},
|
||||||
"socket.io": "^4.5.4",
|
"pnpm": {
|
||||||
"socket.io-client": "^4.5.4",
|
"patchedDependencies": {
|
||||||
"stream-browserify": "^3.0.0",
|
"@crxjs/vite-plugin@2.0.0-beta.21": "patches/@crxjs__vite-plugin@2.0.0-beta.21.patch"
|
||||||
"terser-webpack-plugin": "^5.3.6",
|
},
|
||||||
"ts-node": "^10.9.1",
|
"overrides": {
|
||||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
"es-module-lexer": "^1.4.1"
|
||||||
"tsx": "^3.12.1",
|
}
|
||||||
"typescript": "^4.9.5",
|
|
||||||
"url-loader": "^4.1.1",
|
|
||||||
"webpack": "^5.75.0",
|
|
||||||
"webpack-build-notifier": "^2.3.0",
|
|
||||||
"webpack-dev-server": "^4.11.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
105
patches/@crxjs__vite-plugin@2.0.0-beta.21.patch
Normal file
105
patches/@crxjs__vite-plugin@2.0.0-beta.21.patch
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
diff --git a/dist/index.mjs b/dist/index.mjs
|
||||||
|
index 5c3f6291168987c56b816428080e6f1fe9de7107..abaf6290fe9454ae036a81eacbe7dc3be2fdfbc3 100644
|
||||||
|
--- a/dist/index.mjs
|
||||||
|
+++ b/dist/index.mjs
|
||||||
|
@@ -499,16 +499,43 @@ ${sourceMap}
|
||||||
|
}),
|
||||||
|
mergeMap(async ({ target, code, deps }) => {
|
||||||
|
await lexer.init;
|
||||||
|
- const [imports] = lexer.parse(code, fileName);
|
||||||
|
+ const [imports, exports] = lexer.parse(code, fileName);
|
||||||
|
const depSet = new Set(deps);
|
||||||
|
const magic = new MagicString(code);
|
||||||
|
- for (const i of imports)
|
||||||
|
+ for (const i of imports) {
|
||||||
|
if (i.n) {
|
||||||
|
depSet.add(i.n);
|
||||||
|
const fileName2 = getFileName({ type: "module", id: i.n });
|
||||||
|
const fullImport = code.substring(i.s, i.e);
|
||||||
|
- magic.overwrite(i.s, i.e, fullImport.replace(i.n, `/${fileName2}`));
|
||||||
|
+ const hmrTimestamp = fullImport.match(/\bt=\d{13}&?\b/);
|
||||||
|
+ magic.overwrite(
|
||||||
|
+ i.s,
|
||||||
|
+ i.e,
|
||||||
|
+ fullImport.replace(
|
||||||
|
+ i.n,
|
||||||
|
+ `/${fileName2}${hmrTimestamp ? `?${hmrTimestamp[0]}` : ""}`
|
||||||
|
+ )
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ for (const e of exports) {
|
||||||
|
+ if (e.n === "default") {
|
||||||
|
+ const regex = /\s+['"](.*)['"]/y;
|
||||||
|
+ regex.lastIndex = e.e;
|
||||||
|
+ const fullExport = regex.exec(code)?.[1];
|
||||||
|
+ if (!fullExport)
|
||||||
|
+ continue;
|
||||||
|
+ const start = regex.lastIndex - fullExport.length - 1;
|
||||||
|
+ const end = regex.lastIndex - 1;
|
||||||
|
+ if (fullExport.startsWith("/node_modules")) {
|
||||||
|
+ magic.overwrite(
|
||||||
|
+ start,
|
||||||
|
+ end,
|
||||||
|
+ `http://localhost:5173${fullExport}`
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
+ }
|
||||||
|
return { target, source: magic.toString(), deps: [...depSet] };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
@@ -1229,10 +1256,14 @@ const pluginHMR = () => {
|
||||||
|
handleHotUpdate({ modules, server }) {
|
||||||
|
const { root } = server.config;
|
||||||
|
const relFiles = /* @__PURE__ */ new Set();
|
||||||
|
- for (const m of modules)
|
||||||
|
+ function getRelFile(file) {
|
||||||
|
+ return file.startsWith(root) ? file.slice(server.config.root.length) : file;
|
||||||
|
+ }
|
||||||
|
+ for (const m of modules) {
|
||||||
|
if (m.id?.startsWith(root)) {
|
||||||
|
relFiles.add(m.id.slice(server.config.root.length));
|
||||||
|
}
|
||||||
|
+ }
|
||||||
|
if (inputManifestFiles.background.length) {
|
||||||
|
const background = prefix$1("/", inputManifestFiles.background[0]);
|
||||||
|
if (relFiles.has(background) || modules.some(isImporter(join(server.config.root, background)))) {
|
||||||
|
@@ -1244,7 +1275,14 @@ const pluginHMR = () => {
|
||||||
|
for (const [key, script] of contentScripts)
|
||||||
|
if (key === script.id) {
|
||||||
|
if (relFiles.has(script.id) || modules.some(isImporter(join(server.config.root, script.id)))) {
|
||||||
|
- relFiles.forEach((relFile) => update(relFile));
|
||||||
|
+ modules.filter((mod) => mod.id?.startsWith(root)).forEach((mod) => {
|
||||||
|
+ update(getRelFile(mod.id));
|
||||||
|
+ if (mod.file?.endsWith(".scss")) {
|
||||||
|
+ mod.importers.forEach((imp) => {
|
||||||
|
+ update(getRelFile(imp.id));
|
||||||
|
+ });
|
||||||
|
+ }
|
||||||
|
+ });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1882,7 +1920,7 @@ const pluginWebAccessibleResources = () => {
|
||||||
|
if (contentScripts.size > 0) {
|
||||||
|
const viteManifest = parseJsonAsset(
|
||||||
|
bundle,
|
||||||
|
- "manifest.json"
|
||||||
|
+ ".vite/manifest.json"
|
||||||
|
);
|
||||||
|
const viteFiles = /* @__PURE__ */ new Map();
|
||||||
|
for (const [, file] of Object.entries(viteManifest))
|
||||||
|
diff --git a/package.json b/package.json
|
||||||
|
index e0c47ae66ff399ad3a78abf38d8d93d1f038c55d..f84eb09ffbb5c41094935dd06e04ffe831e2d05a 100644
|
||||||
|
--- a/package.json
|
||||||
|
+++ b/package.json
|
||||||
|
@@ -70,7 +70,7 @@
|
||||||
|
"connect-injector": "^0.4.4",
|
||||||
|
"convert-source-map": "^1.7.0",
|
||||||
|
"debug": "^4.3.3",
|
||||||
|
- "es-module-lexer": "^0.10.0",
|
||||||
|
+ "es-module-lexer": "^1.4.1",
|
||||||
|
"fast-glob": "^3.2.11",
|
||||||
|
"fs-extra": "^10.0.1",
|
||||||
|
"jsesc": "^3.0.2",
|
||||||
6381
pnpm-lock.yaml
generated
Normal file
6381
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
postcss.config.cjs
Normal file
14
postcss.config.cjs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/* eslint-disable global-require */
|
||||||
|
/** @type {import('postcss-load-config').Config} */
|
||||||
|
const config = {
|
||||||
|
plugins:
|
||||||
|
process.env.NODE_ENV !== 'development'
|
||||||
|
? [
|
||||||
|
require('cssnano')({
|
||||||
|
preset: 'advanced',
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
Binary file not shown.
@@ -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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import './hotReload';
|
import { DevStore } from '@shared/storage/DevStore';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { DevStore } from 'src/shared/storage/DevStore';
|
import { createRoot } from 'react-dom/client';
|
||||||
import render from 'src/views/lib/react';
|
|
||||||
|
|
||||||
const manifest = chrome.runtime.getManifest();
|
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
14
src/global.d.ts
vendored
Normal 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
54
src/manifest.ts
Normal 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;
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
|
import { BACKGROUND_MESSAGES } from '@shared/messages';
|
||||||
import { MessageListener } from 'chrome-extension-toolkit';
|
import { MessageListener } from 'chrome-extension-toolkit';
|
||||||
import { BACKGROUND_MESSAGES } from 'src/shared/messages';
|
|
||||||
import onInstall from './events/onInstall';
|
import onInstall from './events/onInstall';
|
||||||
import onServiceWorkerAlive from './events/onServiceWorkerAlive';
|
import onServiceWorkerAlive from './events/onServiceWorkerAlive';
|
||||||
import onUpdate from './events/onUpdate';
|
import onUpdate from './events/onUpdate';
|
||||||
import browserActionHandler from './handler/browserActionHandler';
|
import browserActionHandler from './handler/browserActionHandler';
|
||||||
import hotReloadingHandler from './handler/hotReloadingHandler';
|
|
||||||
import tabManagementHandler from './handler/tabManagementHandler';
|
import tabManagementHandler from './handler/tabManagementHandler';
|
||||||
import userScheduleHandler from './handler/userScheduleHandler';
|
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
|
// initialize the message listener that will listen for messages from the content script
|
||||||
const messageListener = new MessageListener<BACKGROUND_MESSAGES>({
|
const messageListener = new MessageListener<BACKGROUND_MESSAGES>({
|
||||||
...browserActionHandler,
|
...browserActionHandler,
|
||||||
...hotReloadingHandler,
|
|
||||||
...tabManagementHandler,
|
...tabManagementHandler,
|
||||||
...userScheduleHandler,
|
...userScheduleHandler,
|
||||||
});
|
});
|
||||||
@@ -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
|
* Called when the extension is first installed or synced onto a new machine
|
||||||
@@ -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)
|
* 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,
|
version: chrome.runtime.getManifest().version,
|
||||||
lastUpdate: Date.now(),
|
lastUpdate: Date.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
hotReloadTab();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import BrowserActionMessages from '@shared/messages/BrowserActionMessages';
|
||||||
import { MessageHandler } from 'chrome-extension-toolkit';
|
import { MessageHandler } from 'chrome-extension-toolkit';
|
||||||
import BrowserActionMessages from 'src/shared/messages/BrowserActionMessages';
|
|
||||||
|
|
||||||
const browserActionHandler: MessageHandler<BrowserActionMessages> = {
|
const browserActionHandler: MessageHandler<BrowserActionMessages> = {
|
||||||
disableBrowserAction({ sender, sendResponse }) {
|
disableBrowserAction({ sender, sendResponse }) {
|
||||||
@@ -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 { MessageHandler } from 'chrome-extension-toolkit';
|
||||||
import { DevStore } from 'src/shared/storage/DevStore';
|
|
||||||
|
|
||||||
const hotReloadingHandler: MessageHandler<HotReloadingMessages> = {
|
const hotReloadingHandler: MessageHandler<HotReloadingMessages> = {
|
||||||
async reloadExtension({ sendResponse }) {
|
async reloadExtension({ sendResponse }) {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import TabManagementMessages from '@shared/messages/TabManagementMessages';
|
||||||
import { MessageHandler } from 'chrome-extension-toolkit';
|
import { MessageHandler } from 'chrome-extension-toolkit';
|
||||||
import TabManagementMessages from 'src/shared/messages/TabManagementMessages';
|
|
||||||
import openNewTab from '../util/openNewTab';
|
import openNewTab from '../util/openNewTab';
|
||||||
|
|
||||||
const tabManagementHandler: MessageHandler<TabManagementMessages> = {
|
const tabManagementHandler: MessageHandler<TabManagementMessages> = {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { UserScheduleMessages } from '@shared/messages/UserScheduleMessages';
|
||||||
|
import { Course } from '@shared/types/Course';
|
||||||
import { MessageHandler } from 'chrome-extension-toolkit';
|
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 addCourse from '../lib/addCourse';
|
||||||
import clearCourses from '../lib/clearCourses';
|
import clearCourses from '../lib/clearCourses';
|
||||||
import createSchedule from '../lib/createSchedule';
|
import createSchedule from '../lib/createSchedule';
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { UserScheduleStore } from 'src/shared/storage/UserScheduleStore';
|
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
||||||
import { Course } from 'src/shared/types/Course';
|
import { Course } from '@shared/types/Course';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -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> {
|
export default async function clearCourses(scheduleName: string): Promise<void> {
|
||||||
const schedules = await UserScheduleStore.get('schedules');
|
const schedules = await UserScheduleStore.get('schedules');
|
||||||
@@ -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
|
* Creates a new schedule with the given name
|
||||||
@@ -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> {
|
export default async function deleteSchedule(scheduleName: string): Promise<string | undefined> {
|
||||||
const [schedules, activeIndex] = await Promise.all([
|
const [schedules, activeIndex] = await Promise.all([
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { UserScheduleStore } from 'src/shared/storage/UserScheduleStore';
|
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
||||||
import { Course } from 'src/shared/types/Course';
|
import { Course } from '@shared/types/Course';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -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> {
|
export default async function renameSchedule(scheduleName: string, newName: string): Promise<string | undefined> {
|
||||||
const schedules = await UserScheduleStore.get('schedules');
|
const schedules = await UserScheduleStore.get('schedules');
|
||||||
@@ -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> {
|
export default async function switchSchedule(scheduleName: string): Promise<void> {
|
||||||
const schedules = await UserScheduleStore.get('schedules');
|
const schedules = await UserScheduleStore.get('schedules');
|
||||||
@@ -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)
|
* 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> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
* Open the debug tab as the first tab
|
||||||
10
src/pages/calendar/CalendarMain.tsx
Normal file
10
src/pages/calendar/CalendarMain.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
18
src/pages/calendar/index.html
Normal file
18
src/pages/calendar/index.html
Normal 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>
|
||||||
5
src/pages/calendar/index.tsx
Normal file
5
src/pages/calendar/index.tsx
Normal 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 />);
|
||||||
18
src/pages/content/index.tsx
Normal file
18
src/pages/content/index.tsx
Normal 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
13
src/pages/debug/App.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
18
src/pages/debug/index.html
Normal file
18
src/pages/debug/index.html
Normal 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>
|
||||||
1
src/pages/debug/index.tsx
Normal file
1
src/pages/debug/index.tsx
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import 'src/debug';
|
||||||
13
src/pages/options/App.tsx
Normal file
13
src/pages/options/App.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
18
src/pages/options/index.html
Normal file
18
src/pages/options/index.html
Normal 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>
|
||||||
5
src/pages/options/index.tsx
Normal file
5
src/pages/options/index.tsx
Normal 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 />);
|
||||||
0
src/pages/panel/index.tsx
Normal file
0
src/pages/panel/index.tsx
Normal file
18
src/pages/popup/index.html
Normal file
18
src/pages/popup/index.html
Normal 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>
|
||||||
5
src/pages/popup/index.tsx
Normal file
5
src/pages/popup/index.tsx
Normal 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 />);
|
||||||
@@ -1,17 +1,13 @@
|
|||||||
import { createMessenger } from 'chrome-extension-toolkit';
|
import { createMessenger } from 'chrome-extension-toolkit';
|
||||||
import TAB_MESSAGES from './TabMessages';
|
|
||||||
import BrowserActionMessages from './BrowserActionMessages';
|
import BrowserActionMessages from './BrowserActionMessages';
|
||||||
import HotReloadingMessages from './HotReloadingMessages';
|
|
||||||
import TabManagementMessages from './TabManagementMessages';
|
import TabManagementMessages from './TabManagementMessages';
|
||||||
|
import TAB_MESSAGES from './TabMessages';
|
||||||
import { UserScheduleMessages } from './UserScheduleMessages';
|
import { UserScheduleMessages } from './UserScheduleMessages';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a type with all the message definitions that can be sent TO the background script
|
* This is a type with all the message definitions that can be sent TO the background script
|
||||||
*/
|
*/
|
||||||
export type BACKGROUND_MESSAGES = BrowserActionMessages &
|
export type BACKGROUND_MESSAGES = BrowserActionMessages & TabManagementMessages & UserScheduleMessages;
|
||||||
TabManagementMessages &
|
|
||||||
HotReloadingMessages &
|
|
||||||
UserScheduleMessages;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A utility object that can be used to send type-safe messages to the background script
|
* A utility object that can be used to send type-safe messages to the background script
|
||||||
|
|||||||
@@ -24,10 +24,4 @@ export const DevStore = createLocalStore<IDevStore>({
|
|||||||
reloadTabId: undefined,
|
reloadTabId: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
debugStore({ devStore: DevStore });
|
debugStore({ devStore: DevStore });
|
||||||
|
|||||||
@@ -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) */
|
/** whether we should automatically scroll to load more courses on the course schedule page (without having to click next) */
|
||||||
shouldScrollToLoad: boolean;
|
shouldScrollToLoad: boolean;
|
||||||
|
|
||||||
url: URL;
|
// url: URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OptionsStore = createSyncStore<IOptionsStore>({
|
export const OptionsStore = createSyncStore<IOptionsStore>({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { UserSchedule } from '@shared/types/UserSchedule';
|
||||||
import { createLocalStore, debugStore } from 'chrome-extension-toolkit';
|
import { createLocalStore, debugStore } from 'chrome-extension-toolkit';
|
||||||
import { UserSchedule } from 'src/shared/types/UserSchedule';
|
|
||||||
|
|
||||||
interface IUserScheduleStore {
|
interface IUserScheduleStore {
|
||||||
schedules: UserSchedule[];
|
schedules: UserSchedule[];
|
||||||
|
|||||||
@@ -11,7 +11,4 @@
|
|||||||
"node"
|
"node"
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"exclude": [
|
}
|
||||||
"../webpack"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { Course, ScrapedRow } from '@shared/types/Course';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Course, ScrapedRow } from 'src/shared/types/Course';
|
|
||||||
import { useKeyPress } from '../hooks/useKeyPress';
|
import { useKeyPress } from '../hooks/useKeyPress';
|
||||||
import useSchedules from '../hooks/useSchedules';
|
import useSchedules from '../hooks/useSchedules';
|
||||||
import { CourseCatalogScraper } from '../lib/CourseCatalogScraper';
|
import { CourseCatalogScraper } from '../lib/CourseCatalogScraper';
|
||||||
@@ -7,13 +7,12 @@ import getCourseTableRows from '../lib/getCourseTableRows';
|
|||||||
import { SiteSupport } from '../lib/getSiteSupport';
|
import { SiteSupport } from '../lib/getSiteSupport';
|
||||||
import { populateSearchInputs } from '../lib/populateSearchInputs';
|
import { populateSearchInputs } from '../lib/populateSearchInputs';
|
||||||
import ExtensionRoot from './common/ExtensionRoot/ExtensionRoot';
|
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 AutoLoad from './injected/AutoLoad/AutoLoad';
|
||||||
import CoursePopup from './injected/CoursePopup/CoursePopup';
|
import CoursePopup from './injected/CoursePopup/CoursePopup';
|
||||||
import RecruitmentBanner from './injected/RecruitmentBanner/RecruitmentBanner';
|
import RecruitmentBanner from './injected/RecruitmentBanner/RecruitmentBanner';
|
||||||
import TableHead from './injected/TableHead';
|
import TableHead from './injected/TableHead';
|
||||||
import TableRow from './injected/TableRow/TableRow';
|
import TableRow from './injected/TableRow/TableRow';
|
||||||
|
import TableSubheading from './injected/TableSubheading/TableSubheading';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
support: SiteSupport.COURSE_CATALOG_DETAILS | SiteSupport.COURSE_CATALOG_LIST;
|
support: SiteSupport.COURSE_CATALOG_DETAILS | SiteSupport.COURSE_CATALOG_LIST;
|
||||||
@@ -33,7 +32,7 @@ export default function CourseCatalogMain({ support }: Props) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const tableRows = getCourseTableRows(document);
|
const tableRows = getCourseTableRows(document);
|
||||||
const ccs = new CourseCatalogScraper(support);
|
const ccs = new CourseCatalogScraper(support);
|
||||||
const scrapedRows = ccs.scrape(tableRows);
|
const scrapedRows = ccs.scrape(tableRows, true);
|
||||||
setRows(scrapedRows);
|
setRows(scrapedRows);
|
||||||
}, [support]);
|
}, [support]);
|
||||||
|
|
||||||
@@ -64,10 +63,10 @@ export default function CourseCatalogMain({ support }: Props) {
|
|||||||
<ExtensionRoot>
|
<ExtensionRoot>
|
||||||
<RecruitmentBanner />
|
<RecruitmentBanner />
|
||||||
<TableHead>Plus</TableHead>
|
<TableHead>Plus</TableHead>
|
||||||
{rows.map(row => {
|
{rows.map((row, i) => {
|
||||||
if (!row.course) {
|
if (!row.course) {
|
||||||
// TODO: handle the course section headers
|
// TODO: handle the course section headers
|
||||||
return null;
|
return <TableSubheading key={row.element.innerText + i.toString()} row={row} />;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import ExtensionRoot from './common/ExtensionRoot/ExtensionRoot';
|
|
||||||
|
|
||||||
export default function MyCalendarMain() {
|
|
||||||
return <ExtensionRoot>MyCalendarMain</ExtensionRoot>;
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { background } from '@shared/messages';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { background } from 'src/shared/messages';
|
|
||||||
import useSchedules from '../hooks/useSchedules';
|
import useSchedules from '../hooks/useSchedules';
|
||||||
import { Button } from './common/Button/Button';
|
import { Button } from './common/Button/Button';
|
||||||
import ExtensionRoot from './common/ExtensionRoot/ExtensionRoot';
|
import ExtensionRoot from './common/ExtensionRoot/ExtensionRoot';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@import 'src/views/styles/base.module.scss';
|
@use 'sass:color';
|
||||||
|
@use 'src/views/styles/colors.module.scss';
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
background-color: #000;
|
background-color: #000;
|
||||||
@@ -7,9 +8,8 @@
|
|||||||
margin: 10px;
|
margin: 10px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: rgba(0, 0, 0, 0.4) 2px 2px 4px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease-in-out;
|
transition: all 0.1s ease-in-out;
|
||||||
font-family: 'Inter';
|
font-family: 'Inter';
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
animation: click_animation 0.2s ease-in-out;
|
transform: scale(0.96);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
@@ -30,20 +30,20 @@
|
|||||||
opacity: 0.5 !important;
|
opacity: 0.5 !important;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
animation: none !important;
|
transform: unset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@each $color,
|
@each $color,
|
||||||
$value
|
$value
|
||||||
in (
|
in (
|
||||||
primary: $burnt_orange,
|
primary: colors.$burnt_orange,
|
||||||
secondary: $charcoal,
|
secondary: colors.$charcoal,
|
||||||
tertiary: $bluebonnet,
|
tertiary: colors.$bluebonnet,
|
||||||
danger: $speedway_brick,
|
danger: colors.$speedway_brick,
|
||||||
warning: $tangerine,
|
warning: colors.$tangerine,
|
||||||
success: $turtle_pond,
|
success: colors.$turtle_pond,
|
||||||
info: $turquoise
|
info: colors.$turquoise
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
&.#{$color} {
|
&.#{$color} {
|
||||||
@@ -51,12 +51,12 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: lighten($value, 10%);
|
background-color: color.adjust($value, $lightness: 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus,
|
&:focus-visible,
|
||||||
&:active {
|
&:active {
|
||||||
background-color: darken($value, 10%);
|
background-color: color.adjust($value, $lightness: -10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
@@ -65,15 +65,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes click_animation {
|
|
||||||
0% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: scale(0.9);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@import 'src/views/styles/base.module.scss';
|
@use 'src/views/styles/colors.module.scss';
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background: $white;
|
background: colors.$white;
|
||||||
border: 0.5px dotted #c3cee0;
|
border: 0.5px dotted #c3cee0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@import 'src/views/styles/base.module.scss';
|
@use 'src/views/styles/colors.module.scss';
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
border: 1px solid $limestone;
|
border: 1px solid colors.$limestone;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
|
||||||
import classnames from 'classnames';
|
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';
|
import styles from './Divider.module.scss';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
@import 'src/views/styles/fonts.module.scss';
|
@use 'src/views/styles/fonts.module.scss';
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
font-family: 'Material Icons Round';
|
font-family: 'Material Icons Round';
|
||||||
font-weight: $normal_weight;
|
font-weight: fonts.$normal_weight;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: $medium_size;
|
font-size: fonts.$medium_size;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
letter-spacing: normal;
|
letter-spacing: normal;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import colors, { Color } from 'src/views/styles/colors.module.scss';
|
import colors, { Color } from '@views/styles/colors.module.scss';
|
||||||
import fonts, { Size, Weight } from 'src/views/styles/fonts.module.scss';
|
import fonts, { Size } from '@views/styles/fonts.module.scss';
|
||||||
import styles from './Icon.module.scss';
|
import styles from './Icon.module.scss';
|
||||||
import { MaterialIconCode } from './MaterialIcons';
|
import { MaterialIconCode } from './MaterialIcons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
export type Props = {
|
export type Props = {
|
||||||
name: MaterialIconCode;
|
name: MaterialIconCode;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { background } from '@shared/messages';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { PropsWithChildren } from 'react';
|
import React, { PropsWithChildren } from 'react';
|
||||||
import { background } from 'src/shared/messages';
|
|
||||||
import Text, { TextProps } from '../Text/Text';
|
import Text, { TextProps } from '../Text/Text';
|
||||||
import styles from './Link.module.scss';
|
import styles from './Link.module.scss';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import 'src/views/styles/base.module.scss';
|
@use 'src/views/styles/colors.module.scss';
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
.body {
|
.body {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
z-index: 2147483647;
|
z-index: 2147483647;
|
||||||
background-color: $white;
|
background-color: colors.$white;
|
||||||
box-shadow: 0px 12px 30px 0px #323e5f29;
|
box-shadow: 0px 12px 30px 0px #323e5f29;
|
||||||
transition: box-shadow 0.15s;
|
transition: box-shadow 0.15s;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
@import 'src/views/styles/base.module.scss';
|
@use 'src/views/styles/colors.module.scss';
|
||||||
|
|
||||||
$spinner-border-width: 10px;
|
$spinner-border-width: 10px;
|
||||||
|
|
||||||
.spinner {
|
.spinner {
|
||||||
border: 1px solid $charcoal;
|
border: 1px solid colors.$charcoal;
|
||||||
border-width: $spinner-border-width;
|
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;
|
margin: 0 auto 15px auto;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 60px;
|
width: 60px;
|
||||||
|
|||||||
@@ -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 {
|
.text {
|
||||||
font-family: 'Inter', sans-serif;
|
font-family: 'Inter', sans-serif;
|
||||||
color: $charcoal;
|
color: colors.$charcoal;
|
||||||
line-height: initial;
|
line-height: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
.light_weight {
|
.light_weight {
|
||||||
font-weight: $light_weight;
|
font-weight: fonts.$light_weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
.regular_weight {
|
.regular_weight {
|
||||||
font-weight: $regular_weight;
|
font-weight: fonts.$regular_weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
.normal_weight {
|
.normal_weight {
|
||||||
font-weight: $normal_weight;
|
font-weight: fonts.$normal_weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
.semi_bold_weight {
|
.semi_bold_weight {
|
||||||
font-weight: $semi_bold_weight;
|
font-weight: fonts.$semi_bold_weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bold_weight {
|
.bold_weight {
|
||||||
font-weight: $bold_weight;
|
font-weight: fonts.$bold_weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
.black_weight {
|
.black_weight {
|
||||||
font-weight: $black_weight;
|
font-weight: fonts.$black_weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
.x_small_size {
|
.x_small_size {
|
||||||
font-size: $x_small_size;
|
font-size: fonts.$x_small_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
.xx_small_size {
|
.xx_small_size {
|
||||||
font-size: $xx_small_size;
|
font-size: fonts.$xx_small_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
.small_size {
|
.small_size {
|
||||||
font-size: $small_size;
|
font-size: fonts.$small_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
.medium_size {
|
.medium_size {
|
||||||
font-size: $medium_size;
|
font-size: fonts.$medium_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
.large_size {
|
.large_size {
|
||||||
font-size: $large_size;
|
font-size: fonts.$large_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
.x_large_size {
|
.x_large_size {
|
||||||
font-size: $x_large_size;
|
font-size: fonts.$x_large_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
.xx_large_size {
|
.xx_large_size {
|
||||||
font-size: $xx_large_size;
|
font-size: fonts.$xx_large_size;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { PropsWithChildren } from 'react';
|
import React, { PropsWithChildren } from 'react';
|
||||||
import colors, { Color } from 'src/views/styles/colors.module.scss';
|
import colors, { Color } from '@views/styles/colors.module.scss';
|
||||||
import fonts, { Size, Weight } from 'src/views/styles/fonts.module.scss';
|
import { Size, Weight } from '@views/styles/fonts.module.scss';
|
||||||
import styles from './Text.module.scss';
|
import styles from './Text.module.scss';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
export type TextProps = {
|
export type TextProps = {
|
||||||
color?: Color;
|
color?: Color;
|
||||||
weight?: Weight;
|
weight?: Weight;
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
|
import { ScrapedRow } from '@shared/types/Course';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ScrapedRow } from 'src/shared/types/Course';
|
import useInfiniteScroll from '@views/hooks/useInfiniteScroll';
|
||||||
import useInfiniteScroll from 'src/views/hooks/useInfiniteScroll';
|
import { CourseCatalogScraper } from '@views/lib/CourseCatalogScraper';
|
||||||
import { CourseCatalogScraper } from 'src/views/lib/CourseCatalogScraper';
|
import { SiteSupport } from '@views/lib/getSiteSupport';
|
||||||
import { SiteSupport } from 'src/views/lib/getSiteSupport';
|
|
||||||
import {
|
import {
|
||||||
loadNextCourseCatalogPage,
|
|
||||||
AutoLoadStatus,
|
AutoLoadStatus,
|
||||||
|
loadNextCourseCatalogPage,
|
||||||
removePaginationButtons,
|
removePaginationButtons,
|
||||||
} from 'src/views/lib/loadNextCourseCatalogPage';
|
} from '@views/lib/loadNextCourseCatalogPage';
|
||||||
import Spinner from '../../common/Spinner/Spinner';
|
|
||||||
import styles from './AutoLoad.module.scss';
|
import styles from './AutoLoad.module.scss';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -53,16 +52,21 @@ export default function AutoLoad({ addRows }: Props) {
|
|||||||
addRows(scrapedRows);
|
addRows(scrapedRows);
|
||||||
}, [addRows]);
|
}, [addRows]);
|
||||||
|
|
||||||
if (!container || status === AutoLoadStatus.IDLE) {
|
if (!container || status === AutoLoadStatus.DONE) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<div>
|
<div>
|
||||||
{status === AutoLoadStatus.LOADING && (
|
{status !== AutoLoadStatus.ERROR && (
|
||||||
<div>
|
<div
|
||||||
<Spinner />
|
style={{
|
||||||
<h2>Loading Next Page...</h2>
|
height: '500px',
|
||||||
|
backgroundColor: '#f4f4f4',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* <Spinner />
|
||||||
|
<h2>Loading Next Page...</h2> */}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{status === AutoLoadStatus.ERROR && (
|
{status === AutoLoadStatus.ERROR && (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import 'src/views/styles/base.module.scss';
|
@use 'src/views/styles/colors.module.scss';
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.restriction {
|
.restriction {
|
||||||
color: $speedway_brick;
|
color: colors.$speedway_brick;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import { Course } from '@shared/types/Course';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Course } from 'src/shared/types/Course';
|
import Spinner from '@views/components/common/Spinner/Spinner';
|
||||||
import Spinner from 'src/views/components/common/Spinner/Spinner';
|
import Text from '@views/components/common/Text/Text';
|
||||||
import Text from 'src/views/components/common/Text/Text';
|
import { CourseCatalogScraper } from '@views/lib/CourseCatalogScraper';
|
||||||
import { CourseCatalogScraper } from 'src/views/lib/CourseCatalogScraper';
|
import { SiteSupport } from '@views/lib/getSiteSupport';
|
||||||
import { SiteSupport } from 'src/views/lib/getSiteSupport';
|
|
||||||
import Card from '../../../common/Card/Card';
|
import Card from '../../../common/Card/Card';
|
||||||
import styles from './CourseDescription.module.scss';
|
import styles from './CourseDescription.module.scss';
|
||||||
|
|
||||||
@@ -18,6 +18,9 @@ enum LoadStatus {
|
|||||||
ERROR = 'ERROR',
|
ERROR = 'ERROR',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
export default function CourseDescription({ course }: Props) {
|
export default function CourseDescription({ course }: Props) {
|
||||||
const [description, setDescription] = useState<string[]>([]);
|
const [description, setDescription] = useState<string[]>([]);
|
||||||
const [status, setStatus] = useState<LoadStatus>(LoadStatus.LOADING);
|
const [status, setStatus] = useState<LoadStatus>(LoadStatus.LOADING);
|
||||||
|
|||||||
@@ -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 React from 'react';
|
||||||
import { background } from 'src/shared/messages';
|
import { Button } from '@views/components/common/Button/Button';
|
||||||
import { Course } from 'src/shared/types/Course';
|
import Card from '@views/components/common/Card/Card';
|
||||||
import { UserSchedule } from 'src/shared/types/UserSchedule';
|
import Icon from '@views/components/common/Icon/Icon';
|
||||||
import { Button } from 'src/views/components/common/Button/Button';
|
import Text from '@views/components/common/Text/Text';
|
||||||
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 styles from './CourseButtons.module.scss';
|
import styles from './CourseButtons.module.scss';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import { Course } from '@shared/types/Course';
|
||||||
|
import { UserSchedule } from '@shared/types/UserSchedule';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Course } from 'src/shared/types/Course';
|
import Card from '@views/components/common/Card/Card';
|
||||||
import { UserSchedule } from 'src/shared/types/UserSchedule';
|
import Icon from '@views/components/common/Icon/Icon';
|
||||||
import Card from 'src/views/components/common/Card/Card';
|
import Link from '@views/components/common/Link/Link';
|
||||||
import Icon from 'src/views/components/common/Icon/Icon';
|
import Text from '@views/components/common/Text/Text';
|
||||||
import Link from 'src/views/components/common/Link/Link';
|
|
||||||
import Text from 'src/views/components/common/Text/Text';
|
|
||||||
import CourseButtons from './CourseButtons/CourseButtons';
|
import CourseButtons from './CourseButtons/CourseButtons';
|
||||||
import styles from './CourseHeader.module.scss';
|
import styles from './CourseHeader.module.scss';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { Course } from '@shared/types/Course';
|
||||||
|
import { UserSchedule } from '@shared/types/UserSchedule';
|
||||||
import React from 'react';
|
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 Popup from '../../common/Popup/Popup';
|
||||||
import CourseDescription from './CourseDescription/CourseDescription';
|
import CourseDescription from './CourseDescription/CourseDescription';
|
||||||
import CourseHeader from './CourseHeader/CourseHeader';
|
import CourseHeader from './CourseHeader/CourseHeader';
|
||||||
|
|||||||
@@ -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 {
|
.chartContainer {
|
||||||
height: 250px;
|
height: 250px;
|
||||||
@@ -14,11 +15,11 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
select {
|
select {
|
||||||
z-index: $MAX_Z_INDEX;
|
z-index: elevation.$MAX_Z_INDEX;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
font-family: 'Inter';
|
font-family: 'Inter';
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border-color: $charcoal;
|
border-color: colors.$charcoal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
/* eslint-disable no-nested-ternary */
|
/* eslint-disable no-nested-ternary */
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import { Course, Semester } from '@shared/types/Course';
|
||||||
import HighchartsReact from 'highcharts-react-official';
|
import { Distribution, LetterGrade } from '@shared/types/Distribution';
|
||||||
import Highcharts from 'highcharts';
|
import Highcharts from 'highcharts';
|
||||||
import Card from 'src/views/components/common/Card/Card';
|
import HighchartsReact from 'highcharts-react-official';
|
||||||
import { Course, Semester } from 'src/shared/types/Course';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import colors from 'src/views/styles/colors.module.scss';
|
import Card from '@views/components/common/Card/Card';
|
||||||
import Spinner from 'src/views/components/common/Spinner/Spinner';
|
import Icon from '@views/components/common/Icon/Icon';
|
||||||
import Text from 'src/views/components/common/Text/Text';
|
import Spinner from '@views/components/common/Spinner/Spinner';
|
||||||
import Icon from 'src/views/components/common/Icon/Icon';
|
import Text from '@views/components/common/Text/Text';
|
||||||
import { Distribution, LetterGrade } from 'src/shared/types/Distribution';
|
|
||||||
import {
|
import {
|
||||||
NoDataError,
|
NoDataError,
|
||||||
queryAggregateDistribution,
|
queryAggregateDistribution,
|
||||||
querySemesterDistribution,
|
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';
|
import styles from './GradeDistribution.module.scss';
|
||||||
|
|
||||||
enum DataStatus {
|
enum DataStatus {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@import 'src/views/styles/base.module.scss';
|
@use 'src/views/styles/colors.module.scss';
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
background-color: $burnt_orange;
|
background-color: colors.$burnt_orange;
|
||||||
color: white;
|
color: white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ export default function TableHead({ children }: PropsWithChildren) {
|
|||||||
const lastTableHeadCell = document.querySelector('table thead th:last-child');
|
const lastTableHeadCell = document.querySelector('table thead th:last-child');
|
||||||
lastTableHeadCell!.after(container);
|
lastTableHeadCell!.after(container);
|
||||||
setContainer(container);
|
setContainer(container);
|
||||||
|
return () => {
|
||||||
|
container.remove();
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!container) {
|
if (!container) {
|
||||||
|
|||||||
@@ -1,33 +1,21 @@
|
|||||||
@import 'src/views/styles/base.module.scss';
|
@use 'src/views/styles/colors.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
.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 {
|
.conflictTooltip {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: none;
|
display: none;
|
||||||
@@ -54,4 +42,46 @@
|
|||||||
display: initial;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { Course, ScrapedRow } from '@shared/types/Course';
|
||||||
|
import { UserSchedule } from '@shared/types/UserSchedule';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
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 { Button } from '../../common/Button/Button';
|
||||||
import Icon from '../../common/Icon/Icon';
|
import Icon from '../../common/Icon/Icon';
|
||||||
import Text from '../../common/Text/Text';
|
import Text from '../../common/Text/Text';
|
||||||
@@ -29,6 +29,7 @@ export default function TableRow({ row, isSelected, activeSchedule, onClick }: P
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
element.classList.add(styles.row);
|
element.classList.add(styles.row);
|
||||||
const portalContainer = document.createElement('td');
|
const portalContainer = document.createElement('td');
|
||||||
|
portalContainer.style.textAlign = 'right';
|
||||||
const lastTableCell = element.querySelector('td:last-child');
|
const lastTableCell = element.querySelector('td:last-child');
|
||||||
lastTableCell!.after(portalContainer);
|
lastTableCell!.after(portalContainer);
|
||||||
setContainer(portalContainer);
|
setContainer(portalContainer);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -6,6 +6,9 @@ import { useEffect } from 'react';
|
|||||||
* @returns isLoading boolean to indicate if the callback is currently being executed
|
* @returns isLoading boolean to indicate if the callback is currently being executed
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
export default function useInfiniteScroll(
|
export default function useInfiniteScroll(
|
||||||
callback: () => Promise<void> | void,
|
callback: () => Promise<void> | void,
|
||||||
deps?: React.DependencyList | undefined
|
deps?: React.DependencyList | undefined
|
||||||
@@ -13,13 +16,15 @@ export default function useInfiniteScroll(
|
|||||||
const isScrolling = () => {
|
const isScrolling = () => {
|
||||||
const { innerHeight } = window;
|
const { innerHeight } = window;
|
||||||
const { scrollTop, offsetHeight } = document.documentElement;
|
const { scrollTop, offsetHeight } = document.documentElement;
|
||||||
if (innerHeight + scrollTop >= offsetHeight - 100) {
|
if (innerHeight + scrollTop >= offsetHeight - 650) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener('scroll', isScrolling);
|
window.addEventListener('scroll', isScrolling, {
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
return () => window.removeEventListener('scroll', isScrolling);
|
return () => window.removeEventListener('scroll', isScrolling);
|
||||||
}, deps);
|
}, deps);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { 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[]] {
|
export default function useSchedules(): [active: UserSchedule | null, schedules: UserSchedule[]] {
|
||||||
const [schedules, setSchedules] = useState<UserSchedule[]>([]);
|
const [schedules, setSchedules] = useState<UserSchedule[]>([]);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
|
import TAB_MESSAGES from '@shared/messages/TabMessages';
|
||||||
import { createUseMessage } from 'chrome-extension-toolkit';
|
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>();
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Serialized } from 'chrome-extension-toolkit';
|
import { ExtensionStore } from '@shared/storage/ExtensionStore';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { ExtensionStore } from 'src/shared/storage/ExtensionStore';
|
|
||||||
|
|
||||||
export default function useVersion(): string {
|
export default function useVersion(): string {
|
||||||
const [version, setVersion] = useState<string>('');
|
const [version, setVersion] = useState<string>('');
|
||||||
@@ -17,4 +16,3 @@ export default function useVersion(): string {
|
|||||||
|
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
|
import { ContextInvalidated, createShadowDOM, onContextInvalidated } from 'chrome-extension-toolkit';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { background } from 'src/shared/messages';
|
|
||||||
import render from './lib/react';
|
import render from './lib/react';
|
||||||
|
|
||||||
import { ContextInvalidated, createShadowDOM, isExtensionPopup, onContextInvalidated } from 'chrome-extension-toolkit';
|
|
||||||
import CourseCatalogMain from './components/CourseCatalogMain';
|
import CourseCatalogMain from './components/CourseCatalogMain';
|
||||||
import colors from './styles/colors.module.scss';
|
|
||||||
import getSiteSupport, { SiteSupport } from './lib/getSiteSupport';
|
|
||||||
import PopupMain from './components/PopupMain';
|
import PopupMain from './components/PopupMain';
|
||||||
|
import getSiteSupport, { SiteSupport } from './lib/getSiteSupport';
|
||||||
|
import colors from './styles/colors.module.scss';
|
||||||
|
|
||||||
const support = getSiteSupport(window.location.href);
|
const support = getSiteSupport(window.location.href);
|
||||||
console.log('support:', support);
|
console.log('support:', support);
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { Serialized } from 'chrome-extension-toolkit';
|
import { Course, InstructionMode, ScrapedRow, Semester, Status } from '@shared/types/Course';
|
||||||
import { Course, Status, InstructionMode, ScrapedRow, Semester } from 'src/shared/types/Course';
|
import { CourseSchedule } from '@shared/types/CourseSchedule';
|
||||||
import { CourseSchedule } from 'src/shared/types/CourseSchedule';
|
import Instructor from '@shared/types/Instructor';
|
||||||
import Instructor from 'src/shared/types/Instructor';
|
import { SiteSupport } from '@views/lib/getSiteSupport';
|
||||||
import { SiteSupport } from 'src/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)
|
* 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)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import initSqlJs from 'sql.js/dist/sql-wasm';
|
import initSqlJs from 'sql.js/dist/sql-wasm';
|
||||||
|
|
||||||
const WASM_FILE_URL = chrome.runtime.getURL('database/sql-wasm.wasm');
|
import DB_FILE_URL from '@public/database/grades.db?url';
|
||||||
const DB_FILE_URL = chrome.runtime.getURL('database/grades.db');
|
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
|
* A utility type for the SQL.js Database type
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Course, Semester } from 'src/shared/types/Course';
|
import { Course, Semester } from '@shared/types/Course';
|
||||||
import { CourseSQLRow, Distribution } from 'src/shared/types/Distribution';
|
import { CourseSQLRow, Distribution } from '@shared/types/Distribution';
|
||||||
import { initializeDB } from './initializeDB';
|
import { initializeDB } from './initializeDB';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export enum AutoLoadStatus {
|
|||||||
LOADING = 'LOADING',
|
LOADING = 'LOADING',
|
||||||
IDLE = 'IDLE',
|
IDLE = 'IDLE',
|
||||||
ERROR = 'ERROR',
|
ERROR = 'ERROR',
|
||||||
|
DONE = 'DONE',
|
||||||
}
|
}
|
||||||
|
|
||||||
let isLoading = false;
|
let isLoading = false;
|
||||||
@@ -24,7 +25,7 @@ let nextPageURL = getNextButton(document)?.href;
|
|||||||
export async function loadNextCourseCatalogPage(): Promise<[AutoLoadStatus, HTMLTableRowElement[]]> {
|
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 there is no more nextPageURL, then we have reached the end of the course catalog, so we can stop
|
||||||
if (!nextPageURL) {
|
if (!nextPageURL) {
|
||||||
return [AutoLoadStatus.IDLE, []];
|
return [AutoLoadStatus.DONE, []];
|
||||||
}
|
}
|
||||||
// remove the next button so that we don't load the same page twice
|
// remove the next button so that we don't load the same page twice
|
||||||
removePaginationButtons(document);
|
removePaginationButtons(document);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import './colors.module.scss';
|
@use 'colors.module.scss';
|
||||||
@import './fonts.module.scss';
|
@use 'fonts.module.scss';
|
||||||
@import './elevation.module.scss';
|
@use 'elevation.module.scss';
|
||||||
@import './utils.module.scss';
|
@use 'utils.module.scss';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@each $weights in '100' '200' '300' '400' '500' '600' '700' '800' '900' {
|
@each $weights in '100' '200' '300' '400' '500' '600' '700' '800' '900' {
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Inter';
|
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-display: auto;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: #{$weights};
|
font-weight: #{$weights};
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block;
|
font-display: block;
|
||||||
font-weight: 400;
|
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;
|
$light_weight: 300;
|
||||||
|
|||||||
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
@@ -1,55 +1,57 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2021",
|
"baseUrl": ".",
|
||||||
"outDir": "./",
|
"rootDir": ".",
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "esnext",
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
|
"jsx": "react",
|
||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
"./node_modules/@types",
|
"./node_modules/@types",
|
||||||
"./@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,
|
"skipLibCheck": true,
|
||||||
"strictBindCallApply": true,
|
"esModuleInterop": true,
|
||||||
"pretty": true,
|
"resolveJsonModule": true,
|
||||||
"noImplicitReturns": false,
|
"moduleResolution": "node",
|
||||||
"baseUrl": "./",
|
"types": [
|
||||||
|
"vite/client",
|
||||||
|
"node"
|
||||||
|
],
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"src/*": [
|
"src/*": [
|
||||||
"./src/*"
|
"src/*"
|
||||||
],
|
],
|
||||||
"webpack/*": [
|
"@assets/*": [
|
||||||
"./webpack/*"
|
"src/assets/*"
|
||||||
],
|
],
|
||||||
},
|
"@pages/*": [
|
||||||
"noImplicitThis": true,
|
"src/pages/*"
|
||||||
"noImplicitAny": false,
|
],
|
||||||
"strictNullChecks": true,
|
"@public/*": [
|
||||||
"forceConsistentCasingInFileNames": true,
|
"public/*"
|
||||||
|
],
|
||||||
|
"@shared/*": [
|
||||||
|
"src/shared/*"
|
||||||
|
],
|
||||||
|
"@background/*": [
|
||||||
|
"src/pages/background/*"
|
||||||
|
],
|
||||||
|
"@views/*": [
|
||||||
|
"src/views/*"
|
||||||
|
],
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*",
|
"src",
|
||||||
"webpack/**/*",
|
"utils",
|
||||||
"@types/**/*",
|
"vite.config.ts",
|
||||||
"./package.json",
|
"node_modules/@types",
|
||||||
"./release.config.js",
|
"src/manifest.ts",
|
||||||
"webpack/plugins/custom/.ts"
|
"package.json",
|
||||||
],
|
".eslintrc",
|
||||||
"exclude": [
|
"postcss.config.cjs"
|
||||||
"node_modules",
|
]
|
||||||
"**/.*/",
|
}
|
||||||
"build",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|||||||
48
utils/log.ts
Normal file
48
utils/log.ts
Normal file
@@ -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;
|
||||||
26
utils/plugins/make-manifest.ts
Normal file
26
utils/plugins/make-manifest.ts
Normal file
@@ -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');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
148
vite.config.ts
Normal file
148
vite.config.ts
Normal file
@@ -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'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -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();
|
|
||||||
@@ -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;
|
|
||||||
@@ -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<string, any>): 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',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -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: ['<all_urls>'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user