Compare commits

...

16 Commits

Author SHA1 Message Date
ngupta
517fd4cc78 test commit 2025-10-21 09:19:07 -05:00
dependabot[bot]
e8a8b8e1ae chore(deps): bump the npm_and_yarn group across 1 directory with 4 updates (#639)
Bumps the npm_and_yarn group with 4 updates in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite), [@babel/helpers](https://github.com/babel/babel/tree/HEAD/packages/babel-helpers), [brace-expansion](https://github.com/juliangruber/brace-expansion) and [undici](https://github.com/nodejs/undici).


Updates `vite` from 5.4.14 to 5.4.20
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.20/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.20/packages/vite)

Updates `@babel/helpers` from 7.26.9 to 7.28.4
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.28.4/packages/babel-helpers)

Updates `brace-expansion` from 1.1.11 to 1.1.12
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

Updates `undici` from 6.21.1 to 6.22.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.21.1...v6.22.0)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 5.4.20
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: "@babel/helpers"
  dependency-version: 7.28.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: undici
  dependency-version: 6.22.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-13 11:36:53 -05:00
doprz
c21cbd77f0 feat(release): v2.2.2 2025-10-13 11:20:01 -05:00
99a035e29d chore: remove summer 2025 schedule list item (#637) 2025-10-13 02:25:27 -05:00
doprz
64baa6d290 refactor(nix): dev shells (#634) 2025-10-12 22:47:47 -05:00
Samuel Gunter
46fe591fa7 fix: whitespace wrapping in semester warning (#629) 2025-10-07 18:28:41 -05:00
doprz
8f7e1bc0af feat(env): add SENTRY env vars 2025-10-07 16:11:14 -05:00
doprz
9fc1098ef7 chore(env): add .env.example 2025-10-07 16:00:40 -05:00
Warith Rahman
ae094416fc chore(ui): added spring 2026 course schedule (#628) 2025-10-07 15:26:15 -05:00
ishita778
2e7dac1e3e feat: show warning for courses of different semesters (#570)
* chore: removed extra space at calendar footer

* chore: fixed eslint issues

* chore: changed return type to react node

* chore: displaycourses true fixes and checks fixed

* chore: prettier fix

* feat: not working same semester course issue

* feat: modifying components to use the new hook

* feat: small fixes

* fix: remove comments and spaces

* fix: dialog error solved

* fix: add to new schedule

* fix: prettier

* fix: delete unnecessary custom hook and p[rettier

* fix: checks all passing

* fix: added requested changes

* fix: added new conditions

* fix: description fixed

* style: fix Roboto Flex not being used as font in dialog

* fix: made requested changes

---------

Co-authored-by: doprz <52579214+doprz@users.noreply.github.com>
Co-authored-by: Razboy20 <razboy20@gmail.com>
Co-authored-by: Samuel Gunter <29130894+Samathingamajig@users.noreply.github.com>
2025-08-27 13:41:36 -05:00
doprz
7bea23a655 refactor: nix flake (#625)
* chore(nix): update flake

* chore(nix): remove just and update comment

* chore(nix): update node version + volta config

* refactor: nix flake
2025-08-25 11:33:44 -07:00
Samuel Gunter
3d28869e92 chore: spring 2025 grades (#624) 2025-08-18 00:29:59 -05:00
doprz
f0f1f0b365 chore: bump node and pnpm version (#620)
* chore(nix): update flake

* chore(nix): remove just and update comment

* chore(nix): update node version + volta config
2025-08-12 14:00:45 -05:00
be861b823c feat: search result shading (#617)
* feat: site support kws

* feat: function

* feat: stuff before bedtime

* feat: shading function

* feat: shading

* feat: shading the table children

* chore: fix lint issues

* feat: dependency array

* feat: remove

* feat: remove temp console log
2025-08-07 13:28:56 -05:00
Samuel Gunter
95de8df372 fix: fix or ignore various eslint warning (#609) 2025-07-16 07:54:40 -07:00
5994ded8be feat: export schedule button add to calendar (#594)
* feat: export schedule button add to calendar add to util too

* docs: hypen bruh

* chore: lowercase

* style: filecode icon

* chore: unused import

* refactor: use export json deleted old function

* chore: linting

* chore: remove useless import

---------

Co-authored-by: Samuel Gunter <29130894+Samathingamajig@users.noreply.github.com>
2025-06-17 11:57:48 -07:00
24 changed files with 855 additions and 428 deletions

3
.env.example Normal file
View File

@@ -0,0 +1,3 @@
SENTRY_ORG=longhorn-developers
SENTRY_PROJECT=ut-registration-plus
SENTRY_AUTH_TOKEN=

View File

@@ -1,9 +1,27 @@
## [2.2.2](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/v2.2.1...v2.2.2) (2025-10-13)
### Features
* add nix flake ([#593](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/593)) ([7b401ad](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7b401add1565ff401bad99745ff9e53b9a7f899f))
* automatically select new or duplicated schedules ([#583](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/583)) ([#589](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/589)) ([2a50f55](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2a50f5580d3dbeb0d66546c23cf29bbb37d80da2))
* **env:** add SENTRY env vars ([8f7e1bc](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8f7e1bc0af6336549068e02b80df21d4e8f4ef9c))
* export schedule button add to calendar ([#594](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/594)) ([5994ded](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5994ded8be876cb55174d27d3fdb0832b21a0ff9))
* search result shading ([#617](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/617)) ([be861b8](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/be861b823cb2cb7f6f4a1f266351eec3fc1c2f99))
* show warning for courses of different semesters ([#570](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/570)) ([2e7dac1](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2e7dac1e3eba757231ac07ac966231c08c703a16))
* support summer grades, fix summer course parser ([#596](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/596)) ([2d92dd4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2d92dd47f00a44b7d48e92a8ffba94480e4e73f9))
### Bug Fixes
* fix or ignore various eslint warning ([#609](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/609)) ([95de8df](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/95de8df37243b6d59625df515a60442f11b7a9d3))
* limit height of schedule list dropdown in the extension popup ([#543](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/543)) ([eb8141e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/eb8141ee8c3d32bce901457178d50781b78f86dd))
* whitespace wrapping in semester warning ([#629](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/629)) ([46fe591](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/46fe591fa72ef017eea7cfb8aa37d12d8f223926))
## [2.2.1](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/v2.2.0...v2.2.1) (2025-06-04) ## [2.2.1](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/v2.2.0...v2.2.1) (2025-06-04)
### Features ### Features
* add dining app promo ([#598](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/598)) ([be1dccf](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/be1dccfcb9d052c6b291b50cc53418d6bb645beb)) * add dining app promo ([#598](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/598)) ([be1dccf](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/be1dccfcb9d052c6b291b50cc53418d6bb645beb))
* inside jokes005 ([#590](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/590)) ([37471ef](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/37471efb740c7a5828cf3b54bac70954694359d7)) * inside jokes005 ([#590](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/590)) ([37471ef](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/37471efb740c7a5828cf3b54bac70954694359d7))
* **release:** v2.2.1 ([234f3d6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/234f3d627d603adf8555b4d0e93106d198918169))
### Bug Fixes ### Bug Fixes

View File

@@ -216,3 +216,5 @@ Special thanks to the developers and contributors behind these amazing tools and
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Longhorn-Developers/UT-Registration-Plus&type=Date" /> <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Longhorn-Developers/UT-Registration-Plus&type=Date" />
</picture> </picture>
</a> </a>

6
flake.lock generated
View File

@@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1744932701, "lastModified": 1759831965,
"narHash": "sha256-fusHbZCyv126cyArUwwKrLdCkgVAIaa/fQJYFlCEqiU=", "narHash": "sha256-vgPm2xjOmKdZ0xKA6yLXPJpjOtQPHfaZDRtH+47XEBo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "b024ced1aac25639f8ca8fdfc2f8c4fbd66c48ef", "rev": "c9b6fb798541223bbb396d287d16f43520250518",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -1,31 +1,42 @@
{ {
inputs = { inputs = {
flake-utils.url = "github:numtide/flake-utils";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
}; };
outputs = outputs =
inputs: {
inputs.flake-utils.lib.eachDefaultSystem ( self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system: system:
let let
pkgs = (import (inputs.nixpkgs) { inherit system; }); pkgs = (import nixpkgs { inherit system; });
commonPackages = with pkgs; [
nodejs_20 # v20.19.5
pnpm_10 # v10.18.0
];
additionalPackages = with pkgs; [
bun
nodePackages.conventional-changelog-cli
sentry-cli
];
in in
{ {
formatter = pkgs.nixfmt-rfc-style; formatter = pkgs.nixfmt-rfc-style;
devShell = pkgs.mkShell { devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [ name = "utrp-dev";
nodejs_20 # v20.19.0 buildInputs = commonPackages;
pnpm_10 # v10.8.1 };
just
];
shellHook = '' devShells.full = pkgs.mkShell {
echo "UTRP Nix Flake Environment Loaded" name = "utrp-dev-full";
echo "Node: $(node --version)" buildInputs = commonPackages ++ additionalPackages;
echo "pnpm: $(pnpm --version)"
'';
}; };
} }
); );

View File

@@ -1,7 +1,7 @@
{ {
"name": "ut-registration-plus", "name": "ut-registration-plus",
"displayName": "UT Registration Plus", "displayName": "UT Registration Plus",
"version": "2.2.1", "version": "2.2.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",
@@ -144,7 +144,7 @@
"unocss": "^0.63.6", "unocss": "^0.63.6",
"unocss-preset-primitives": "0.0.2-beta.1", "unocss-preset-primitives": "0.0.2-beta.1",
"unplugin-icons": "^0.19.3", "unplugin-icons": "^0.19.3",
"vite": "^5.4.14", "vite": "^5.4.20",
"vite-plugin-inspect": "^0.8.9", "vite-plugin-inspect": "^0.8.9",
"vitest": "^2.1.9" "vitest": "^2.1.9"
}, },
@@ -162,7 +162,7 @@
} }
}, },
"volta": { "volta": {
"node": "20.9.0", "node": "20.19.4",
"pnpm": "10.6.5" "pnpm": "10.14.0"
} }
} }

935
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
import CourseCatalogMain from '@views/components/CourseCatalogMain'; import CourseCatalogMain from '@views/components/CourseCatalogMain';
import InjectedButton from '@views/components/injected/AddAllButton'; import InjectedButton from '@views/components/injected/AddAllButton';
import DaysCheckbox from '@views/components/injected/DaysCheckbox'; import DaysCheckbox from '@views/components/injected/DaysCheckbox';
import ShadedResults from '@views/components/injected/SearchResultShader';
import getSiteSupport, { SiteSupport } from '@views/lib/getSiteSupport'; import getSiteSupport, { SiteSupport } from '@views/lib/getSiteSupport';
import React from 'react'; import React from 'react';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
@@ -30,3 +31,7 @@ if (support === SiteSupport.MY_UT) {
if (support === SiteSupport.COURSE_CATALOG_SEARCH) { if (support === SiteSupport.COURSE_CATALOG_SEARCH) {
renderComponent(DaysCheckbox); renderComponent(DaysCheckbox);
} }
if (support === SiteSupport.COURSE_CATALOG_KWS) {
renderComponent(ShadedResults);
}

View File

@@ -15,6 +15,8 @@ import type { SiteSupportType } from '@views/lib/getSiteSupport';
import { populateSearchInputs } from '@views/lib/populateSearchInputs'; import { populateSearchInputs } from '@views/lib/populateSearchInputs';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import DialogProvider from './common/DialogProvider/DialogProvider';
interface Props { interface Props {
support: Extract<SiteSupportType, 'COURSE_CATALOG_DETAILS' | 'COURSE_CATALOG_LIST'>; support: Extract<SiteSupportType, 'COURSE_CATALOG_DETAILS' | 'COURSE_CATALOG_LIST'>;
} }
@@ -82,28 +84,30 @@ export default function CourseCatalogMain({ support }: Props): JSX.Element | nul
return ( return (
<ExtensionRoot> <ExtensionRoot>
<NewSearchLink /> <DialogProvider>
<RecruitmentBanner /> <NewSearchLink />
<TableHead>Plus</TableHead> <RecruitmentBanner />
{rows.map( <TableHead>Plus</TableHead>
row => {rows.map(
row.course && ( row =>
<TableRow row.course && (
key={row.course.uniqueId} <TableRow
row={row} key={row.course.uniqueId}
isSelected={row.course.uniqueId === selectedCourse?.uniqueId} row={row}
activeSchedule={activeSchedule} isSelected={row.course.uniqueId === selectedCourse?.uniqueId}
onClick={handleRowButtonClick(row.course)} activeSchedule={activeSchedule}
/> onClick={handleRowButtonClick(row.course)}
) />
)} )
<CourseCatalogInjectedPopup )}
course={selectedCourse!} // always defined when showPopup is true <CourseCatalogInjectedPopup
show={showPopup} course={selectedCourse!} // always defined when showPopup is true
onClose={() => setShowPopup(false)} show={showPopup}
afterLeave={() => setSelectedCourse(null)} onClose={() => setShowPopup(false)}
/> afterLeave={() => setSelectedCourse(null)}
{enableScrollToLoad && <AutoLoad addRows={addRows} />} />
{enableScrollToLoad && <AutoLoad addRows={addRows} />}
</DialogProvider>
</ExtensionRoot> </ExtensionRoot>
); );
} }

View File

@@ -27,12 +27,10 @@ export default function HexColorEditor({ hexCode, setHexCode }: HexColorEditorPr
const tagColor = pickFontColor(previewColor.slice(1) as `#${string}`); const tagColor = pickFontColor(previewColor.slice(1) as `#${string}`);
const [localHexCode, setLocalHexCode] = React.useState(hexCode); const [localHexCode, setLocalHexCode] = React.useState(hexCode);
const debouncedSetHexCode = useDebounce((value: string) => setHexCode(value), 500); const debouncedSetHexCode = useDebounce(setHexCode, 500);
React.useEffect(() => { React.useEffect(() => {
if (hexCode !== localHexCode) { setLocalHexCode(hexCode);
setLocalHexCode(hexCode);
}
}, [hexCode]); }, [hexCode]);
React.useEffect(() => { React.useEffect(() => {

View File

@@ -1,5 +1,5 @@
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'; import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react';
import { CalendarDots, Export, FilePng, Sidebar } from '@phosphor-icons/react'; import { CalendarDots, Export, FileCode, FilePng, Sidebar } from '@phosphor-icons/react';
import styles from '@views/components/calendar/CalendarHeader/CalendarHeader.module.scss'; import styles from '@views/components/calendar/CalendarHeader/CalendarHeader.module.scss';
import { Button } from '@views/components/common/Button'; import { Button } from '@views/components/common/Button';
import DialogProvider from '@views/components/common/DialogProvider/DialogProvider'; import DialogProvider from '@views/components/common/DialogProvider/DialogProvider';
@@ -11,7 +11,7 @@ import useSchedules from '@views/hooks/useSchedules';
import clsx from 'clsx'; import clsx from 'clsx';
import React from 'react'; import React from 'react';
import { saveAsCal, saveCalAsPng } from '../utils'; import { handleExportJson, saveAsCal, saveCalAsPng } from '../utils';
interface CalendarHeaderProps { interface CalendarHeaderProps {
sidebarOpen?: boolean; sidebarOpen?: boolean;
@@ -98,6 +98,18 @@ export default function CalendarHeader({ sidebarOpen, onSidebarToggle }: Calenda
Save as .cal Save as .cal
</Button> </Button>
</MenuItem> </MenuItem>
<MenuItem>
<Button
className='w-full flex justify-start'
onClick={() => handleExportJson(activeSchedule.id)}
color='ut-black'
size='small'
variant='minimal'
icon={FileCode}
>
Save as .json
</Button>
</MenuItem>
{/* <MenuItem> {/* <MenuItem>
<Button color='ut-black' size='small' variant='minimal' icon={FileTxt}> <Button color='ut-black' size='small' variant='minimal' icon={FileTxt}>
Export Unique IDs Export Unique IDs

View File

@@ -1,5 +1,5 @@
import { AppStoreLogo, ForkKnife, X as CloseIcon } from '@phosphor-icons/react'; import { AppStoreLogo, ForkKnife, X as CloseIcon } from '@phosphor-icons/react';
import { UT_DINING_APP_STORE_URL, UT_DINING_GOOGLE_PLAY_URL } from '@shared/util/appUrls'; import { UT_DINING_APP_STORE_URL } from '@shared/util/appUrls';
import { Button } from '@views/components/common/Button'; import { Button } from '@views/components/common/Button';
import Text from '@views/components/common/Text/Text'; import Text from '@views/components/common/Text/Text';
import React from 'react'; import React from 'react';

View File

@@ -14,18 +14,14 @@ interface LinkItem {
} }
const links: LinkItem[] = [ const links: LinkItem[] = [
{
text: "Spring '26 Course Schedule",
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20262/',
},
{ {
text: "Fall '25 Course Schedule", text: "Fall '25 Course Schedule",
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20259/', url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20259/',
}, },
{
text: "Summer '25 Course Schedule",
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20256/',
},
// {
// text: "Spring '25 Course Schedule",
// url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20252/',
// },
{ {
text: 'Course Schedule Archives', text: 'Course Schedule Archives',
url: 'https://registrar.utexas.edu/schedules/archive', url: 'https://registrar.utexas.edu/schedules/archive',
@@ -34,10 +30,10 @@ const links: LinkItem[] = [
text: 'My Degree Audit (IDA)', text: 'My Degree Audit (IDA)',
url: 'https://utdirect.utexas.edu/apps/degree/audits/', url: 'https://utdirect.utexas.edu/apps/degree/audits/',
}, },
// { {
// text: "'24-'25 Academic Calendar", text: "'25-'26 Academic Calendar",
// url: 'https://registrar.utexas.edu/calendars/24-25', url: 'https://registrar.utexas.edu/calendars/25-26',
// }, },
{ {
text: 'Registration Info Sheet (RIS)', text: 'Registration Info Sheet (RIS)',
url: 'https://utdirect.utexas.edu/registrar/ris.WBX', url: 'https://utdirect.utexas.edu/registrar/ris.WBX',

View File

@@ -1,4 +1,5 @@
import { tz, TZDate } from '@date-fns/tz'; import { tz, TZDate } from '@date-fns/tz';
import exportSchedule from '@pages/background/lib/exportSchedule';
import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
import type { Course } from '@shared/types/Course'; import type { Course } from '@shared/types/Course';
import type { CourseMeeting } from '@shared/types/CourseMeeting'; import type { CourseMeeting } from '@shared/types/CourseMeeting';
@@ -261,6 +262,22 @@ export const saveAsCal = async () => {
downloadBlob(icsString, 'CALENDAR', 'schedule.ics'); downloadBlob(icsString, 'CALENDAR', 'schedule.ics');
}; };
/**
* Saves current schedule to JSON that can be imported on other devices.
* @param id - Provided schedule ID to download
*/
export const handleExportJson = async (id: string) => {
const jsonString = await exportSchedule(id);
if (jsonString) {
const schedules = await UserScheduleStore.get('schedules');
const schedule = schedules.find(s => s.id === id);
const fileName = `${schedule?.name ?? `schedule_${id}`}_${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
await downloadBlob(jsonString, 'JSON', fileName);
} else {
console.error('Error exporting schedule: jsonString is undefined');
}
};
/** /**
* Saves the calendar as a PNG image. * Saves the calendar as a PNG image.
* *

View File

@@ -15,6 +15,11 @@
@apply font-sans; @apply font-sans;
color: #303030; color: #303030;
// fix font-family on injected pages
* {
@apply font-sans;
}
[data-rfd-drag-handle-context-id=':r1:'] { [data-rfd-drag-handle-context-id=':r1:'] {
cursor: move; cursor: move;
} }

View File

@@ -15,7 +15,7 @@ import React, { useEffect, useState } from 'react';
*/ */
const WHATSNEW_POPUP_VERSION = 2; const WHATSNEW_POPUP_VERSION = 2;
const WHATSNEW_VIDEO_URL = 'https://cdn.longhorns.dev/whats-new-v2.1.2.mp4'; // const WHATSNEW_VIDEO_URL = 'https://cdn.longhorns.dev/whats-new-v2.1.2.mp4';
type Feature = { type Feature = {
id: string; id: string;
@@ -60,7 +60,7 @@ const NEW_FEATURES = [
* @returns A JSX of WhatsNewPopupContent component. * @returns A JSX of WhatsNewPopupContent component.
*/ */
export default function WhatsNewPopupContent(): JSX.Element { export default function WhatsNewPopupContent(): JSX.Element {
const [videoError, setVideoError] = useState(false); const [videoError, _setVideoError] = useState(false);
return ( return (
<div className='w-full flex flex-row justify-between'> <div className='w-full flex flex-row justify-between'>

View File

@@ -1,6 +1,5 @@
import { addCourseByURL } from '@pages/background/lib/addCourseByURL'; import { addCourseByURL } from '@pages/background/lib/addCourseByURL';
import { background } from '@shared/messages'; import { background } from '@shared/messages';
import { validateLoginStatus } from '@shared/util/checkLoginStatus';
import { Button } from '@views/components/common/Button'; import { Button } from '@views/components/common/Button';
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot'; import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
import useSchedules from '@views/hooks/useSchedules'; import useSchedules from '@views/hooks/useSchedules';
@@ -43,6 +42,8 @@ export default function InjectedButton(): JSX.Element | null {
await addCourseByURL(activeSchedule, a); await addCourseByURL(activeSchedule, a);
} }
} else { } else {
// We'll allow the alert for this WIP feature
// eslint-disable-next-line no-alert
window.alert('Logged into UT Registrar.'); window.alert('Logged into UT Registrar.');
} }
}; };

View File

@@ -1,3 +1,5 @@
import createSchedule from '@pages/background/lib/createSchedule';
import switchSchedule from '@pages/background/lib/switchSchedule';
import { import {
ArrowUpRight, ArrowUpRight,
CalendarDots, CalendarDots,
@@ -14,8 +16,10 @@ import { background } from '@shared/messages';
import type { Course } from '@shared/types/Course'; 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 { englishStringifyList } from '@shared/util/string';
import { Button } from '@views/components/common/Button'; import { Button } from '@views/components/common/Button';
import { Chip, coreMap, flagMap } from '@views/components/common/Chip'; import { Chip, coreMap, flagMap } from '@views/components/common/Chip';
import { usePrompt } from '@views/components/common/DialogProvider/DialogProvider';
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';
@@ -60,7 +64,7 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
const [isCopied, setIsCopied] = useState<boolean>(false); const [isCopied, setIsCopied] = useState<boolean>(false);
const lastCopyTime = useRef<number>(0); const lastCopyTime = useRef<number>(0);
const showDialog = usePrompt();
const getInstructorFullName = (instructor: Instructor) => instructor.toString({ format: 'first_last' }); const getInstructorFullName = (instructor: Instructor) => instructor.toString({ format: 'first_last' });
const handleCopy = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => { const handleCopy = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
@@ -112,10 +116,78 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
} }
}; };
const handleAddToNewSchedule = async (close: () => void) => {
const newScheduleId = await createSchedule(`${course.semester.season} ${course.semester.year}`);
switchSchedule(newScheduleId);
addCourse({ course, scheduleId: newScheduleId });
close();
};
const handleAddOrRemoveCourse = async () => { const handleAddOrRemoveCourse = async () => {
const uniqueSemesterCodes = [
...new Set(
activeSchedule.courses
.map(course => course.semester.code)
.filter((code): code is string => code !== undefined)
),
];
uniqueSemesterCodes.sort();
const codeToReadableMap: Record<string, string> = {};
activeSchedule.courses.forEach(course => {
const { code } = course.semester;
if (code) {
const readable = `${course.semester.season} ${course.semester.year}`;
codeToReadableMap[code] = readable;
}
});
const sortedSemesters = uniqueSemesterCodes
.map(code => codeToReadableMap[code])
.filter((value): value is string => value !== undefined);
const activeSemesters = englishStringifyList(sortedSemesters);
if (!activeSchedule) return; if (!activeSchedule) return;
if (!courseAdded) { if (!courseAdded) {
addCourse({ course, scheduleId: activeSchedule.id }); const currentSemesterCode = course.semester.code;
// Show warning if this course is for a different semester than the selected schedule
if (
activeSchedule.courses.length > 0 &&
activeSchedule.courses.every(otherCourse => otherCourse.semester.code !== currentSemesterCode)
) {
const dialogButtons = (close: () => void) => (
<>
<Button variant='minimal' color='ut-black' onClick={close}>
Cancel
</Button>
<Button
variant='filled'
color='ut-burntorange'
onClick={() => {
handleAddToNewSchedule(close);
}}
>
Start a new schedule
</Button>
</>
);
showDialog({
title: 'This course section is from a different semester!',
description: (
<>
The section you&apos;re adding is for{' '}
<span className='text-ut-burntorange whitespace-nowrap'>
{course.semester.season} {course.semester.year}
</span>
, but your current schedule contains sections in{' '}
<span className='text-ut-burntorange whitespace-nowrap'>{activeSemesters}</span>. Mixing
semesters in one schedule may cause confusion.
</>
),
buttons: dialogButtons,
});
} else {
addCourse({ course, scheduleId: activeSchedule.id });
}
} else { } else {
removeCourse({ course, scheduleId: activeSchedule.id }); removeCourse({ course, scheduleId: activeSchedule.id });
} }

View File

@@ -0,0 +1,39 @@
import { useEffect } from 'react';
// @TODO Get a better name for this class
/**
* The existing search results (kws), only with alternate shading for easier readability
*
*/
export default function ShadedResults(): null {
useEffect(() => {
const table = document.getElementById('kw_results_table');
if (!table) {
console.error('Results table not found');
return;
}
const tbody = table.querySelector('tbody');
if (!tbody) {
console.error('Table tbody not found');
return;
}
const style = document.createElement('style');
style.textContent = `
#kw_results_table tbody tr:nth-child(even) {
background-color: #f0f0f0 !important;
}
#kw_results_table tbody tr:nth-child(even) td {
background-color: #f0f0f0 !important;
}
`;
document.head.appendChild(style);
return () => {
style.remove();
};
}, []);
return null;
}

View File

@@ -1,16 +1,13 @@
// import addCourse from '@pages/background/lib/addCourse'; // import addCourse from '@pages/background/lib/addCourse';
import { addCourseByURL } from '@pages/background/lib/addCourseByURL'; import { addCourseByURL } from '@pages/background/lib/addCourseByURL';
import { deleteAllSchedules } from '@pages/background/lib/deleteSchedule'; import { deleteAllSchedules } from '@pages/background/lib/deleteSchedule';
import exportSchedule from '@pages/background/lib/exportSchedule';
import importSchedule from '@pages/background/lib/importSchedule'; import importSchedule from '@pages/background/lib/importSchedule';
import { CalendarDots, Trash } from '@phosphor-icons/react'; import { CalendarDots, Trash } from '@phosphor-icons/react';
import { background } from '@shared/messages'; import { background } from '@shared/messages';
import { DevStore } from '@shared/storage/DevStore'; import { DevStore } from '@shared/storage/DevStore';
import { initSettings, OptionsStore } from '@shared/storage/OptionsStore'; import { initSettings, OptionsStore } from '@shared/storage/OptionsStore';
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
import { CRX_PAGES } from '@shared/types/CRXPages'; import { CRX_PAGES } from '@shared/types/CRXPages';
import MIMEType from '@shared/types/MIMEType'; import MIMEType from '@shared/types/MIMEType';
import { downloadBlob } from '@shared/util/downloadBlob';
// import { addCourseByUrl } from '@shared/util/courseUtils'; // import { addCourseByUrl } from '@shared/util/courseUtils';
// import { getCourseColors } from '@shared/util/colors'; // import { getCourseColors } from '@shared/util/colors';
// import CalendarCourseCell from '@views/components/calendar/CalendarCourseCell'; // import CalendarCourseCell from '@views/components/calendar/CalendarCourseCell';
@@ -32,6 +29,7 @@ import React, { useCallback, useEffect, useState } from 'react';
import IconoirGitFork from '~icons/iconoir/git-fork'; import IconoirGitFork from '~icons/iconoir/git-fork';
import { handleExportJson } from '../calendar/utils';
// import { ExampleCourse } from 'src/stories/components/ConflictsWithWarning.stories';; // import { ExampleCourse } from 'src/stories/components/ConflictsWithWarning.stories';;
import FileUpload from '../common/FileUpload'; import FileUpload from '../common/FileUpload';
import { useMigrationDialog } from '../common/MigrationDialog'; import { useMigrationDialog } from '../common/MigrationDialog';
@@ -232,18 +230,6 @@ export default function Settings(): JSX.Element {
}); });
}; };
const handleExportClick = async (id: string) => {
const jsonString = await exportSchedule(id);
if (jsonString) {
const schedules = await UserScheduleStore.get('schedules');
const schedule = schedules.find(s => s.id === id);
const fileName = `${schedule?.name ?? `schedule_${id}`}_${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
await downloadBlob(jsonString, 'JSON', fileName);
} else {
console.error('Error exporting schedule: jsonString is undefined');
}
};
const handleImportClick = async (event: React.ChangeEvent<HTMLInputElement>) => { const handleImportClick = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]; const file = event.target.files?.[0];
if (file) { if (file) {
@@ -400,7 +386,7 @@ export default function Settings(): JSX.Element {
<Button <Button
variant='outline' variant='outline'
color='ut-burntorange' color='ut-burntorange'
onClick={() => handleExportClick(activeSchedule.id)} onClick={() => handleExportJson(activeSchedule.id)}
> >
Export Export
</Button> </Button>

View File

@@ -5,8 +5,7 @@ import WhatsNewPopupContent from '@views/components/common/WhatsNewPopup';
import { useDialog } from '@views/contexts/DialogContext'; import { useDialog } from '@views/contexts/DialogContext';
import React from 'react'; import React from 'react';
import { LogoIcon } from '../components/common/LogoIcon'; // import useChangelog from './useChangelog';
import useChangelog from './useChangelog';
const LDIconURL = new URL('/src/assets/LD-icon-new.png', import.meta.url).href; const LDIconURL = new URL('/src/assets/LD-icon-new.png', import.meta.url).href;
@@ -17,8 +16,8 @@ const LDIconURL = new URL('/src/assets/LD-icon-new.png', import.meta.url).href;
*/ */
export default function useWhatsNewPopUp(): () => void { export default function useWhatsNewPopUp(): () => void {
const showDialog = useDialog(); const showDialog = useDialog();
const showChangeLog = useChangelog(); // const showChangeLog = useChangelog();
const { version } = chrome.runtime.getManifest(); // const { version } = chrome.runtime.getManifest();
const showPopUp = () => { const showPopUp = () => {
showDialog(close => ({ showDialog(close => ({

View File

@@ -15,6 +15,7 @@ export const SiteSupport = {
MY_UT: 'MY_UT', MY_UT: 'MY_UT',
COURSE_CATALOG_SEARCH: 'COURSE_CATALOG_SEARCH', COURSE_CATALOG_SEARCH: 'COURSE_CATALOG_SEARCH',
CLASSLIST: 'CLASSLIST', CLASSLIST: 'CLASSLIST',
COURSE_CATALOG_KWS: 'COURSE_CATALOG_KWS',
} as const; } as const;
/** /**
@@ -40,6 +41,9 @@ export default function getSiteSupport(url: string): SiteSupportType | null {
return SiteSupport.UT_PLANNER; return SiteSupport.UT_PLANNER;
} }
if (url.includes('utdirect.utexas.edu/apps/registrar/course_schedule')) { if (url.includes('utdirect.utexas.edu/apps/registrar/course_schedule')) {
if (url.includes('kws_results')) {
return SiteSupport.COURSE_CATALOG_KWS;
}
if (url.includes('results')) { if (url.includes('results')) {
return SiteSupport.COURSE_CATALOG_LIST; return SiteSupport.COURSE_CATALOG_LIST;
} }