Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93548627a6 | ||
|
|
638ee88c96 | ||
|
|
50e88fa015 | ||
|
|
b3ae91d8f3 | ||
|
|
0077ae70d2 | ||
|
|
94744e01b9 | ||
|
|
8de88d6ad7 | ||
|
|
2d0804f90e | ||
|
|
d3577358d0 | ||
|
|
7346720894 | ||
|
|
b00bf6c180 | ||
|
|
eb306787ae | ||
|
|
643ea13207 | ||
|
|
6f1afc5b25 | ||
|
|
83d76f72da | ||
|
|
768ac776ed | ||
|
|
9995b93d2a | ||
|
|
4f609aeec7 | ||
|
|
b6eccaca6a | ||
|
|
cef99c2d72 | ||
|
|
86792eb56f | ||
|
|
a715bbd933 | ||
|
|
c2007ef090 | ||
|
|
b967240f8f | ||
|
|
839f9c6d6a | ||
|
|
f29e3ef97d | ||
|
|
79dd29cfc9 | ||
|
|
e082158592 | ||
|
|
e261641e59 | ||
|
|
5634fbed8a | ||
|
|
ceba38b1ac | ||
|
|
05f00b23d2 | ||
|
|
59f173c4e7 | ||
|
|
aeff5e09a2 |
@@ -172,7 +172,14 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
'@typescript-eslint/no-explicit-any': 'error',
|
'@typescript-eslint/no-explicit-any': 'error',
|
||||||
'@typescript-eslint/no-unused-vars': 'warn',
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
caughtErrorsIgnorePattern: '^_',
|
||||||
|
},
|
||||||
|
],
|
||||||
'@typescript-eslint/naming-convention': 'off',
|
'@typescript-eslint/naming-convention': 'off',
|
||||||
'@typescript-eslint/space-before-function-paren': 'off',
|
'@typescript-eslint/space-before-function-paren': 'off',
|
||||||
'@typescript-eslint/ban-ts-comment': 'off',
|
'@typescript-eslint/ban-ts-comment': 'off',
|
||||||
|
|||||||
1
@types/vite-env.d.ts
vendored
1
@types/vite-env.d.ts
vendored
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly VITE_PACKAGE_VERSION: string;
|
readonly VITE_PACKAGE_VERSION: string;
|
||||||
|
readonly VITE_SENTRY_ENVIRONMENT: string;
|
||||||
readonly VITE_BETA_BUILD?: 'true';
|
readonly VITE_BETA_BUILD?: 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
46
CHANGELOG.md
46
CHANGELOG.md
@@ -1,4 +1,46 @@
|
|||||||
## 2.0.0 (2024-10-15)
|
## [2.0.2](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/v2.0.1...v2.0.2) (2024-11-05)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- add core curriculum chips to injected popup ([#372](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/372)) ([6f1afc5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6f1afc5b25441c6a1fbfdf57b3c8b5b74e36f5a0))
|
||||||
|
- Add linkedin social to calendar ([#368](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/368)) ([b6eccac](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b6eccaca6a2cdba9b57d2f49f064ae8504bbd5cb))
|
||||||
|
- add more relevant links to the From the Team section ([#380](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/380)) ([643ea13](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/643ea1320798aabb7783d267f5e6fd7c00fc2e3f))
|
||||||
|
- bold course number in grade distribution chart, change text to ut-black ([#406](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/406)) ([638ee88](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/638ee88c96510a779c157b524903caaeffc9ef19))
|
||||||
|
- disable/some actions when no instructor ([#319](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/319)) ([839f9c6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/839f9c6d6afd4a1eae1a0bdf8893ab2e19b9fdff))
|
||||||
|
- **ui:** changed popup close icon to ut-black ([#394](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/394)) ([0077ae7](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0077ae70d22f24549c4c3b243188d19adbfbac14)), closes [#333F48](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/333F48)
|
||||||
|
- update senior swe admins ([#326](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/326)) ([b967240](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b967240f8fbb7a790a78f4aa256f0a77a491abb8))
|
||||||
|
- update useful links ([#367](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/367)) ([cef99c2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/cef99c2d72d3a2800f8a918d01cb116f8795d0c8))
|
||||||
|
- use "copy of" for duplicated schedules and place them under the original schedule [#358](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/358) ([#397](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/397)) ([94744e0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/94744e01b94819fb4f5d64616ea56857b906c2dd))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- added descending sort for commits on contributor section in settings page ([#365](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/365)) ([a715bbd](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a715bbd933a87742e7bce3a44e8ba1bd419ad5eb)), closes [#363](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/363) [#363](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/363) [#363](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/363)
|
||||||
|
- change schedule total courses text color to UTRP black ([#369](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/369)) ([b00bf6c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b00bf6c180f1c6c3a61c5ef855e160ddf4af3ea4))
|
||||||
|
- changed the font-weight of h1-course ([#370](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/370)) ([4f609ae](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4f609aeec797c1f99f0a57e5aeef7b82756ea4bc)), closes [#347](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/347)
|
||||||
|
- ensure input elements take full width of parent ([#364](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/364)) ([c2007ef](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/c2007ef090aab3bbfcb8bca1ebc476255d09cb90))
|
||||||
|
- remove screenshot padding class for png download for [#344](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/344) ([#376](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/376)) ([768ac77](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/768ac776ed4d5ca2113a032a93c2dc7432915aa1)), closes [#334](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/334)
|
||||||
|
- sentry issues ([#389](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/389)) ([2d0804f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2d0804f90e5d7a9ff83f7fd5c5acfdc7c1b1cc84))
|
||||||
|
- typo in settings page ([#386](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/386)) ([d357735](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d3577358d0d1fb60f2c776ae4b01e255fcf9109e))
|
||||||
|
- **ui:** add space before/after forward slash in "ASYNC/OTHER" text ([#366](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/366)) ([86792eb](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/86792eb56f04b615f7d52b2f417b88f4cb9a82ec))
|
||||||
|
- **ui:** duplicate schedule warning ([#295](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/295)) ([7346720](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/73467208947e0116ce8538052ee75dea1d8038f9))
|
||||||
|
- **ui:** main popup now shows 0 for empty schedule ([#395](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/395)) ([8de88d6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8de88d6ad7d4c2b5c3aa08e1efc59f7226b40c6b))
|
||||||
|
- **ui:** multiple instructors are formatted properly, displays last name only, and are capitalized in all course blocks ([#342](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/342)) ([#403](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/403)) ([50e88fa](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/50e88fa015e0290fbe0dab8a19f8fcdbc4dd02b0))
|
||||||
|
- **ui:** placeholder text for no instructor course [#400](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/400) ([#402](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/402)) ([b3ae91d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b3ae91d8f3cebb89e5e5cea7f1200d28326afb4d))
|
||||||
|
|
||||||
|
## [2.0.1](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/v2.0.0...v2.0.1) (2024-10-17)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- spring 2024 instructors db ([#317](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/317)) ([79dd29c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/79dd29cfc9e849b09e7d91bd0eed51c1c93b3352))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- add a little error checking to settings ([#315](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/315)) ([e261641](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e261641e5985d8bd5047d8a0be5d1caae844e40f))
|
||||||
|
- gulp zip ([#314](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/314)) ([05f00b2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/05f00b23d26b90f564710db4364426e90c8d6831))
|
||||||
|
- migration loop ([aeff5e0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/aeff5e09a238503293c3882d97d40270da1e4883))
|
||||||
|
- show calendar in active window ([#312](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/312)) ([ceba38b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ceba38b1ac74ec9e6630222183bd466a8d12c27d))
|
||||||
|
|
||||||
|
## [2.0.0](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/b4e8c7589e53f1064d70703459cc6d66fae1b04c...v2.0.0) (2024-10-15)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
@@ -133,12 +175,14 @@
|
|||||||
- splash text additions before v2 release ([#296](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/296)) ([e774f31](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e774f316e3f92c03e79274a55f1f729178c9a14a))
|
- splash text additions before v2 release ([#296](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/296)) ([e774f31](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e774f316e3f92c03e79274a55f1f729178c9a14a))
|
||||||
- splash text has arrived! ([#246](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/246)) ([9971435](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9971435716dec11e954dc26c14bf7d1505563d0d))
|
- splash text has arrived! ([#246](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/246)) ([9971435](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9971435716dec11e954dc26c14bf7d1505563d0d))
|
||||||
- Storybook for Vite ([#52](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/52)) ([9cc299c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9cc299ced6132644a5c2375b95a8a16a3482601b))
|
- Storybook for Vite ([#52](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/52)) ([9cc299c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9cc299ced6132644a5c2375b95a8a16a3482601b))
|
||||||
|
- swe title updates ([#310](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/310)) ([4629626](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4629626a31934c491758ddca874e5018d9c22e57))
|
||||||
- switch button ([#229](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/229)) ([abae7a5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/abae7a5c22ab944262d66ae897298b008a03c5f1))
|
- switch button ([#229](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/229)) ([abae7a5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/abae7a5c22ab944262d66ae897298b008a03c5f1))
|
||||||
- temporary removal of waitlist etc ([#236](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/236)) ([d424ccc](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d424ccce49ba05305b24cb61aeb4ffeada5bab33))
|
- temporary removal of waitlist etc ([#236](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/236)) ([d424ccc](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d424ccce49ba05305b24cb61aeb4ffeada5bab33))
|
||||||
- UnoCSS (TailwindCSS) (Storybook only) ([#61](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/61)) ([85c7f78](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/85c7f7817c58f13f6a4e8bec13a45f6412ad87db))
|
- UnoCSS (TailwindCSS) (Storybook only) ([#61](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/61)) ([85c7f78](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/85c7f7817c58f13f6a4e8bec13a45f6412ad87db))
|
||||||
- UnoCSS (TailwindCSS) (Storybook only) ([#61](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/61)) ([6521a4b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6521a4b2a90af06e182c779f2fc7cb1a3b1e55c1))
|
- UnoCSS (TailwindCSS) (Storybook only) ([#61](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/61)) ([6521a4b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6521a4b2a90af06e182c779f2fc7cb1a3b1e55c1))
|
||||||
- unplugin-icons ([#62](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/62)) ([2d67b12](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2d67b1218ff852c59901b77909615c5f54794a67))
|
- unplugin-icons ([#62](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/62)) ([2d67b12](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2d67b1218ff852c59901b77909615c5f54794a67))
|
||||||
- unplugin-icons ([#62](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/62)) ([945e09b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/945e09b49e4ffa9bd4b02442f6567bb99831923e))
|
- unplugin-icons ([#62](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/62)) ([945e09b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/945e09b49e4ffa9bd4b02442f6567bb99831923e))
|
||||||
|
- update admin titles ([4cf8c3f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4cf8c3f9645cd8fda07244413cdc406105b12fec))
|
||||||
- update badge count when schedule changes ([#150](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/150)) ([a5e9e3c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a5e9e3c2145d61d8cc5788eb50fa19718e6d13bf))
|
- update badge count when schedule changes ([#150](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/150)) ([a5e9e3c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a5e9e3c2145d61d8cc5788eb50fa19718e6d13bf))
|
||||||
- update Button to v2 design ([863521f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/863521fb3bb268131bd3f369064ae10a442b4fbc))
|
- update Button to v2 design ([863521f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/863521fb3bb268131bd3f369064ae10a442b4fbc))
|
||||||
- update dialog component to headlessui ([#159](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/159)) ([442be8c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/442be8cbee06ec403467b100d8d5300dd4a7ea72))
|
- update dialog component to headlessui ([#159](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/159)) ([442be8c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/442be8cbee06ec403467b100d8d5300dd4a7ea72))
|
||||||
|
|||||||
17
gulpfile.js
17
gulpfile.js
@@ -1,18 +1,31 @@
|
|||||||
// (Thanks go to https://github.com/pnd280/complexity/blob/alpha/gulpfile.js)
|
// (Thanks go to https://github.com/pnd280/complexity/blob/alpha/gulpfile.js)
|
||||||
|
|
||||||
|
import cp from 'child_process';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import gulp from 'gulp';
|
import gulp from 'gulp';
|
||||||
import gulpZip from 'gulp-zip';
|
import gulpZip from 'gulp-zip';
|
||||||
import { createRequire } from 'module';
|
import { createRequire } from 'module';
|
||||||
|
|
||||||
function zip() {
|
// Make sure sentry is configured https://docs.sentry.io/platforms/javascript/sourcemaps/uploading/typescript/#2-configure-sentry-cli
|
||||||
|
function instrumentWithSentry() {
|
||||||
|
return cp.exec('sentry-cli sourcemaps inject dist/ && sentry-cli sourcemaps upload dist/');
|
||||||
|
}
|
||||||
|
|
||||||
|
function zipDist() {
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
const manifest = require('./package.json');
|
const manifest = require('./package.json');
|
||||||
const zipFileName = `${manifest.name.replaceAll(' ', '-')}-${manifest.version}.zip`;
|
const zipFileName = `${manifest.name.replaceAll(' ', '-')}-${manifest.version}.zip`;
|
||||||
|
|
||||||
return gulp.src('dist/**').pipe(gulpZip(zipFileName)).pipe(gulp.dest('package'));
|
return gulp
|
||||||
|
.src('dist/**', {
|
||||||
|
encoding: false,
|
||||||
|
})
|
||||||
|
.pipe(gulpZip(zipFileName))
|
||||||
|
.pipe(gulp.dest('package'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const zip = gulp.series(instrumentWithSentry, zipDist);
|
||||||
|
|
||||||
// Temp fix for CSP on Chrome 130
|
// Temp fix for CSP on Chrome 130
|
||||||
// Manually remove them because there is no option to disable use_dynamic_url on @crxjs/vite-plugin
|
// Manually remove them because there is no option to disable use_dynamic_url on @crxjs/vite-plugin
|
||||||
function forceDisableUseDynamicUrl(done) {
|
function forceDisableUseDynamicUrl(done) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ut-registration-plus",
|
"name": "ut-registration-plus",
|
||||||
"displayName": "UT Registration Plus",
|
"displayName": "UT Registration Plus",
|
||||||
"version": "2.0.0",
|
"version": "2.0.2",
|
||||||
"description": "UT Registration Plus is a Chrome extension that allows students to easily register for classes.",
|
"description": "UT Registration Plus is a Chrome extension that allows students to easily register for classes.",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "https://github.com/Longhorn-Developers/UT-Registration-Plus",
|
"homepage": "https://github.com/Longhorn-Developers/UT-Registration-Plus",
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"build:watch": "NODE_ENV='development' vite build --mode development -w",
|
"build:watch": "NODE_ENV='development' vite build --mode development -w",
|
||||||
"zip": "pnpm build && pnpm gulp zip",
|
"zip": "PROD=true pnpm build && pnpm gulp zip",
|
||||||
"prettier": "prettier src --check",
|
"prettier": "prettier src --check",
|
||||||
"prettier:fix": "prettier src --write",
|
"prettier:fix": "prettier src --write",
|
||||||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives",
|
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives",
|
||||||
|
|||||||
Binary file not shown.
@@ -32,7 +32,7 @@ chrome.runtime.onInstalled.addListener(details => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// migration/login logic
|
// migration/login logic
|
||||||
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
|
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo) => {
|
||||||
// console.log(changeInfo);
|
// console.log(changeInfo);
|
||||||
if (changeInfo.url === 'https://utdirect.utexas.edu/apps/registrar/course_schedule/utrp_login/') {
|
if (changeInfo.url === 'https://utdirect.utexas.edu/apps/registrar/course_schedule/utrp_login/') {
|
||||||
function openPopupAction() {
|
function openPopupAction() {
|
||||||
@@ -59,15 +59,11 @@ messageListener.listen();
|
|||||||
UserScheduleStore.listen('schedules', async schedules => {
|
UserScheduleStore.listen('schedules', async schedules => {
|
||||||
const index = await UserScheduleStore.get('activeIndex');
|
const index = await UserScheduleStore.get('activeIndex');
|
||||||
const numCourses = schedules.newValue[index]?.courses?.length;
|
const numCourses = schedules.newValue[index]?.courses?.length;
|
||||||
if (!numCourses) return;
|
updateBadgeText(numCourses || 0);
|
||||||
|
|
||||||
updateBadgeText(numCourses);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
UserScheduleStore.listen('activeIndex', async ({ newValue }) => {
|
UserScheduleStore.listen('activeIndex', async ({ newValue }) => {
|
||||||
const schedules = await UserScheduleStore.get('schedules');
|
const schedules = await UserScheduleStore.get('schedules');
|
||||||
const numCourses = schedules[newValue]?.courses?.length;
|
const numCourses = schedules[newValue]?.courses?.length;
|
||||||
if (!numCourses) return;
|
updateBadgeText(numCourses || 0);
|
||||||
|
|
||||||
updateBadgeText(numCourses);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ const calendarBackgroundHandler: MessageHandler<CalendarBackgroundMessages> = {
|
|||||||
if (openCalendarTabInfo !== undefined) {
|
if (openCalendarTabInfo !== undefined) {
|
||||||
const tabid = openCalendarTabInfo.tab.id;
|
const tabid = openCalendarTabInfo.tab.id;
|
||||||
|
|
||||||
chrome.tabs.update(tabid, { active: true });
|
await chrome.tabs.update(tabid, { active: true });
|
||||||
|
await chrome.windows.update(openCalendarTabInfo.tab.windowId, { focused: true, drawAttention: true });
|
||||||
if (uniqueId !== undefined) await tabs.openCoursePopup({ uniqueId }, tabid);
|
if (uniqueId !== undefined) await tabs.openCoursePopup({ uniqueId }, tabid);
|
||||||
|
|
||||||
sendResponse(openCalendarTabInfo.tab);
|
sendResponse(openCalendarTabInfo.tab);
|
||||||
|
|||||||
@@ -10,15 +10,18 @@ import handleDuplicate from './handleDuplicate';
|
|||||||
*/
|
*/
|
||||||
export default async function duplicateSchedule(scheduleId: string): Promise<string | undefined> {
|
export default async function duplicateSchedule(scheduleId: string): Promise<string | undefined> {
|
||||||
const schedules = await UserScheduleStore.get('schedules');
|
const schedules = await UserScheduleStore.get('schedules');
|
||||||
const schedule = schedules.find(schedule => schedule.id === scheduleId);
|
const scheduleIndex = schedules.findIndex(schedule => schedule.id === scheduleId);
|
||||||
|
|
||||||
if (schedule === undefined) {
|
if (scheduleIndex === -1) {
|
||||||
throw new Error(`Schedule ${scheduleId} does not exist`);
|
throw new Error(`Schedule ${scheduleId} does not exist`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedName = await handleDuplicate(schedule.name);
|
const schedule = schedules[scheduleIndex]!;
|
||||||
|
|
||||||
schedules.push({
|
const copyOfName = `Copy of ${schedule.name}`;
|
||||||
|
const updatedName = await handleDuplicate(copyOfName);
|
||||||
|
|
||||||
|
schedules.splice(scheduleIndex + 1, 0, {
|
||||||
id: generateRandomId(),
|
id: generateRandomId(),
|
||||||
name: updatedName,
|
name: updatedName,
|
||||||
courses: JSON.parse(JSON.stringify(schedule.courses)),
|
courses: JSON.parse(JSON.stringify(schedule.courses)),
|
||||||
|
|||||||
15
src/shared/types/CRXPages.ts
Normal file
15
src/shared/types/CRXPages.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* An object containing the paths to various pages used in the CRX application.
|
||||||
|
*/
|
||||||
|
export const CRX_PAGES = {
|
||||||
|
DEBUG: '/debug.html',
|
||||||
|
CALENDAR: '/calendar.html',
|
||||||
|
OPTIONS: '/options.html',
|
||||||
|
REPORT: '/report.html',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a type that corresponds to the keys of the `CRX_PAGES` object.
|
||||||
|
* This type is used to ensure that only valid page keys are used within the application.
|
||||||
|
*/
|
||||||
|
export type CRX_Page = keyof typeof CRX_PAGES;
|
||||||
@@ -79,6 +79,8 @@ export class Course {
|
|||||||
scrapedAt!: number;
|
scrapedAt!: number;
|
||||||
/** The colors of the course when displayed */
|
/** The colors of the course when displayed */
|
||||||
colors: CourseColors;
|
colors: CourseColors;
|
||||||
|
/** The core curriculum requirements the course satisfies */
|
||||||
|
core: string[];
|
||||||
|
|
||||||
constructor(course: Serialized<Course>) {
|
constructor(course: Serialized<Course>) {
|
||||||
Object.assign(this, course);
|
Object.assign(this, course);
|
||||||
@@ -88,6 +90,7 @@ export class Course {
|
|||||||
this.scrapedAt = Date.now();
|
this.scrapedAt = Date.now();
|
||||||
}
|
}
|
||||||
this.colors = course.colors ? structuredClone(course.colors) : getCourseColors('emerald', 500);
|
this.colors = course.colors ? structuredClone(course.colors) : getCourseColors('emerald', 500);
|
||||||
|
this.core = course.core ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -15,10 +15,9 @@ export const colors = {
|
|||||||
gray: '#9CADB7',
|
gray: '#9CADB7',
|
||||||
offwhite: '#D6D2C4',
|
offwhite: '#D6D2C4',
|
||||||
concrete: '#95A5A6',
|
concrete: '#95A5A6',
|
||||||
red: '#B91C1C', // Not sure if this should be here, but it's used for remove course, and add course is ut-green
|
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
red: '#BF0000',
|
red: '#D10000',
|
||||||
black: '#1A2024',
|
black: '#1A2024',
|
||||||
},
|
},
|
||||||
} as const satisfies Record<string, Record<string, string>>;
|
} as const satisfies Record<string, Record<string, string>>;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import CancelledIcon from '~icons/material-symbols/warning';
|
|||||||
export function StatusIcon(props: SVGProps<SVGSVGElement> & { status: StatusType }): JSX.Element | null {
|
export function StatusIcon(props: SVGProps<SVGSVGElement> & { status: StatusType }): JSX.Element | null {
|
||||||
const { status, ...rest } = props;
|
const { status, ...rest } = props;
|
||||||
|
|
||||||
switch (props.status) {
|
switch (status) {
|
||||||
case Status.WAITLISTED:
|
case Status.WAITLISTED:
|
||||||
return <WaitlistIcon {...rest} />;
|
return <WaitlistIcon {...rest} />;
|
||||||
case Status.CLOSED:
|
case Status.CLOSED:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const BADGE_LIMIT = 10;
|
|||||||
*/
|
*/
|
||||||
export default function updateBadgeText(value: number): void {
|
export default function updateBadgeText(value: number): void {
|
||||||
let badgeText = '';
|
let badgeText = '';
|
||||||
if (value > 0) {
|
if (value >= 0) {
|
||||||
if (value > BADGE_LIMIT) {
|
if (value > BADGE_LIMIT) {
|
||||||
badgeText = `${BADGE_LIMIT}+`;
|
badgeText = `${BADGE_LIMIT}+`;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -16,8 +16,16 @@ export default meta;
|
|||||||
|
|
||||||
type Story = StoryObj<typeof meta>;
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
export const Default: Story = {
|
export const FlagChip: Story = {
|
||||||
args: {
|
args: {
|
||||||
label: 'QR',
|
label: 'QR',
|
||||||
|
variant: 'flag',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CoreChip: Story = {
|
||||||
|
args: {
|
||||||
|
label: 'SB',
|
||||||
|
variant: 'core',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export const ExampleCourse: Course = new Course({
|
|||||||
'Taught as a Web-based course.',
|
'Taught as a Web-based course.',
|
||||||
],
|
],
|
||||||
flags: ['Quantitative Reasoning'],
|
flags: ['Quantitative Reasoning'],
|
||||||
|
core: ['Natural Science and Technology, Part I'],
|
||||||
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
||||||
instructionMode: 'Online',
|
instructionMode: 'Online',
|
||||||
instructors: [
|
instructors: [
|
||||||
@@ -60,6 +61,7 @@ export const ExampleCourse2: Course = new Course({
|
|||||||
'May be counted toward the Independent Inquiry flag requirement.',
|
'May be counted toward the Independent Inquiry flag requirement.',
|
||||||
],
|
],
|
||||||
flags: ['Independent Inquiry'],
|
flags: ['Independent Inquiry'],
|
||||||
|
core: ['Natural Science and Technology, Part II'],
|
||||||
fullName: 'C S 439 PRINCIPLES OF COMPUTER SYSTEMS',
|
fullName: 'C S 439 PRINCIPLES OF COMPUTER SYSTEMS',
|
||||||
instructionMode: 'In Person',
|
instructionMode: 'In Person',
|
||||||
instructors: [
|
instructors: [
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ const generateCourses = (count: number): Course[] => {
|
|||||||
'Taught as a Web-based course.',
|
'Taught as a Web-based course.',
|
||||||
],
|
],
|
||||||
flags: ['Quantitative Reasoning'],
|
flags: ['Quantitative Reasoning'],
|
||||||
|
core: ['Natural Science and Technology, Part I'],
|
||||||
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
||||||
instructionMode: 'Online',
|
instructionMode: 'Online',
|
||||||
instructors: [
|
instructors: [
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export const ExampleCourse: Course = new Course({
|
|||||||
'Taught as a Web-based course.',
|
'Taught as a Web-based course.',
|
||||||
],
|
],
|
||||||
flags: ['Quantitative Reasoning'],
|
flags: ['Quantitative Reasoning'],
|
||||||
|
core: ['Natural Science and Technology, Part I'],
|
||||||
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
||||||
instructionMode: 'Online',
|
instructionMode: 'Online',
|
||||||
instructors: [
|
instructors: [
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const exampleGovCourse: Course = new Course({
|
|||||||
department: 'GOV',
|
department: 'GOV',
|
||||||
description: ['nah', 'aint typing this', 'corndog'],
|
description: ['nah', 'aint typing this', 'corndog'],
|
||||||
flags: ['no flag for you >:)'],
|
flags: ['no flag for you >:)'],
|
||||||
|
core: ['American and Texas Government'],
|
||||||
fullName: 'GOV 312L Something something',
|
fullName: 'GOV 312L Something something',
|
||||||
instructionMode: 'Online',
|
instructionMode: 'Online',
|
||||||
instructors: [
|
instructors: [
|
||||||
@@ -43,6 +44,7 @@ const examplePsyCourse: Course = new Course({
|
|||||||
department: 'PSY',
|
department: 'PSY',
|
||||||
description: ['nah', 'aint typing this', 'corndog'],
|
description: ['nah', 'aint typing this', 'corndog'],
|
||||||
flags: ['no flag for you >:)'],
|
flags: ['no flag for you >:)'],
|
||||||
|
core: ['Social and Behavioral Sciences'],
|
||||||
fullName: 'PSY 317L Yada yada',
|
fullName: 'PSY 317L Yada yada',
|
||||||
instructionMode: 'Online',
|
instructionMode: 'Online',
|
||||||
scrapedAt: Date.now(),
|
scrapedAt: Date.now(),
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export const exampleCourse: Course = new Course({
|
|||||||
'Taught as a Web-based course.',
|
'Taught as a Web-based course.',
|
||||||
],
|
],
|
||||||
flags: ['Quantitative Reasoning'],
|
flags: ['Quantitative Reasoning'],
|
||||||
|
core: ['Natural Science and Technology, Part I'],
|
||||||
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
||||||
instructionMode: 'Online',
|
instructionMode: 'Online',
|
||||||
scrapedAt: Date.now(),
|
scrapedAt: Date.now(),
|
||||||
@@ -99,6 +100,7 @@ export const bevoCourse: Course = new Course({
|
|||||||
},
|
},
|
||||||
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
|
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
|
||||||
flags: ['Independent Inquiry', 'Writing'],
|
flags: ['Independent Inquiry', 'Writing'],
|
||||||
|
core: ['Humanities'],
|
||||||
instructionMode: 'In Person',
|
instructionMode: 'In Person',
|
||||||
semester: {
|
semester: {
|
||||||
code: '12345',
|
code: '12345',
|
||||||
@@ -154,6 +156,7 @@ export const mikeScottCS314Course: Course = new Course({
|
|||||||
},
|
},
|
||||||
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/50825/',
|
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/50825/',
|
||||||
flags: ['Writing', 'Independent Inquiry'],
|
flags: ['Writing', 'Independent Inquiry'],
|
||||||
|
core: ['Natural Science and Technology, Part II'],
|
||||||
instructionMode: 'In Person',
|
instructionMode: 'In Person',
|
||||||
semester: {
|
semester: {
|
||||||
code: '12345',
|
code: '12345',
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { openReportWindow } from '@shared/util/openReportWindow';
|
|||||||
import Divider from '@views/components/common/Divider';
|
import Divider from '@views/components/common/Divider';
|
||||||
import List from '@views/components/common/List';
|
import List from '@views/components/common/List';
|
||||||
import Text from '@views/components/common/Text/Text';
|
import Text from '@views/components/common/Text/Text';
|
||||||
|
import { useEnforceScheduleLimit } from '@views/hooks/useEnforceScheduleLimit';
|
||||||
import useSchedules, { getActiveSchedule, replaceSchedule, switchSchedule } from '@views/hooks/useSchedules';
|
import useSchedules, { getActiveSchedule, replaceSchedule, switchSchedule } from '@views/hooks/useSchedules';
|
||||||
import { getUpdatedAtDateTimeString } from '@views/lib/getUpdatedAtDateTimeString';
|
import { getUpdatedAtDateTimeString } from '@views/lib/getUpdatedAtDateTimeString';
|
||||||
import useKC_DABR_WASM from 'kc-dabr-wasm';
|
import useKC_DABR_WASM from 'kc-dabr-wasm';
|
||||||
@@ -59,9 +60,16 @@ export default function PopupMain(): JSX.Element {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [activeSchedule, schedules] = useSchedules();
|
const [activeSchedule, schedules] = useSchedules();
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
// const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
const [funny, setFunny] = useState<string>('');
|
const [funny, setFunny] = useState<string>('');
|
||||||
|
|
||||||
|
const enforceScheduleLimit = useEnforceScheduleLimit();
|
||||||
|
const handleAddSchedule = () => {
|
||||||
|
if (enforceScheduleLimit()) {
|
||||||
|
createSchedule('New Schedule');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const randomIndex = Math.floor(Math.random() * splashText.length);
|
const randomIndex = Math.floor(Math.random() * splashText.length);
|
||||||
setFunny(
|
setFunny(
|
||||||
@@ -128,7 +136,7 @@ export default function PopupMain(): JSX.Element {
|
|||||||
variant='filled'
|
variant='filled'
|
||||||
color='ut-burntorange'
|
color='ut-burntorange'
|
||||||
className='h-fit p-0 btn'
|
className='h-fit p-0 btn'
|
||||||
onClick={() => createSchedule('New Schedule')}
|
onClick={handleAddSchedule}
|
||||||
>
|
>
|
||||||
<AddSchedule className='h-6 w-6' />
|
<AddSchedule className='h-6 w-6' />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -81,29 +81,33 @@ export default function ReportIssueMain(): JSX.Element {
|
|||||||
<label htmlFor='email' className='mb-1 block text-sm text-ut-black font-medium'>
|
<label htmlFor='email' className='mb-1 block text-sm text-ut-black font-medium'>
|
||||||
Your @utexas.edu email
|
Your @utexas.edu email
|
||||||
</label>
|
</label>
|
||||||
<input
|
<div className='flex'>
|
||||||
type='email'
|
<input
|
||||||
id='email'
|
type='email'
|
||||||
value={email}
|
id='email'
|
||||||
onChange={e => setEmail(e.target.value)}
|
value={email}
|
||||||
className='w-full border border-gray-300 rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-500'
|
onChange={e => setEmail(e.target.value)}
|
||||||
placeholder='bevo@utexas.edu'
|
className='w-full border border-gray-300 rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-500'
|
||||||
required
|
placeholder='bevo@utexas.edu'
|
||||||
/>
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mb-4'>
|
<div className='mb-4'>
|
||||||
<label htmlFor='feedback' className='mb-1 block text-sm text-ut-black font-medium'>
|
<label htmlFor='feedback' className='mb-1 block text-sm text-ut-black font-medium'>
|
||||||
Your feedback
|
Your feedback
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<div className='flex'>
|
||||||
id='feedback'
|
<textarea
|
||||||
value={feedback}
|
id='feedback'
|
||||||
onChange={e => setFeedback(e.target.value)}
|
value={feedback}
|
||||||
className='h-24 w-full resize-none border border-gray-300 rounded-md px-3 py-2 text-sm font-sans focus:outline-none focus:ring-2 focus:ring-orange-500'
|
onChange={e => setFeedback(e.target.value)}
|
||||||
placeholder='I wish UT Registration Plus could...'
|
className='h-24 w-full resize-none border border-gray-300 rounded-md px-3 py-2 text-sm font-sans focus:outline-none focus:ring-2 focus:ring-orange-500'
|
||||||
required
|
placeholder='I wish UT Registration Plus could...'
|
||||||
/>
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default function CalendarBottomBar({ courseCells, setCourse }: CalendarBo
|
|||||||
{displayCourses && (
|
{displayCourses && (
|
||||||
<>
|
<>
|
||||||
<Text variant='p' className='text-ut-black'>
|
<Text variant='p' className='text-ut-black'>
|
||||||
ASYNC./OTHER
|
ASYNC / OTHER
|
||||||
</Text>
|
</Text>
|
||||||
<Text variant='h4' className='text-gray-300'>
|
<Text variant='h4' className='text-gray-300'>
|
||||||
—
|
—
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export default function CalendarCourseCell({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'h-full w-0 flex justify-center rounded p-x-2 p-y-1.2 cursor-pointer screenshot:p-1.5 hover:shadow-md transition-shadow-100 ease-out',
|
'h-full w-0 flex justify-center rounded p-x-2 p-y-1.2 cursor-pointer hover:shadow-md transition-shadow-100 ease-out',
|
||||||
{
|
{
|
||||||
'min-w-full': timeAndLocation,
|
'min-w-full': timeAndLocation,
|
||||||
'w-full': !timeAndLocation,
|
'w-full': !timeAndLocation,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React from 'react';
|
|||||||
import DiscordIcon from '~icons/bi/discord';
|
import DiscordIcon from '~icons/bi/discord';
|
||||||
import GithubIcon from '~icons/ri/github-fill';
|
import GithubIcon from '~icons/ri/github-fill';
|
||||||
import InstagramIcon from '~icons/ri/instagram-line';
|
import InstagramIcon from '~icons/ri/instagram-line';
|
||||||
|
import LinkedinIcon from '~icons/ri/linkedin-box-fill';
|
||||||
|
|
||||||
import Link from '../common/Link';
|
import Link from '../common/Link';
|
||||||
|
|
||||||
@@ -23,6 +24,12 @@ export default function CalendarFooter(): JSX.Element {
|
|||||||
<Link className='linkanimate' href='https://github.com/Longhorn-Developers'>
|
<Link className='linkanimate' href='https://github.com/Longhorn-Developers'>
|
||||||
<GithubIcon className='h-6 w-6' />
|
<GithubIcon className='h-6 w-6' />
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
className='linkanimate'
|
||||||
|
href='https://www.linkedin.com/company/longhorn-developers/posts/?feedView=all'
|
||||||
|
>
|
||||||
|
<LinkedinIcon className='h-6 w-6 -mx-0.75' />
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<p className='text-2.5 text-ut-concrete font-light tracking-wide'>
|
<p className='text-2.5 text-ut-concrete font-light tracking-wide'>
|
||||||
UT Registration Plus is a project under Longhorn Developers, a student-led organization aimed at
|
UT Registration Plus is a project under Longhorn Developers, a student-led organization aimed at
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ function makeGridRow(row: number, cols: number): JSX.Element {
|
|||||||
*/
|
*/
|
||||||
export default function CalendarGrid({
|
export default function CalendarGrid({
|
||||||
courseCells,
|
courseCells,
|
||||||
saturdayClass, // TODO: implement/move away from props
|
saturdayClass: _saturdayClass, // TODO: implement/move away from props
|
||||||
setCourse,
|
setCourse,
|
||||||
}: React.PropsWithChildren<Props>): JSX.Element {
|
}: React.PropsWithChildren<Props>): JSX.Element {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,13 +4,12 @@ import { Button } from '@views/components/common/Button';
|
|||||||
import List from '@views/components/common/List';
|
import List from '@views/components/common/List';
|
||||||
import ScheduleListItem from '@views/components/common/ScheduleListItem';
|
import ScheduleListItem from '@views/components/common/ScheduleListItem';
|
||||||
import Text from '@views/components/common/Text/Text';
|
import Text from '@views/components/common/Text/Text';
|
||||||
|
import { useEnforceScheduleLimit } from '@views/hooks/useEnforceScheduleLimit';
|
||||||
import useSchedules, { getActiveSchedule, switchSchedule } from '@views/hooks/useSchedules';
|
import useSchedules, { getActiveSchedule, switchSchedule } from '@views/hooks/useSchedules';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import AddSchedule from '~icons/material-symbols/add';
|
import AddSchedule from '~icons/material-symbols/add';
|
||||||
|
|
||||||
import { usePrompt } from '../common/DialogProvider/DialogProvider';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a component that displays a list of schedules.
|
* Renders a component that displays a list of schedules.
|
||||||
*
|
*
|
||||||
@@ -19,32 +18,12 @@ import { usePrompt } from '../common/DialogProvider/DialogProvider';
|
|||||||
*/
|
*/
|
||||||
export function CalendarSchedules() {
|
export function CalendarSchedules() {
|
||||||
const [, schedules] = useSchedules();
|
const [, schedules] = useSchedules();
|
||||||
const showDialog = usePrompt();
|
|
||||||
|
|
||||||
|
const enforceScheduleLimit = useEnforceScheduleLimit();
|
||||||
const handleAddSchedule = () => {
|
const handleAddSchedule = () => {
|
||||||
if (schedules.length >= 10) {
|
if (enforceScheduleLimit()) {
|
||||||
showDialog({
|
createSchedule('New Schedule');
|
||||||
title: `You have 10 active schedules!`,
|
|
||||||
|
|
||||||
description: (
|
|
||||||
<>
|
|
||||||
To encourage organization,{' '}
|
|
||||||
<span className='text-ut-burntorange'>please consider removing some unused schedules</span> you
|
|
||||||
may have.
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
// eslint-disable-next-line react/no-unstable-nested-components
|
|
||||||
buttons: close => (
|
|
||||||
<Button variant='filled' color='ut-burntorange' onClick={close}>
|
|
||||||
I Understand
|
|
||||||
</Button>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createSchedule('New Schedule');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -19,29 +19,29 @@ const links: LinkItem[] = [
|
|||||||
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20252/',
|
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20252/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "Fall '24 Course Schedule",
|
text: 'Course Schedule Archives',
|
||||||
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20249/',
|
url: 'https://registrar.utexas.edu/schedules/archive',
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// text: "Summer '24 Course Schedule",
|
// text: "Summer '24 Course Schedule",
|
||||||
// url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20246/',
|
// url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20246/',
|
||||||
// },
|
// },
|
||||||
|
{
|
||||||
|
text: "'24-'25 Academic Calendar",
|
||||||
|
url: 'https://registrar.utexas.edu/calendars/24-25',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: 'Registration Info Sheet',
|
text: 'Registration Info Sheet',
|
||||||
url: 'https://utdirect.utexas.edu/registrar/ris.WBX',
|
url: 'https://utdirect.utexas.edu/registrar/ris.WBX',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Register For Courses',
|
text: 'Register for Courses',
|
||||||
url: 'https://utdirect.utexas.edu/registration/chooseSemester.WBX',
|
url: 'https://utdirect.utexas.edu/registration/chooseSemester.WBX',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Degree Audit',
|
text: 'Degree Audit',
|
||||||
url: 'https://utdirect.utexas.edu/apps/degree/audits/',
|
url: 'https://utdirect.utexas.edu/apps/degree/audits/',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: 'My Registered Courses',
|
|
||||||
url: 'https://utdirect.utexas.edu/registration/classlist.WBX',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { CRX_PAGES } from '@shared/types/CRXPages';
|
||||||
|
import { openReportWindow } from '@shared/util/openReportWindow';
|
||||||
import Text from '@views/components/common/Text/Text';
|
import Text from '@views/components/common/Text/Text';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@@ -13,26 +15,41 @@ interface LinkItem {
|
|||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const links: LinkItem[] = [
|
const links = [
|
||||||
// {
|
{
|
||||||
// text: 'Feedback Form',
|
text: 'Rate us on Chrome Web Store',
|
||||||
// url: '#',
|
url: 'https://chromewebstore.google.com/detail/ut-registration-plus/hboadpjkoaieogjimneceaahlppnipaa',
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// text: 'Apply to Longhorn Developers',
|
text: 'Send us Feedback & Ideas',
|
||||||
// url: '#',
|
url: CRX_PAGES.REPORT,
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
text: 'Become a Beta Tester',
|
text: 'Become a Beta Tester',
|
||||||
url: 'https://forms.gle/Y9dmQAb1yzW5PRg48',
|
url: 'https://forms.gle/Y9dmQAb1yzW5PRg48',
|
||||||
},
|
},
|
||||||
];
|
{
|
||||||
|
text: 'Credits – Meet the team',
|
||||||
|
url: CRX_PAGES.OPTIONS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Apply to Longhorn Developers',
|
||||||
|
url: 'https://forms.gle/cdkLKmFwPmvHmiBe9',
|
||||||
|
},
|
||||||
|
] as const satisfies LinkItem[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The "From The Team" section of the calendar website
|
* The "From The Team" section of the calendar website
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export default function TeamLinks({ className }: Props): JSX.Element {
|
export default function TeamLinks({ className }: Props): JSX.Element {
|
||||||
|
const handleClick = (link: LinkItem, event: React.MouseEvent) => {
|
||||||
|
if (link.url === CRX_PAGES.REPORT) {
|
||||||
|
event.preventDefault();
|
||||||
|
openReportWindow();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article className={clsx(className, 'flex flex-col gap-2')}>
|
<article className={clsx(className, 'flex flex-col gap-2')}>
|
||||||
<Text variant='h3'>From the Team</Text>
|
<Text variant='h3'>From the Team</Text>
|
||||||
@@ -43,6 +60,7 @@ export default function TeamLinks({ className }: Props): JSX.Element {
|
|||||||
className='flex items-center gap-0.5 text-ut-burntorange underline-offset-2 hover:underline'
|
className='flex items-center gap-0.5 text-ut-burntorange underline-offset-2 hover:underline'
|
||||||
target='_blank'
|
target='_blank'
|
||||||
rel='noreferrer'
|
rel='noreferrer'
|
||||||
|
onClick={event => handleClick(link, event)}
|
||||||
>
|
>
|
||||||
<Text variant='p'>{link.text}</Text>
|
<Text variant='p'>{link.text}</Text>
|
||||||
<OutwardArrowIcon className='h-3 w-3' />
|
<OutwardArrowIcon className='h-3 w-3' />
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import Text from '@views/components/common/Text/Text';
|
import Text from '@views/components/common/Text/Text';
|
||||||
|
import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -14,26 +15,60 @@ export const flagMap = {
|
|||||||
'Independent Inquiry': 'II',
|
'Independent Inquiry': 'II',
|
||||||
} as const satisfies Record<string, Flag>;
|
} as const satisfies Record<string, Flag>;
|
||||||
|
|
||||||
interface Props {
|
/**
|
||||||
label: Flag;
|
* A type that represents the core curriculum aspects that a course can satisfy.
|
||||||
}
|
*/
|
||||||
|
export type Core = 'ID' | 'C1' | 'HU' | 'GO' | 'HI' | 'SB' | 'MA' | 'N1' | 'N2' | 'VP';
|
||||||
|
export const coreMap = {
|
||||||
|
'First-Year Signature Course': 'ID',
|
||||||
|
'English Composition': 'C1',
|
||||||
|
Humanities: 'HU',
|
||||||
|
'American and Texas Government': 'GO',
|
||||||
|
'U.S. History': 'HI',
|
||||||
|
'Social and Behavioral Sciences': 'SB',
|
||||||
|
'Natural Science and Technology, Part I': 'N1',
|
||||||
|
'Natural Science and Technology, Part II': 'N2',
|
||||||
|
Mathematics: 'MA',
|
||||||
|
'Visual and Performing Arts': 'VP',
|
||||||
|
} as const satisfies Record<string, Core>;
|
||||||
|
|
||||||
|
type Props =
|
||||||
|
| {
|
||||||
|
variant: 'core';
|
||||||
|
label: Core;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
variant: 'flag';
|
||||||
|
label: Flag;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A reusable chip component that follows the design system of the extension.
|
* A reusable chip component that follows the design system of the extension.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function Chip({ label }: React.PropsWithChildren<Props>): JSX.Element {
|
export function Chip({ variant, label }: React.PropsWithChildren<Props>): JSX.Element {
|
||||||
const longFlagName = Object.entries(flagMap).find(([full, short]) => short === label)?.[0] ?? label;
|
let labelMap;
|
||||||
|
switch (variant) {
|
||||||
|
case 'core':
|
||||||
|
labelMap = coreMap;
|
||||||
|
break;
|
||||||
|
case 'flag':
|
||||||
|
labelMap = flagMap;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
labelMap = {};
|
||||||
|
}
|
||||||
|
const longName = Object.entries(labelMap).find(([_full, short]) => short === label)?.[0] ?? label;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
as='div'
|
as='div'
|
||||||
variant='h4'
|
variant='h4'
|
||||||
className='min-w-5 inline-flex items-center justify-center gap-2.5 rounded-lg px-1.5 py-0.5'
|
className={clsx('min-w-5 inline-flex items-center justify-center gap-2.5 rounded-lg px-1.5 py-0.5', {
|
||||||
style={{
|
'bg-ut-yellow text-black': variant === 'flag',
|
||||||
backgroundColor: '#FFD600',
|
'bg-ut-blue text-white': variant === 'core',
|
||||||
}}
|
})}
|
||||||
title={`${longFlagName} flag`}
|
title={variant === 'flag' ? `${longName} flag` : `${longName} core curriculum requirement`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ type Props = TextProps<'a'> & {
|
|||||||
* A reusable Text component with props that build on top of the design system for the extension
|
* A reusable Text component with props that build on top of the design system for the extension
|
||||||
*/
|
*/
|
||||||
export default function Link(props: PropsWithChildren<Props>): JSX.Element {
|
export default function Link(props: PropsWithChildren<Props>): JSX.Element {
|
||||||
let { className, href, ...passedProps } = props;
|
const { className, href, ...passedProps } = props;
|
||||||
|
|
||||||
if (href && !props.onClick) {
|
if (href && !props.onClick) {
|
||||||
passedProps.onClick = e => {
|
passedProps.onClick = e => {
|
||||||
@@ -37,7 +37,7 @@ export default function Link(props: PropsWithChildren<Props>): JSX.Element {
|
|||||||
'underline cursor-pointer': !isDisabled,
|
'underline cursor-pointer': !isDisabled,
|
||||||
'cursor-not-allowed color-ut-gray': isDisabled,
|
'cursor-not-allowed color-ut-gray': isDisabled,
|
||||||
},
|
},
|
||||||
props.className
|
className
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ function MigrationButtons({ close }: { close: () => void }): JSX.Element {
|
|||||||
}
|
}
|
||||||
setProcessState(2);
|
setProcessState(2);
|
||||||
close();
|
close();
|
||||||
} else {
|
} else if (processState === 0) {
|
||||||
const { pendingMigration } = await chrome.storage.session.get('pendingMigration');
|
const { pendingMigration } = await chrome.storage.session.get('pendingMigration');
|
||||||
if (pendingMigration) setProcessState(1);
|
if (pendingMigration) setProcessState(1);
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ function MigrationButtons({ close }: { close: () => void }): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{error && (
|
{error && (
|
||||||
<Text variant='p' className='text-ut-red'>
|
<Text variant='p' className='text-theme-red'>
|
||||||
An error occurred while migrating your courses. Please try again later in settings. (
|
An error occurred while migrating your courses. Please try again later in settings. (
|
||||||
{error.substring(0, 8)})
|
{error.substring(0, 8)})
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -78,8 +78,9 @@ export default function PopupCourseBlock({
|
|||||||
<DragIndicatorIcon className='h-6 w-6 text-white' />
|
<DragIndicatorIcon className='h-6 w-6 text-white' />
|
||||||
</div>
|
</div>
|
||||||
<Text className={clsx('flex-1 py-3.5 truncate', fontColor)} variant='h1-course'>
|
<Text className={clsx('flex-1 py-3.5 truncate', fontColor)} variant='h1-course'>
|
||||||
<span className='px-0.5 font-450'>{formattedUniqueId}</span> {course.department} {course.number} –{' '}
|
<span className='px-0.5 font-450'>{formattedUniqueId}</span> {course.department} {course.number}
|
||||||
{course.instructors.length === 0 ? 'Unknown' : course.instructors.map(v => v.lastName)}
|
{course.instructors.length > 0 ? <> – </> : ''}
|
||||||
|
{course.instructors.map(v => v.toString({ format: 'last', case: 'capitalize' })).join('; ')}
|
||||||
</Text>
|
</Text>
|
||||||
{enableCourseStatusChips && course.status !== Status.OPEN && (
|
{enableCourseStatusChips && course.status !== Status.OPEN && (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import duplicateSchedule from '@pages/background/lib/duplicateSchedule';
|
|||||||
import renameSchedule from '@pages/background/lib/renameSchedule';
|
import renameSchedule from '@pages/background/lib/renameSchedule';
|
||||||
import type { UserSchedule } from '@shared/types/UserSchedule';
|
import type { UserSchedule } from '@shared/types/UserSchedule';
|
||||||
import Text from '@views/components/common/Text/Text';
|
import Text from '@views/components/common/Text/Text';
|
||||||
|
import { useEnforceScheduleLimit } from '@views/hooks/useEnforceScheduleLimit';
|
||||||
import useSchedules from '@views/hooks/useSchedules';
|
import useSchedules from '@views/hooks/useSchedules';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
@@ -34,6 +35,12 @@ export default function ScheduleListItem({ schedule, dragHandleProps, onClick }:
|
|||||||
const [editorValue, setEditorValue] = useState(schedule.name);
|
const [editorValue, setEditorValue] = useState(schedule.name);
|
||||||
|
|
||||||
const showDialog = usePrompt();
|
const showDialog = usePrompt();
|
||||||
|
const enforceScheduleLimit = useEnforceScheduleLimit();
|
||||||
|
const handleDuplicateSchedule = (scheduleId: string) => {
|
||||||
|
if (enforceScheduleLimit()) {
|
||||||
|
duplicateSchedule(scheduleId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const editorRef = React.useRef<HTMLInputElement>(null);
|
const editorRef = React.useRef<HTMLInputElement>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -92,7 +99,7 @@ export default function ScheduleListItem({ schedule, dragHandleProps, onClick }:
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant='filled'
|
variant='filled'
|
||||||
color='ut-red'
|
color='theme-red'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
close();
|
close();
|
||||||
deleteSchedule(schedule.id);
|
deleteSchedule(schedule.id);
|
||||||
@@ -180,7 +187,7 @@ export default function ScheduleListItem({ schedule, dragHandleProps, onClick }:
|
|||||||
<Text
|
<Text
|
||||||
as='button'
|
as='button'
|
||||||
variant='small'
|
variant='small'
|
||||||
onClick={() => duplicateSchedule(schedule.id)}
|
onClick={() => handleDuplicateSchedule(schedule.id)}
|
||||||
className='w-full rounded bg-transparent p-2 text-left data-[focus]:bg-gray-200/40'
|
className='w-full rounded bg-transparent p-2 text-left data-[focus]:bg-gray-200/40'
|
||||||
>
|
>
|
||||||
Duplicate
|
Duplicate
|
||||||
@@ -191,7 +198,7 @@ export default function ScheduleListItem({ schedule, dragHandleProps, onClick }:
|
|||||||
as='button'
|
as='button'
|
||||||
variant='small'
|
variant='small'
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
className='w-full rounded bg-transparent p-2 text-left text-ut-red data-[focus]:bg-red-200/40'
|
className='w-full rounded bg-transparent p-2 text-left text-theme-red data-[focus]:bg-red-200/40'
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default function ScheduleTotalHoursAndCourses({
|
|||||||
</Text>
|
</Text>
|
||||||
<Text variant='h3' as='div' className='flex flex-row items-center gap-2 text-theme-black'>
|
<Text variant='h3' as='div' className='flex flex-row items-center gap-2 text-theme-black'>
|
||||||
{totalHours} {totalHours === 1 ? 'Hour' : 'Hours'}
|
{totalHours} {totalHours === 1 ? 'Hour' : 'Hours'}
|
||||||
<Text variant='h4' as='span' className='hidden text-ut-black capitalize screenshot:inline sm:inline'>
|
<Text variant='h4' as='span' className='hidden capitalize screenshot:inline sm:inline'>
|
||||||
{totalCourses} {totalCourses === 1 ? 'Course' : 'Courses'}
|
{totalCourses} {totalCourses === 1 ? 'Course' : 'Courses'}
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
|
|
||||||
.h1-course {
|
.h1-course {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 600;
|
font-weight: 550;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
letter-spacing: 0;
|
letter-spacing: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,8 +211,11 @@ export default function GradeDistribution({ course }: GradeDistributionProps): J
|
|||||||
{status === DataStatus.FOUND && (
|
{status === DataStatus.FOUND && (
|
||||||
<>
|
<>
|
||||||
<div className='flex flex-wrap content-center items-center self-stretch justify-center gap-3'>
|
<div className='flex flex-wrap content-center items-center self-stretch justify-center gap-3'>
|
||||||
<Text variant='small'>
|
<Text variant='small' className='text-ut-black'>
|
||||||
Grade Distribution for {course.department} {course.number}
|
Grade Distribution for{' '}
|
||||||
|
<Text variant='small' className='font-extrabold!' as='strong'>
|
||||||
|
{course.department} {course.number}
|
||||||
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
<select
|
<select
|
||||||
className='border border rounded border-solid px-3 py-2'
|
className='border border rounded border-solid px-3 py-2'
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { Course } from '@shared/types/Course';
|
|||||||
import type Instructor from '@shared/types/Instructor';
|
import type Instructor from '@shared/types/Instructor';
|
||||||
import type { UserSchedule } from '@shared/types/UserSchedule';
|
import type { UserSchedule } from '@shared/types/UserSchedule';
|
||||||
import { Button } from '@views/components/common/Button';
|
import { Button } from '@views/components/common/Button';
|
||||||
import { Chip, flagMap } from '@views/components/common/Chip';
|
import { Chip, coreMap, flagMap } from '@views/components/common/Chip';
|
||||||
import Divider from '@views/components/common/Divider';
|
import Divider from '@views/components/common/Divider';
|
||||||
import Link from '@views/components/common/Link';
|
import Link from '@views/components/common/Link';
|
||||||
import Text from '@views/components/common/Text/Text';
|
import Text from '@views/components/common/Text/Text';
|
||||||
@@ -49,16 +49,13 @@ const capitalizeString = (str: string) => str.charAt(0).toUpperCase() + str.slic
|
|||||||
* @returns {JSX.Element} The rendered component.
|
* @returns {JSX.Element} The rendered component.
|
||||||
*/
|
*/
|
||||||
export default function HeadingAndActions({ course, activeSchedule, onClose }: HeadingAndActionProps): JSX.Element {
|
export default function HeadingAndActions({ course, activeSchedule, onClose }: HeadingAndActionProps): JSX.Element {
|
||||||
const { courseName, department, number: courseNumber, uniqueId, instructors, flags, schedule } = course;
|
const { courseName, department, number: courseNumber, uniqueId, instructors, flags, schedule, core } = course;
|
||||||
const courseAdded = activeSchedule.courses.some(ourCourse => ourCourse.uniqueId === uniqueId);
|
const courseAdded = activeSchedule.courses.some(ourCourse => ourCourse.uniqueId === uniqueId);
|
||||||
const formattedUniqueId = uniqueId.toString().padStart(5, '0');
|
const formattedUniqueId = uniqueId.toString().padStart(5, '0');
|
||||||
const isInCalendar = useCalendar();
|
const isInCalendar = useCalendar();
|
||||||
|
|
||||||
const getInstructorFullName = (instructor: Instructor) => {
|
const getInstructorFullName = (instructor: Instructor) =>
|
||||||
const { firstName = '', lastName = '' } = instructor;
|
instructor.toString({ format: 'first_last', case: 'capitalize' });
|
||||||
if (firstName === '') return capitalizeString(lastName);
|
|
||||||
return `${capitalizeString(firstName)} ${capitalizeString(lastName)}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getBuildingUrl = (building: string) =>
|
const getBuildingUrl = (building: string) =>
|
||||||
`https://utdirect.utexas.edu/apps/campus/buildings/nlogon/maps/UTM/${building}`;
|
`https://utdirect.utexas.edu/apps/campus/buildings/nlogon/maps/UTM/${building}`;
|
||||||
@@ -95,6 +92,12 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
|
|||||||
const url = `https://utdirect.utexas.edu/apps/student/coursedocs/nlogon/?year=&semester=&department=${department}&course_number=${courseNumber}&course_title=&unique=&instructor_first=${firstName}&instructor_last=${lastName}&course_type=In+Residence&search=Search`;
|
const url = `https://utdirect.utexas.edu/apps/student/coursedocs/nlogon/?year=&semester=&department=${department}&course_number=${courseNumber}&course_title=&unique=&instructor_first=${firstName}&instructor_last=${lastName}&course_type=In+Residence&search=Search`;
|
||||||
openNewTab({ url });
|
openNewTab({ url });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show the course's syllabi when no instructors listed
|
||||||
|
if (instructors.length === 0) {
|
||||||
|
const url = `https://utdirect.utexas.edu/apps/student/coursedocs/nlogon/?year=&semester=&department=${department}&course_number=${courseNumber}&course_title=&unique=&instructor_first=&instructor_last=&course_type=In+Residence&search=Search`;
|
||||||
|
openNewTab({ url });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddOrRemoveCourse = async () => {
|
const handleAddOrRemoveCourse = async () => {
|
||||||
@@ -119,13 +122,13 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
|
|||||||
<Button color='ut-burntorange' variant='single' icon={Copy} onClick={handleCopy}>
|
<Button color='ut-burntorange' variant='single' icon={Copy} onClick={handleCopy}>
|
||||||
{formattedUniqueId}
|
{formattedUniqueId}
|
||||||
</Button>
|
</Button>
|
||||||
<button className='bg-transparent p-0 text-theme-black btn' onClick={onClose}>
|
<button className='bg-transparent p-0 text-ut-black btn' onClick={onClose}>
|
||||||
<CloseIcon className='h-7 w-7' />
|
<CloseIcon className='h-7 w-7' />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
{instructors.length > 0 && (
|
{instructors.length > 0 ? (
|
||||||
<Text variant='h4' as='p' className='items-center justify-center'>
|
<Text variant='h4' as='p'>
|
||||||
with{' '}
|
with{' '}
|
||||||
{instructors
|
{instructors
|
||||||
.map(instructor => (
|
.map(instructor => (
|
||||||
@@ -140,12 +143,24 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
|
|||||||
))
|
))
|
||||||
.flatMap((el, i) => (i === 0 ? [el] : [', ', el]))}
|
.flatMap((el, i) => (i === 0 ? [el] : [', ', el]))}
|
||||||
</Text>
|
</Text>
|
||||||
|
) : (
|
||||||
|
<Text variant='h4' as='p'>
|
||||||
|
(No instructor has been provided)
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
<div className='flex items-center gap-1'>
|
<div className='flex items-center gap-1'>
|
||||||
{flags.map((flag: string) => (
|
{flags.map((flag: string) => (
|
||||||
<Chip
|
<Chip
|
||||||
key={flagMap[flag as keyof typeof flagMap]}
|
key={flagMap[flag as keyof typeof flagMap]}
|
||||||
label={flagMap[flag as keyof typeof flagMap]}
|
label={flagMap[flag as keyof typeof flagMap]}
|
||||||
|
variant='flag'
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{core.map((coreVal: string) => (
|
||||||
|
<Chip
|
||||||
|
key={coreMap[coreVal as keyof typeof coreMap]}
|
||||||
|
label={coreMap[coreVal as keyof typeof coreMap]}
|
||||||
|
variant='core'
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -199,10 +214,22 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Divider size='1.75rem' orientation='vertical' />
|
<Divider size='1.75rem' orientation='vertical' />
|
||||||
<Button variant='outline' color='ut-blue' icon={Reviews} onClick={handleOpenRateMyProf}>
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='ut-blue'
|
||||||
|
icon={Reviews}
|
||||||
|
onClick={handleOpenRateMyProf}
|
||||||
|
disabled={instructors.length === 0}
|
||||||
|
>
|
||||||
RateMyProf
|
RateMyProf
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='outline' color='ut-teal' icon={Mood} onClick={handleOpenCES}>
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='ut-teal'
|
||||||
|
icon={Mood}
|
||||||
|
onClick={handleOpenCES}
|
||||||
|
disabled={instructors.length === 0}
|
||||||
|
>
|
||||||
CES
|
CES
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='outline' color='ut-orange' icon={Description} onClick={handleOpenPastSyllabi}>
|
<Button variant='outline' color='ut-orange' icon={Description} onClick={handleOpenPastSyllabi}>
|
||||||
|
|||||||
@@ -79,11 +79,11 @@ const useDevMode = (targetCount: number): [boolean, () => void] => {
|
|||||||
* @returns The Settings component.
|
* @returns The Settings component.
|
||||||
*/
|
*/
|
||||||
export default function Settings(): JSX.Element {
|
export default function Settings(): JSX.Element {
|
||||||
const [enableCourseStatusChips, setEnableCourseStatusChips] = useState<boolean>(false);
|
const [_enableCourseStatusChips, setEnableCourseStatusChips] = useState<boolean>(false);
|
||||||
const [showTimeLocation, setShowTimeLocation] = useState<boolean>(false);
|
const [_showTimeLocation, setShowTimeLocation] = useState<boolean>(false);
|
||||||
const [highlightConflicts, setHighlightConflicts] = useState<boolean>(false);
|
const [highlightConflicts, setHighlightConflicts] = useState<boolean>(false);
|
||||||
const [loadAllCourses, setLoadAllCourses] = useState<boolean>(false);
|
const [loadAllCourses, setLoadAllCourses] = useState<boolean>(false);
|
||||||
const [enableDataRefreshing, setEnableDataRefreshing] = useState<boolean>(false);
|
const [_enableDataRefreshing, setEnableDataRefreshing] = useState<boolean>(false);
|
||||||
|
|
||||||
const showMigrationDialog = useMigrationDialog();
|
const showMigrationDialog = useMigrationDialog();
|
||||||
|
|
||||||
@@ -101,8 +101,12 @@ export default function Settings(): JSX.Element {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchGitHubStats = async () => {
|
const fetchGitHubStats = async () => {
|
||||||
const stats = await gitHubStatsService.fetchGitHubStats();
|
try {
|
||||||
setGitHubStats(stats);
|
const stats = await gitHubStatsService.fetchGitHubStats();
|
||||||
|
setGitHubStats(stats);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Error fetching GitHub stats:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const initAndSetSettings = async () => {
|
const initAndSetSettings = async () => {
|
||||||
@@ -207,29 +211,40 @@ export default function Settings(): JSX.Element {
|
|||||||
// Exit if the user cancels the prompt
|
// Exit if the user cancels the prompt
|
||||||
if (link === null) return;
|
if (link === null) return;
|
||||||
|
|
||||||
const response = await fetch(link);
|
try {
|
||||||
const text = await response.text();
|
let response: Response;
|
||||||
const doc = new DOMParser().parseFromString(text, 'text/html');
|
try {
|
||||||
|
response = await fetch(link);
|
||||||
const scraper = new CourseCatalogScraper(SiteSupport.COURSE_CATALOG_DETAILS, doc, link);
|
} catch (e) {
|
||||||
const tableRows = getCourseTableRows(doc);
|
// eslint-disable-next-line no-alert
|
||||||
const courses = scraper.scrape(tableRows, false);
|
alert(`Failed to fetch url '${link}'`);
|
||||||
|
return;
|
||||||
if (courses.length === 1) {
|
|
||||||
const description = scraper.getDescription(doc);
|
|
||||||
const row = courses[0]!;
|
|
||||||
const course = row.course!;
|
|
||||||
course.description = description;
|
|
||||||
// console.log(course);
|
|
||||||
|
|
||||||
if (activeSchedule.courses.every(c => c.uniqueId !== course.uniqueId)) {
|
|
||||||
console.log('adding course');
|
|
||||||
addCourse(activeSchedule.id, course);
|
|
||||||
} else {
|
|
||||||
console.log('course already exists');
|
|
||||||
}
|
}
|
||||||
} else {
|
const text = await response.text();
|
||||||
console.log(courses);
|
const doc = new DOMParser().parseFromString(text, 'text/html');
|
||||||
|
|
||||||
|
const scraper = new CourseCatalogScraper(SiteSupport.COURSE_CATALOG_DETAILS, doc, link);
|
||||||
|
const tableRows = getCourseTableRows(doc);
|
||||||
|
const courses = scraper.scrape(tableRows, false);
|
||||||
|
|
||||||
|
if (courses.length === 1) {
|
||||||
|
const description = scraper.getDescription(doc);
|
||||||
|
const row = courses[0]!;
|
||||||
|
const course = row.course!;
|
||||||
|
course.description = description;
|
||||||
|
// console.log(course);
|
||||||
|
|
||||||
|
if (activeSchedule.courses.every(c => c.uniqueId !== course.uniqueId)) {
|
||||||
|
console.log('adding course');
|
||||||
|
addCourse(activeSchedule.id, course);
|
||||||
|
} else {
|
||||||
|
console.log('course already exists');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(courses);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error scraping course:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -396,7 +411,7 @@ export default function Settings(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant='outline'
|
variant='outline'
|
||||||
color='ut-red'
|
color='theme-red'
|
||||||
icon={DeleteForeverIcon}
|
icon={DeleteForeverIcon}
|
||||||
onClick={handleEraseAll}
|
onClick={handleEraseAll}
|
||||||
>
|
>
|
||||||
@@ -413,7 +428,7 @@ export default function Settings(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
<Text
|
<Text
|
||||||
variant='h2-course'
|
variant='h2-course'
|
||||||
className={clsx('text-center text-ut-red !font-normal', {
|
className={clsx('text-center text-theme-red !font-normal', {
|
||||||
'line-through': highlightConflicts,
|
'line-through': highlightConflicts,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
@@ -475,7 +490,7 @@ export default function Settings(): JSX.Element {
|
|||||||
<p className='text-xs text-ut-green'>
|
<p className='text-xs text-ut-green'>
|
||||||
{githubStats.adminGitHubStats[admin.githubUsername]?.linesAdded} ++
|
{githubStats.adminGitHubStats[admin.githubUsername]?.linesAdded} ++
|
||||||
</p>
|
</p>
|
||||||
<p className='text-xs text-ut-red'>
|
<p className='text-xs text-theme-red'>
|
||||||
{githubStats.adminGitHubStats[admin.githubUsername]?.linesDeleted} --
|
{githubStats.adminGitHubStats[admin.githubUsername]?.linesDeleted} --
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -485,9 +500,13 @@ export default function Settings(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section className='my-8'>
|
<section className='my-8'>
|
||||||
<h2 className='mb-4 text-xl text-ut-black font-semibold'>UTRP CONTRIBUTERS</h2>
|
<h2 className='mb-4 text-xl text-ut-black font-semibold'>UTRP CONTRIBUTORS</h2>
|
||||||
<div className='grid grid-cols-2 gap-4 2xl:grid-cols-4 md:grid-cols-3 xl:grid-cols-3'>
|
<div className='grid grid-cols-2 gap-4 2xl:grid-cols-4 md:grid-cols-3 xl:grid-cols-3'>
|
||||||
{LONGHORN_DEVELOPERS_SWE.map(swe => (
|
{LONGHORN_DEVELOPERS_SWE.sort(
|
||||||
|
(a, b) =>
|
||||||
|
(githubStats?.userGitHubStats[b.githubUsername]?.commits ?? 0) -
|
||||||
|
(githubStats?.userGitHubStats[a.githubUsername]?.commits ?? 0)
|
||||||
|
).map(swe => (
|
||||||
<div
|
<div
|
||||||
key={swe.githubUsername}
|
key={swe.githubUsername}
|
||||||
className='border border-gray-300 rounded bg-ut-gray/10 p-4'
|
className='border border-gray-300 rounded bg-ut-gray/10 p-4'
|
||||||
@@ -517,7 +536,7 @@ export default function Settings(): JSX.Element {
|
|||||||
<p className='text-xs text-ut-green'>
|
<p className='text-xs text-ut-green'>
|
||||||
{githubStats.userGitHubStats[swe.githubUsername]?.linesAdded} ++
|
{githubStats.userGitHubStats[swe.githubUsername]?.linesAdded} ++
|
||||||
</p>
|
</p>
|
||||||
<p className='text-xs text-ut-red'>
|
<p className='text-xs text-theme-red'>
|
||||||
{githubStats.userGitHubStats[swe.githubUsername]?.linesDeleted} --
|
{githubStats.userGitHubStats[swe.githubUsername]?.linesDeleted} --
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -532,6 +551,11 @@ export default function Settings(): JSX.Element {
|
|||||||
admin => admin.githubUsername === username
|
admin => admin.githubUsername === username
|
||||||
) && !LONGHORN_DEVELOPERS_SWE.some(swe => swe.githubUsername === username)
|
) && !LONGHORN_DEVELOPERS_SWE.some(swe => swe.githubUsername === username)
|
||||||
)
|
)
|
||||||
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
(githubStats.userGitHubStats[b]?.commits ?? 0) -
|
||||||
|
(githubStats.userGitHubStats[a]?.commits ?? 0)
|
||||||
|
)
|
||||||
.map(username => (
|
.map(username => (
|
||||||
<div
|
<div
|
||||||
key={username}
|
key={username}
|
||||||
@@ -560,7 +584,7 @@ export default function Settings(): JSX.Element {
|
|||||||
<p className='text-xs text-ut-green'>
|
<p className='text-xs text-ut-green'>
|
||||||
{githubStats.userGitHubStats[username]?.linesAdded} ++
|
{githubStats.userGitHubStats[username]?.linesAdded} ++
|
||||||
</p>
|
</p>
|
||||||
<p className='text-xs text-ut-red'>
|
<p className='text-xs text-theme-red'>
|
||||||
{githubStats.userGitHubStats[username]?.linesDeleted} --
|
{githubStats.userGitHubStats[username]?.linesDeleted} --
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -65,8 +65,9 @@ export default function SentryProvider({
|
|||||||
integrations,
|
integrations,
|
||||||
transport: makeFetchTransport,
|
transport: makeFetchTransport,
|
||||||
stackParser: defaultStackParser,
|
stackParser: defaultStackParser,
|
||||||
// debug: true,
|
debug: import.meta.env.DEV,
|
||||||
release: import.meta.env.VITE_PACKAGE_VERSION,
|
release: import.meta.env.VITE_PACKAGE_VERSION,
|
||||||
|
environment: import.meta.env.VITE_SENTRY_ENVIRONMENT,
|
||||||
};
|
};
|
||||||
|
|
||||||
let client: Client;
|
let client: Client;
|
||||||
|
|||||||
45
src/views/hooks/useEnforceScheduleLimit.tsx
Normal file
45
src/views/hooks/useEnforceScheduleLimit.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import useSchedules from '@views/hooks/useSchedules';
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { Button } from '../components/common/Button';
|
||||||
|
import { usePrompt } from '../components/common/DialogProvider/DialogProvider';
|
||||||
|
|
||||||
|
const SCHEDULE_LIMIT = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook that creates a function that enforces a maximum amount of schedules
|
||||||
|
*
|
||||||
|
* If a new schedule can be created without exceeding the limit, the function returns true
|
||||||
|
* Otherwise, display a prompt explaining the limit, and returns false
|
||||||
|
*
|
||||||
|
* @returns a function, () => boolean
|
||||||
|
*/
|
||||||
|
export function useEnforceScheduleLimit(): () => boolean {
|
||||||
|
const [, schedules] = useSchedules();
|
||||||
|
const showDialog = usePrompt();
|
||||||
|
|
||||||
|
return useCallback(() => {
|
||||||
|
if (schedules.length >= SCHEDULE_LIMIT) {
|
||||||
|
showDialog({
|
||||||
|
title: `You have ${SCHEDULE_LIMIT} active schedules!`,
|
||||||
|
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
To encourage organization,{' '}
|
||||||
|
<span className='text-ut-burntorange'>please consider removing some unused schedules</span> you
|
||||||
|
may have.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
|
||||||
|
buttons: close => (
|
||||||
|
<Button variant='filled' color='ut-burntorange' onClick={close}>
|
||||||
|
I Understand
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}, [schedules, showDialog]);
|
||||||
|
}
|
||||||
@@ -95,9 +95,11 @@ function extractCourseInfo(course: Course) {
|
|||||||
|
|
||||||
let courseDeptAndInstr = `${course.department} ${course.number}`;
|
let courseDeptAndInstr = `${course.department} ${course.number}`;
|
||||||
|
|
||||||
const mainInstructor = course.instructors[0];
|
if (course.instructors.length > 0) {
|
||||||
if (mainInstructor) {
|
courseDeptAndInstr += ' \u2013 ';
|
||||||
courseDeptAndInstr += ` – ${mainInstructor.toString({ format: 'first_last', case: 'capitalize' })}`;
|
courseDeptAndInstr += course.instructors
|
||||||
|
.map(instructor => instructor.toString({ format: 'last', case: 'capitalize' }))
|
||||||
|
.join('; ');
|
||||||
}
|
}
|
||||||
|
|
||||||
return { status, courseDeptAndInstr, meetings, course };
|
return { status, courseDeptAndInstr, meetings, course };
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ const TableDataSelector = {
|
|||||||
SCHEDULE_HOURS: 'td[data-th="Hour"]>span',
|
SCHEDULE_HOURS: 'td[data-th="Hour"]>span',
|
||||||
SCHEDULE_LOCATION: 'td[data-th="Room"]>span',
|
SCHEDULE_LOCATION: 'td[data-th="Room"]>span',
|
||||||
FLAGS: 'td[data-th="Flags"] ul li',
|
FLAGS: 'td[data-th="Flags"] ul li',
|
||||||
|
CORE_CURRICULUM: 'td[data-th="Core"] ul li',
|
||||||
} as const satisfies Record<string, string>;
|
} as const satisfies Record<string, string>;
|
||||||
|
|
||||||
type TableDataSelectorType = (typeof TableDataSelector)[keyof typeof TableDataSelector];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The selectors that we use to scrape the course details page for an individual course (https://utdirect.utexas.edu/apps/registrar/course_schedule/20239/52700/)
|
* The selectors that we use to scrape the course details page for an individual course (https://utdirect.utexas.edu/apps/registrar/course_schedule/20239/52700/)
|
||||||
*/
|
*/
|
||||||
@@ -31,8 +30,6 @@ const DetailsSelector = {
|
|||||||
COURSE_DESCRIPTION: '#details p',
|
COURSE_DESCRIPTION: '#details p',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
type DetailsSelectorType = (typeof DetailsSelector)[keyof typeof DetailsSelector];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that allows us to scrape information from UT's course catalog to create our internal representation of a course
|
* A class that allows us to scrape information from UT's course catalog to create our internal representation of a course
|
||||||
*/
|
*/
|
||||||
@@ -99,6 +96,7 @@ export class CourseCatalogScraper {
|
|||||||
semester: this.getSemester(),
|
semester: this.getSemester(),
|
||||||
scrapedAt: Date.now(),
|
scrapedAt: Date.now(),
|
||||||
colors: getCourseColors('emerald', 500),
|
colors: getCourseColors('emerald', 500),
|
||||||
|
core: this.getCore(row),
|
||||||
});
|
});
|
||||||
courses.push({
|
courses.push({
|
||||||
element: row,
|
element: row,
|
||||||
@@ -337,6 +335,21 @@ export class CourseCatalogScraper {
|
|||||||
return Array.from(lis).map(li => li.textContent || '');
|
return Array.from(lis).map(li => li.textContent || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of core curriculum requirements the course satisfies
|
||||||
|
* @param row
|
||||||
|
* @returns an array of core curriculum codes
|
||||||
|
*/
|
||||||
|
getCore(row: HTMLTableRowElement): string[] {
|
||||||
|
const lis = row.querySelectorAll(TableDataSelector.CORE_CURRICULUM);
|
||||||
|
return (
|
||||||
|
Array.from(lis)
|
||||||
|
// ut schedule is weird and puts a blank core curriculum element even if there aren't any core requirements so filter those out
|
||||||
|
.filter(li => li.getAttribute('title') !== ' core curriculum requirement')
|
||||||
|
.map(li => li.textContent || '')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This will scrape all the time information from the course catalog table row and return it as a CourseSchedule object, which represents all of the meeting timiestimes/places of the course.
|
* This will scrape all the time information from the course catalog table row and return it as a CourseSchedule object, which represents all of the meeting timiestimes/places of the course.
|
||||||
* @param row the row of the course catalog table
|
* @param row the row of the course catalog table
|
||||||
|
|||||||
@@ -43,11 +43,11 @@ export const LONGHORN_DEVELOPERS_ADMINS = [
|
|||||||
{ name: 'Diego Perez', role: 'Staff Engineer', githubUsername: 'doprz' },
|
{ name: 'Diego Perez', role: 'Staff Engineer', githubUsername: 'doprz' },
|
||||||
{ name: 'Lukas Zenick', role: 'Senior Software Engineer', githubUsername: 'Lukas-Zenick' },
|
{ name: 'Lukas Zenick', role: 'Senior Software Engineer', githubUsername: 'Lukas-Zenick' },
|
||||||
{ name: 'Isaiah Rodriguez', role: 'Chief Operations and Design Officer', githubUsername: 'IsaDavRod' },
|
{ name: 'Isaiah Rodriguez', role: 'Chief Operations and Design Officer', githubUsername: 'IsaDavRod' },
|
||||||
|
{ name: 'Samuel Gunter', role: 'Senior Software Engineer', githubUsername: 'Samathingamajig' },
|
||||||
|
{ name: 'Derek Chen', role: 'Senior Software Engineer', githubUsername: 'DereC4' },
|
||||||
] as const satisfies TeamMember[];
|
] as const satisfies TeamMember[];
|
||||||
|
|
||||||
export const LONGHORN_DEVELOPERS_SWE = [
|
export const LONGHORN_DEVELOPERS_SWE = [
|
||||||
{ name: 'Samuel Gunter', role: 'Software Engineer', githubUsername: 'Samathingamajig' },
|
|
||||||
{ name: 'Derek Chen', role: 'Software Engineer', githubUsername: 'DereC4' },
|
|
||||||
{ name: 'Casey Charleston', role: 'Software Engineer', githubUsername: 'caseycharleston' },
|
{ name: 'Casey Charleston', role: 'Software Engineer', githubUsername: 'caseycharleston' },
|
||||||
{ name: 'Vinson', role: 'Software Engineer', githubUsername: 'vinsonzheng499' },
|
{ name: 'Vinson', role: 'Software Engineer', githubUsername: 'vinsonzheng499' },
|
||||||
{ name: 'Vivek', role: 'Software Engineer', githubUsername: 'vivek12311' },
|
{ name: 'Vivek', role: 'Software Engineer', githubUsername: 'vivek12311' },
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ $turquoise: #00a9b7;
|
|||||||
$bluebonnet: #005f86;
|
$bluebonnet: #005f86;
|
||||||
$shade: #9cadb7;
|
$shade: #9cadb7;
|
||||||
$limestone: #d6d2c4;
|
$limestone: #d6d2c4;
|
||||||
$speedway_brick: #af2e2d;
|
$speedway_brick: #d10000;
|
||||||
|
|
||||||
//scss hover active focus color calculation
|
//scss hover active focus color calculation
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,13 @@ if (isBeta) {
|
|||||||
process.env.VITE_BETA_BUILD = 'true';
|
process.env.VITE_BETA_BUILD = 'true';
|
||||||
}
|
}
|
||||||
process.env.VITE_PACKAGE_VERSION = packageJson.version;
|
process.env.VITE_PACKAGE_VERSION = packageJson.version;
|
||||||
|
if (process.env.PROD) {
|
||||||
|
process.env.VITE_SENTRY_ENVIRONMENT = 'production';
|
||||||
|
} else if (isBeta) {
|
||||||
|
process.env.VITE_SENTRY_ENVIRONMENT = 'beta';
|
||||||
|
} else {
|
||||||
|
process.env.VITE_SENTRY_ENVIRONMENT = 'development';
|
||||||
|
}
|
||||||
|
|
||||||
export const preambleCode = `
|
export const preambleCode = `
|
||||||
import RefreshRuntime from "__BASE__@react-refresh"
|
import RefreshRuntime from "__BASE__@react-refresh"
|
||||||
@@ -89,10 +96,13 @@ export default defineConfig({
|
|||||||
apply: 'serve',
|
apply: 'serve',
|
||||||
transform(code, id) {
|
transform(code, id) {
|
||||||
if (id.endsWith('.tsx') || id.endsWith('.ts') || id.endsWith('?url')) {
|
if (id.endsWith('.tsx') || id.endsWith('.ts') || id.endsWith('?url')) {
|
||||||
return code.replace(
|
return {
|
||||||
/(['"])(\/public\/.*?)(['"])/g,
|
code: code.replace(
|
||||||
(_, quote1, path, quote2) => `chrome.runtime.getURL(${quote1}${path}${quote2})`
|
/(['"])(\/public\/.*?)(['"])/g,
|
||||||
);
|
(_, quote1, path, quote2) => `chrome.runtime.getURL(${quote1}${path}${quote2})`
|
||||||
|
),
|
||||||
|
map: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -101,10 +111,13 @@ export default defineConfig({
|
|||||||
apply: 'build',
|
apply: 'build',
|
||||||
transform(code, id) {
|
transform(code, id) {
|
||||||
if (id.endsWith('.tsx') || id.endsWith('.ts') || id.endsWith('?url')) {
|
if (id.endsWith('.tsx') || id.endsWith('.ts') || id.endsWith('?url')) {
|
||||||
return code.replace(
|
return {
|
||||||
/(['"])(__VITE_ASSET__.*?__)(['"])/g,
|
code: code.replace(
|
||||||
(_, quote1, path, quote2) => `chrome.runtime.getURL(${quote1}${path}${quote2})`
|
/(['"])(__VITE_ASSET__.*?__)(['"])/g,
|
||||||
);
|
(_, quote1, path, quote2) => `chrome.runtime.getURL(${quote1}${path}${quote2})`
|
||||||
|
),
|
||||||
|
map: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -114,13 +127,16 @@ export default defineConfig({
|
|||||||
enforce: 'post',
|
enforce: 'post',
|
||||||
transform(code, id) {
|
transform(code, id) {
|
||||||
if (process.env.NODE_ENV === 'development' && (id.endsWith('.css') || id.endsWith('.scss'))) {
|
if (process.env.NODE_ENV === 'development' && (id.endsWith('.css') || id.endsWith('.scss'))) {
|
||||||
return code.replace(
|
return {
|
||||||
/url\((.*?)\)/g,
|
code: code.replace(
|
||||||
(_, path) =>
|
/url\((.*?)\)/g,
|
||||||
`url(\\"" + chrome.runtime.getURL(${path
|
(_, path) =>
|
||||||
.replaceAll(`\\"`, `"`)
|
`url(\\"" + chrome.runtime.getURL(${path
|
||||||
.replace(/public\//, '')}) + "\\")`
|
.replaceAll(`\\"`, `"`)
|
||||||
);
|
.replace(/public\//, '')}) + "\\")`
|
||||||
|
),
|
||||||
|
map: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -133,9 +149,8 @@ export default defineConfig({
|
|||||||
/(__VITE_ASSET__.*?__)/g,
|
/(__VITE_ASSET__.*?__)/g,
|
||||||
(_, path) => `chrome-extension://__MSG_@@extension_id__${path}`
|
(_, path) => `chrome-extension://__MSG_@@extension_id__${path}`
|
||||||
);
|
);
|
||||||
return transformedCode;
|
return { code: transformedCode, map: null };
|
||||||
}
|
}
|
||||||
return code;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
renameFile('src/pages/debug/index.html', 'debug.html'),
|
renameFile('src/pages/debug/index.html', 'debug.html'),
|
||||||
@@ -143,7 +158,7 @@ export default defineConfig({
|
|||||||
renameFile('src/pages/calendar/index.html', 'calendar.html'),
|
renameFile('src/pages/calendar/index.html', 'calendar.html'),
|
||||||
renameFile('src/pages/report/index.html', 'report.html'),
|
renameFile('src/pages/report/index.html', 'report.html'),
|
||||||
vitePluginRunCommandOnDemand({
|
vitePluginRunCommandOnDemand({
|
||||||
afterServerStart: 'pnpm gulp forceDisableUseDynamicUrl',
|
// afterServerStart: 'pnpm gulp forceDisableUseDynamicUrl',
|
||||||
closeBundle: 'pnpm gulp forceDisableUseDynamicUrl',
|
closeBundle: 'pnpm gulp forceDisableUseDynamicUrl',
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
@@ -187,6 +202,7 @@ export default defineConfig({
|
|||||||
target: ['chrome120', 'edge120', 'firefox120'],
|
target: ['chrome120', 'edge120', 'firefox120'],
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
reportCompressedSize: false,
|
reportCompressedSize: false,
|
||||||
|
sourcemap: true,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
debug: 'src/pages/debug/index.html',
|
debug: 'src/pages/debug/index.html',
|
||||||
@@ -205,4 +221,11 @@ export default defineConfig({
|
|||||||
provider: 'v8',
|
provider: 'v8',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
api: 'modern-compiler',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user