Compare commits

..

263 Commits

Author SHA1 Message Date
DhruvArora-03
70c75ff481 add button to the rows, use new ConflictsWithWarning component 2024-02-19 11:29:24 -06:00
DhruvArora-03
7f76af7ab3 fix ConflictsWithWarning 2024-02-19 11:28:33 -06:00
DhruvArora-03
a538f20aad Merge remote-tracking branch 'origin/hackathon' into feat-conflict-row 2024-02-19 11:11:37 -06:00
Lukas Zenick
0acd0b722c html2canvas -> htmlToImage
also fixed derick's bugs
2024-02-18 12:46:35 -06:00
ae32d0b645 feat: Derek/export png (#95)
* Attempting to use more lightweight version

* Did not work.

* This is not what I wanted

* The image saves correctly. Needs padding

* Padding !!

* Removed downloadjs

* Padding more
2024-02-17 19:09:41 -06:00
DhruvArora-03
f214ed7c01 Merge remote-tracking branch 'origin/hackathon' into feat-conflict-row 2024-02-17 18:21:39 -06:00
Lukas Zenick
e44b0c0e45 FIX README 2024-02-17 17:24:27 -06:00
Lukas Zenick
206c97c5b5 fixed README 2024-02-17 17:23:04 -06:00
ac71b838db feat: Derek vinson/calendar header (#94)
* CalendarHeader alignment progress

* Boom

* css

* Between

* Lol

* Gap fix

* whitespace-nowrap

* Gaps

* Finished alignment of CourseStatus and Buttons

* Colors

* ESLint auto format

* Color UT Registration Plus text

* Reverting vscode

---------

Co-authored-by: Vinson Zheng <vinsonzheng499@gmail.com>
2024-02-17 17:02:36 -06:00
Abhinav Chadaga
4f5753917b sort the flattened course schedule (#93)
first by dayIndex, then startIndex, then endIndex.
2024-02-17 16:57:51 -06:00
DhruvArora-03
478ab31706 Merge branch 'testing-useFlattenedCourseSchedule' into hackathon 2024-02-17 16:37:58 -06:00
DhruvArora-03
79c5c97d98 fix extra space 2024-02-17 16:37:35 -06:00
Samuel Gunter
42420f5502 feat: bottom bar for the calendar page (#91) 2024-02-17 16:25:50 -06:00
DhruvArora-03
0ced198f01 remove empty file 2024-02-17 16:23:17 -06:00
Lukas Zenick
a03bcf17b8 broken file 2024-02-17 15:49:52 -06:00
DhruvArora-03
314ea09b41 nit: reorder classnames 2024-02-17 15:45:56 -06:00
DhruvArora-03
1891bd941e Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 15:45:34 -06:00
Lukas Zenick
af991e6609 broken close bracket 2024-02-17 15:42:30 -06:00
Lukas Zenick
fe3521dec7 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 15:40:47 -06:00
Lukas Zenick
a363efb2d2 Save as PNG functionality 2024-02-17 15:40:42 -06:00
knownotunknown
e7ba9c54a6 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 15:33:29 -06:00
knownotunknown
d69e3435cf WIP 2024-02-17 15:32:42 -06:00
vivek12311
b554b4344d Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 15:31:05 -06:00
DhruvArora-03
14f241d603 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 15:30:16 -06:00
DhruvArora-03
419e3066f1 cleanup useFlattenedCourseSchedule 2024-02-17 15:30:14 -06:00
vivek12311
ed3ff846d8 Merge branch 'PopupMain' into hackathon 2024-02-17 15:26:10 -06:00
Lukas Zenick
7132bcf572 adjust location of svg files 2024-02-17 15:26:06 -06:00
vivek12311
0bc30d3d1e test 2024-02-17 15:25:58 -06:00
Lukas Zenick
3878c7104e cal save buttons (no functionality) 2024-02-17 15:15:24 -06:00
knownotunknown
851947db0b Update 2024-02-17 15:03:18 -06:00
Casey Charleston
724e1a1d19 Calendar schedules component (clicking on storybook not working) 2024-02-17 14:40:20 -06:00
Abhinav Chadaga
66fea21abd Merge branch 'hackathon' of https://github.com/ut-developers/UT-Registration-Plus into hackathon 2024-02-17 14:37:59 -06:00
Abhinav Chadaga
7550b55b9b Update CalendarCourseCell.tsx 2024-02-17 14:37:48 -06:00
Abhinav Chadaga
65f0cb27af Merge branch 'hackathon' of https://github.com/ut-developers/UT-Registration-Plus into hackathon 2024-02-17 14:33:51 -06:00
DhruvArora-03
9ce69c2f2e Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 14:32:34 -06:00
DhruvArora-03
a46526fa40 working on course popup - stopping because abhinav already doing 2024-02-17 14:32:28 -06:00
Abhinav Chadaga
c5968f3f11 Update CalendarCourseCell.tsx 2024-02-17 14:31:34 -06:00
Abhinav Chadaga
bba067f591 implement flatten course schedule helper function
takes a course schedule and returns an array of objects that can be used to render all the individual course
2024-02-17 14:30:59 -06:00
knownotunknown
71d8ac7486 Update CalendarGrid.stories.tsx 2024-02-17 14:30:15 -06:00
knownotunknown
2997cb87d4 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 14:29:06 -06:00
knownotunknown
8b2d07033c update 2024-02-17 14:27:32 -06:00
Vinson Zheng
b8fe5109a9 Added CalendarHeader and its Storybook, need to resize 2024-02-17 14:22:24 -06:00
Razboy20
2cffb794db update course block names 2024-02-17 13:32:06 -06:00
Razboy20
73fe14e17a fix calendar course cell spacing 2024-02-17 13:27:07 -06:00
knownotunknown
33ca937d12 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 13:08:43 -06:00
knownotunknown
c0968ef7a0 Revert "Revert "update""
This reverts commit 3c7b35d5f3.
2024-02-17 13:07:43 -06:00
knownotunknown
3c7b35d5f3 Revert "update"
This reverts commit 8edd062588.
2024-02-17 13:06:46 -06:00
knownotunknown
8edd062588 update 2024-02-17 13:02:24 -06:00
DhruvArora-03
42d24f6367 create empty settings component - waiting on design 2024-02-17 13:02:06 -06:00
Razboy20
2f19c57553 fix margins on list component 2024-02-17 12:59:43 -06:00
Razboy20
e3b8e4c270 fix calendar course cell colors 2024-02-17 12:54:10 -06:00
DhruvArora-03
5aef43496f create important links component 2024-02-17 12:44:05 -06:00
Razboy20
e1224e11af Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 12:24:49 -06:00
Razboy20
7f826c2a78 eslint on save 2024-02-17 12:24:46 -06:00
knownotunknown
89a8e42059 Made draggable only by handle 2024-02-17 12:23:52 -06:00
knownotunknown
ed8915bcd1 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 12:09:10 -06:00
knownotunknown
1168584b8a Update 2024-02-17 12:09:01 -06:00
Razboy20
a0496fea18 Merge branch 'hackathon' of github.com:UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 12:07:56 -06:00
Lukas Zenick
fd747e9e8e Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 12:04:42 -06:00
Lukas Zenick
27fdd9c559 fix undefined color case 2024-02-17 12:04:41 -06:00
Vinson Zheng
ba22ba427c Reverted CalendarGrid and CalendarGridCell back to SCSS from Tailwind 2024-02-17 11:54:50 -06:00
Razboy20
3e4cec43d1 update button stylings 2024-02-17 11:41:48 -06:00
Razboy20
633c9ba593 fix unocss theme color namings 2024-02-17 11:41:36 -06:00
knownotunknown
0f14b8ce1b Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 11:26:51 -06:00
knownotunknown
bee506b79b Merge branch 'pr/85' into hackathon 2024-02-17 11:25:33 -06:00
DhruvArora-03
d8a4c91a80 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 11:24:41 -06:00
DhruvArora-03
14ab537db1 add JSdoc to calendar page 2024-02-17 11:24:40 -06:00
knownotunknown
939208532b Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-17 11:07:35 -06:00
knownotunknown
8e3502593e Converted all LabelsAndDetails Components to Tailwind 2024-02-17 11:06:36 -06:00
DhruvArora-03
7b7b858cf5 undo dummy commit 2024-02-17 10:55:44 -06:00
DhruvArora-03
82b467a223 dummy commit 2024-02-17 10:53:44 -06:00
knownotunknown
f049a25aac Merge branch 'pr/68' into hackathon 2024-02-17 10:52:02 -06:00
knownotunknown
39b4396f88 Merge branch 'NonVirtualList' into hackathon 2024-02-17 10:49:39 -06:00
knownotunknown
854d24bf0d Changed Chip to tailwind css. Fixed eslint for ConflictsWithWarning 2024-02-17 10:48:25 -06:00
knownotunknown
c47320e9a3 Merge branch 'LabelsAndDetails' into hackathon 2024-02-17 10:40:35 -06:00
knownotunknown
982b7de50e Merge branch 'ScheduleTotalHoursAndCourses' into hackathon 2024-02-17 10:40:16 -06:00
knownotunknown
17efb94e68 Merge branch 'InfoCard' into hackathon 2024-02-17 10:40:01 -06:00
knownotunknown
40631e2421 Merge branch 'ConflictsWithWarningComponent' into hackathon 2024-02-17 10:39:51 -06:00
knownotunknown
81e02c8187 Build without errors 2024-02-17 10:36:28 -06:00
vivek12311
d907a43552 Merge branch 'pr/90' into PopupMain 2024-02-16 17:16:49 -06:00
Vinson Zheng
3e1a20a9f2 Deleted scss files 2024-02-16 16:34:56 -06:00
Vinson Zheng
d21843468b Tailwind conversion halfway done 2024-02-16 16:34:06 -06:00
vinsonzheng499
6b7e45b5f4 Merge branch 'UT-Developers:main' into master 2024-02-16 16:05:29 -06:00
Razboy20
36b9189a6c fix non-virtual dnd 2024-02-14 19:25:04 -06:00
knownotunknown
80148ba0ff Early iteration of non-virtual list 2024-02-14 17:55:19 -06:00
knownotunknown
776cab9297 Merge branch 'main' into ListComponentContinuation 2024-02-14 14:23:45 -06:00
knownotunknown
f95736339a Update CalendarCourse.stories.tsx 2024-02-14 13:24:55 -06:00
knownotunknown
3c83a3c064 Eslint, removed React-beautiful-dnd 2024-02-14 13:23:22 -06:00
Samuel Gunter
0928e8d033 chore: merge branch 'main' into sgunter/button-component 2024-02-13 16:54:36 -06:00
vivek12311
3b9ea8ebd5 Popup 2024-02-13 12:53:51 -06:00
Samuel Gunter
b2e7af64b3 fix: only show button hover effects when not disabled 2024-02-12 01:51:46 -06:00
Samuel Gunter
11ce6be578 chore: suppress useless TS warnings 2024-02-11 23:38:24 -06:00
Samuel Gunter
524e3b46d3 chore: removed unused Button.module.scss 2024-02-11 23:29:42 -06:00
Samuel Gunter
000682b4db feat: buttons with icons in tailwind
i am not proud of some of this code
2024-02-11 23:24:24 -06:00
Som Gupta
fa1d7374bc feat: CourseStatus Component implemented (#83)
Co-authored-by: Razboy20 <razboy20@gmail.com>
2024-02-10 21:26:21 -06:00
147d38d5c5 Lined em up 2024-02-10 18:21:18 -06:00
knownotunknown
14a90f3dc0 Made List more extensible 2024-02-10 15:09:49 -06:00
dcc7c731c6 Merge branch 'sghsri:main' into master 2024-02-09 18:40:39 -06:00
Razboy20
c122e744ef ci: update pnpm action (#86)
* ci: update pnpm action

* hotfix: change action type
2024-02-09 18:39:20 -06:00
ad77d73363 Warning 2024-02-09 17:57:48 -06:00
80ee5bfdda Wat 2024-02-09 17:56:29 -06:00
e93c5d3167 Brought back coursemeeting.ts 2024-02-09 17:54:25 -06:00
5ec0f19c53 Merge branch 'master' of https://github.com/DereC4/UT-Registration-Plus 2024-02-09 17:47:24 -06:00
24da9baffe Y 2024-02-09 17:47:12 -06:00
de16ece2ea Merge branch 'sghsri:main' into master 2024-02-09 17:40:52 -06:00
d42ba72170 Correcting text 2024-02-09 17:35:52 -06:00
Razboy20
708a136a5c ci: chromatic builds (#84)
* ci: chromatic builds

* hotfix: add pnpm install to workflow

* hotfix: add pnpm version
2024-02-09 17:30:12 -06:00
64563b2f40 Merge branch 'sghsri:main' into master 2024-02-09 17:27:14 -06:00
18dcf8a139 Cool looking tickmarks 2024-02-09 17:26:26 -06:00
72ecb314e8 Final polishing up 2024-02-09 17:20:46 -06:00
0ce6de8b13 ...now WE GOT IT 2024-02-09 17:13:02 -06:00
fa30c526b9 WE GOT IT 2024-02-09 17:12:39 -06:00
c44fd014e9 ARRAY.FROM PAIN 2024-02-09 17:07:21 -06:00
d44f5216f8 Fixing alignment 2024-02-09 16:55:18 -06:00
Vinson Zheng
f1e8485eb5 Small style adjustments 2024-02-09 16:00:38 -06:00
knownotunknown
b57415c846 Updated packages installed 2024-02-09 15:59:31 -06:00
knownotunknown
ff06d05857 Merge branch 'main' into ListComponentContinuation 2024-02-09 15:54:44 -06:00
knownotunknown
1a4d22ccf0 Updated alignment 2024-02-09 14:33:11 -06:00
knownotunknown
279ac076b0 Updated with Tailwind 2024-02-09 12:05:02 -06:00
knownotunknown
e3301cc200 Done, but might want to update font size 2024-02-09 11:57:22 -06:00
knownotunknown
ccf2c68340 Update ConflictsWithWarning.tsx 2024-02-09 11:01:42 -06:00
knownotunknown
dc19d3975a Implemented ConflictsWithWarning 2024-02-09 10:53:41 -06:00
Samuel Gunter
2eaf1b3c36 chore: merge branch 'main' into sgunter/button-component 2024-02-08 14:08:44 -06:00
Dhruv
f045b400a5 feat: PopupCourseBlock Component (#79)
Co-authored-by: Razboy20 <Razboy20@users.noreply.github.com>
Co-authored-by: Razboy20 <razboy20@gmail.com>
2024-02-08 11:52:29 -06:00
Samuel Gunter
16cc2f071e chore: merge branch 'main' into sgunter/button-component
for clsx, and other features
2024-02-07 00:27:55 -06:00
Samuel Gunter
ae08ee02f4 feat: tailwind version of Button component
still WIP, need to figure out icon spacing exactly
2024-02-06 23:56:35 -06:00
4f3ccd9c90 stories for grid cell 2024-02-05 23:22:56 -06:00
aaccd9b562 Combine Days with Hours 2024-02-05 23:12:51 -06:00
349e1e5332 Merge branch 'master' of https://github.com/DereC4/UT-Registration-Plus 2024-02-05 23:12:12 -06:00
08e66c95e3 Now the Day labels are aligned and part of the grid 2024-02-05 23:05:15 -06:00
dd2f696f8d feat: Calendar Grid and Grid Cells (#81) 2024-02-05 23:04:24 -06:00
873be55b8b Filter Function replacing double pop(); 2024-02-05 23:00:42 -06:00
c20fb0827f Space bar 2024-02-05 22:54:23 -06:00
1c0ad51914 Reverted settings.json 2024-02-05 22:54:04 -06:00
d81ec5c2fc Exited Suez Canal 2024-02-05 22:45:04 -06:00
4bc65ec171 Got Delayed in the Suez Canal 2024-02-05 22:24:20 -06:00
e630bc82ee Time Column Being Transported via Crane 2024-02-05 22:23:03 -06:00
36314e0e3b Preparing to re-add hours to the left side 2024-02-05 22:11:58 -06:00
cdc1fc9224 Merge branch 'master' of https://github.com/DereC4/UT-Registration-Plus 2024-02-05 22:11:40 -06:00
be76f4bcc7 Merge branch 'sghsri:main' into master 2024-02-05 22:10:53 -06:00
a71bc0f9c3 THEY'RE EVOLVING CUBES 2024-02-05 22:05:20 -06:00
3d95d03813 THE CUBES HAVE LISTENED TO ME 2024-02-05 22:02:29 -06:00
d1720f3356 Retrying 2024-02-05 21:58:40 -06:00
8a317c590b Tactically Retreating 2024-02-05 21:47:10 -06:00
9ab86cfab1 Off center now 2024-02-05 21:38:34 -06:00
01a3918502 Reverts in Stylesheets 2024-02-05 21:33:59 -06:00
7a014a7aab Screams in Stylesheets 2024-02-05 21:33:40 -06:00
Razboy20
b2b6a06280 refactor: replace classnames with clsx (#78) 2024-02-05 21:27:22 -06:00
618089b17e Format 2024-02-05 21:15:38 -06:00
96dbe40637 Merge branch 'sghsri:main' into master 2024-02-05 21:03:37 -06:00
Dhruv
1b51d65c89 feat: Create icon helper (#77)
* create icon helper

* change getStatusIcon to StatusIcon react component

---------

Co-authored-by: Razboy20 <razboy20@gmail.com>
2024-02-05 18:24:02 -06:00
Abhinav Chadaga
a41cb3ed87 feat: calendar course block component (#75) 2024-02-03 16:43:59 -06:00
Samuel Gunter
13bc06cc6d chore: merge branch 'main' into sgunter/button-component 2024-02-03 15:22:27 -06:00
Razboy20
6521a4b2a9 feat: UnoCSS (TailwindCSS) (Storybook only) (#61)
* feat: unocss (wip)

* feat: unocss (storybook only)

* cleanup unocss config

* revert button.stories.tsx changes
2024-02-03 15:21:19 -06:00
knownotunknown
429b49111a InfoCard implemented 2024-02-03 15:14:07 -06:00
knownotunknown
6e595d21aa Update Chip.stories.tsx 2024-02-03 14:29:07 -06:00
Razboy20
e015a79526 hotfix: icon library resolution (#74) 2024-02-03 14:21:25 -06:00
knownotunknown
80043dc652 Deleted unnecessary imports 2024-02-03 14:20:48 -06:00
knownotunknown
b470bf6996 Chip Implemented
Styled inline since I'll need to rewrite in TailwindCSS anyways
2024-02-03 14:15:18 -06:00
Razboy20
359e65496f hotfix: change material icons to material symbols (#71) 2024-02-03 13:42:32 -06:00
Samuel Gunter
e0bf48805a chore: merge branch 'main' into sgunter/button-component 2024-02-03 13:29:43 -06:00
Samuel Gunter
bb727f70be feat: updated Text component to latest design specification (#70) 2024-02-03 10:20:58 -06:00
ab2cd688fa feat: Calendar Components 3rd Attempt at Merging (#60) 2024-02-03 10:20:30 -06:00
bb2caa2dda Path alias 2024-02-03 00:47:07 -06:00
knownotunknown
dad74ddf4e Drag only on vertical axis
Made this change a while ago, but didn't push since it looks like we might switch to Elie's DnD. Pushing since it looks like there might still be reviewers.
2024-02-03 00:11:46 -06:00
cbf31aecd5 Storybook Story 2024-02-02 22:14:35 -06:00
6aab174618 Imported DAY_MAP 2024-02-02 21:52:46 -06:00
8f2b61d28a Ok 2024-02-02 21:32:38 -06:00
fb6781e17f Colors modules switch 2024-02-02 20:44:52 -06:00
1683d8c48b Docs 2024-02-02 20:42:11 -06:00
e9f95ad3d8 Docs and needed changes 2024-02-02 20:41:14 -06:00
745b83e945 CalendarGridCell 2024-02-02 20:38:28 -06:00
b0dec80364 Camelcase conversion for css modules 2024-02-02 20:27:18 -06:00
Samuel Gunter
03e4ab9d60 chore: merge branch 'main' into sgunter/button-component 2024-02-02 20:26:02 -06:00
9854c9aafa Merge branch 'sghsri:main' into master 2024-02-02 20:25:58 -06:00
Razboy20
f5e8fb5782 chore: update dependencies (#67) 2024-02-02 20:12:55 -06:00
b161e319bd Merge branch 'sghsri:main' into master 2024-02-02 20:09:22 -06:00
knownotunknown
ae0d1a3c67 Revert "rename to course block and fix line height for styling"
This reverts commit 77a1d67af3.
2024-02-02 19:59:52 -06:00
knownotunknown
3da4a395dd Implemented List component
Used CalendarCourseMeeting component in story for demonstration purposes
2024-02-02 13:08:55 -06:00
Razboy20
945e09b49e feat: unplugin-icons (#62) 2024-02-02 11:55:33 -06:00
abhinavchadaga
77a1d67af3 rename to course block and fix line height for styling 2024-02-01 20:55:13 -06:00
abhinavchadaga
856d6cda24 change the default course 2024-01-31 13:10:29 -06:00
abhinavchadaga
cbbea7f810 basic component laid out - missing Text and Right Icon, can add more stories 2024-01-31 03:27:37 -06:00
Samuel Gunter
cedaa27a2c feat: updated Button to v2 design
wip commit
2024-01-31 00:11:01 -06:00
72b7a9d7b1 Calendar Components 3rd Attempt at Merging 2024-01-30 21:17:19 -06:00
Razboy20
9cc299ced6 feat: Storybook for Vite (#52)
* feat: storybook

* feat: option to add figma links

* refactor: resolve pr comments

* chore: fix storybook build

---------

Co-authored-by: Sriram Hariharan <sghsri@gmail.com>
2024-01-30 16:43:30 -06:00
Razboy20
0560a01a55 refactor: Replace Webpack with Vite (#53) 2024-01-24 19:40:30 -06:00
Sriram Hariharan
1629c85818 Merge pull request #47 from doprz/utrp-v2 2023-12-29 10:14:37 -06:00
doprz
5e98f45210 feat: Refactor database initialization code 2023-12-23 09:26:49 -06:00
Sriram Hariharan
56643f9753 bunch of misc changes 2023-11-17 11:11:01 -06:00
Sriram Hariharan
52431747ee wrote all course/schedule background messages with handlers 2023-09-17 20:58:22 -05:00
Sriram Hariharan
6061295e0a simplified typing 2023-09-17 20:00:28 -05:00
Sriram Hariharan
89423d24b4 minor changes 2023-09-17 19:44:50 -05:00
Sriram Hariharan
4f170db07d fixes + added button titles 2023-09-17 19:38:12 -05:00
Sriram Hariharan
aea9b16f98 refactoring courseschedule storage 2023-09-17 19:29:00 -05:00
Sriram Hariharan
9658697d96 fxied 2023-09-17 15:25:14 -05:00
Sriram Hariharan
864afd8dcb created my calendar main 2023-09-17 15:21:45 -05:00
Sriram Hariharan
1fac71dbd1 generating calendar html 2023-09-17 15:20:42 -05:00
Sriram Hariharan
e199a0b766 fixing errors 2023-09-17 14:55:33 -05:00
Sriram Hariharan
4ae2966e98 not using param 2023-09-17 14:29:18 -05:00
Sriram Hariharan
e2c9955b41 inline database destructure 2023-09-17 14:28:45 -05:00
doprz
9f1dcc667d feat: parallelize initializeDB.ts promises 2023-04-18 10:30:13 -05:00
Sriram Hariharan
fcfda3a447 Merge pull request #45 from doprz/bugfix/ModuleNotFoundError-CaseSensitivePathsPlugin
fix: #44 Module Not Found Error
2023-03-30 13:12:01 -05:00
doprz
486b2e4dfc bugfix: fix #44 2023-03-30 10:33:40 -05:00
Sriram Hariharan
ad85c2b816 adding tooltip with which class has conflicts 2023-03-22 22:48:14 -05:00
Sriram Hariharan
2ddfde2642 course conflict highlighting and calculations 2023-03-22 22:16:51 -05:00
Sriram Hariharan
882b5b4e00 schedules working 2023-03-16 00:32:10 -05:00
Sriram Hariharan
6afd372945 multiple schedule suppport kinda 2023-03-15 23:54:07 -05:00
Sriram Hariharan
6d4a4307cf RecruitmentBanner, thank you Lukas 2023-03-12 23:34:58 -05:00
Sriram Hariharan
fe4f0e7ecd infra changes 2023-03-11 22:03:25 -06:00
Sriram Hariharan
32b73da959 moved stores back into shared 2023-03-10 23:38:39 -06:00
Sriram Hariharan
d06b0f9f7a infrastructure for multiple schedules 2023-03-10 23:37:57 -06:00
Sriram Hariharan
f28ab5182c better select styling for semesters for grade dist 2023-03-09 22:26:56 -06:00
Sriram Hariharan
e60242198a query grade distributions working, and filtering by semesters working 2023-03-09 16:11:42 -06:00
Sriram Hariharan
5be0cbbbf1 test 2023-03-09 11:02:30 -06:00
Sriram Hariharan
2462d24214 removed icon from gitignore 2023-03-09 10:59:24 -06:00
Sriram Hariharan
7dd53f3a94 removed the necessity for hardcoded line height variables 2023-03-08 21:12:14 -06:00
Sriram Hariharan
c1910bacb0 finished grade distribution ui 2023-03-08 20:44:34 -06:00
Sriram Hariharan
2562e65d66 some styling 2023-03-08 00:41:04 -06:00
Sriram Hariharan
fe8c2378d2 displaying bar graph, fetching distributions message 2023-03-08 00:26:56 -06:00
Sriram Hariharan
f3bf746c6e textbook button, properly scraping the semester (year and season) from the url" 2023-03-07 22:17:17 -06:00
Sriram Hariharan
353c43c987 lot of refactoring, reorganized course buttons. Now linking to professors directory page 2023-03-07 21:49:41 -06:00
Sriram Hariharan
04a82fb6a6 bunch of refactor and styling changes, coming along nicely 2023-03-07 01:42:26 -06:00
Sriram Hariharan
f48f39e67b refactoring 2023-03-06 23:28:21 -06:00
Sriram Hariharan
1fa67f451a auto generate classes using scss magic, some restyling and refactoring' 2023-03-06 23:28:08 -06:00
Sriram Hariharan
ebeb7d692b created reusable button component, created course info header component, created utility type for Colors, removed typescript-css-modules plugin, and added a threshold to the infinite scroll hook 2023-03-06 22:45:34 -06:00
Sriram Hariharan
950c4a573a added onclose to popup, coursepopup now displaying time info, renamed vars, added compiler for scss to typescript and tsconfig plugins 2023-03-06 21:02:29 -06:00
Sriram Hariharan
9b76f8afa0 line height text, refactored course schedule, added string representation functions to course meeting 2023-03-06 16:51:46 -06:00
Sriram Hariharan
007ade81a0 minor styling 2023-03-06 00:23:20 -06:00
Sriram Hariharan
8b5fabce0c properly generating and formating instructor text 2023-03-06 00:10:03 -06:00
Sriram Hariharan
7401138d87 fixed serialization type in chrome-extension-toolkit, and then updated package version 2023-03-05 23:58:16 -06:00
Sriram Hariharan
ad8a06d831 Link component, Card component, Course Popup component styling, and wrangling with the serialization type" 2023-03-05 22:52:11 -06:00
Sriram Hariharan
6d69cd2548 renamed panel to popup since that's kinda what it is lmao 2023-03-05 20:50:40 -06:00
Sriram Hariharan
295b466505 fixed bug with material icons, and reusable icon component 2023-03-05 19:54:59 -06:00
Sriram Hariharan
1f2374927d added material icons 2023-03-05 17:46:25 -06:00
Sriram Hariharan
6147289b40 created reusable text component, and setup typing for it automatically. also fixed bugs with autoload and scraper so that it would handle appending course name headers 2023-03-05 17:34:56 -06:00
Sriram Hariharan
0956525e94 auto-loading completely done 2023-03-05 14:34:26 -06:00
Sriram Hariharan
2b952d0591 minor reorg 2023-03-04 23:33:59 -06:00
Sriram Hariharan
15e9ff92a8 moved files around 2023-03-04 23:33:39 -06:00
Sriram Hariharan
b7c3d22961 minor changes 2023-03-04 23:24:50 -06:00
Sriram Hariharan
9dbe0d7ff7 passing a scss classname down to a vanilla dom element on react state changes 🤯 2023-03-04 23:20:12 -06:00
Sriram Hariharan
d9739cdb56 decided to add season support 2023-03-04 23:00:01 -06:00
Sriram Hariharan
bc464cd264 lots of UI changes, and keyboard command support 2023-03-04 22:42:51 -06:00
Sriram Hariharan
00b8cd74b6 added ExtensionRoot for consistent styling across injected components, and added a bunch of comments for all the added types and classes 2023-03-04 21:11:04 -06:00
Sriram Hariharan
070c8ea486 added ExtensionRoot for consistent styling across injected components, and added a bunch of comments for all the added types and classes 2023-03-04 21:10:12 -06:00
Sriram Hariharan
46282a0406 added deviceId, ExtensionStore, working on CoursePopup 2023-03-04 20:33:35 -06:00
Sriram Hariharan
e99ba5864a CourseScraper completely done
'
2023-03-04 20:14:26 -06:00
Sriram Hariharan
c9684beb5b wip scraping infra 2023-03-04 11:51:56 -06:00
Sriram Hariharan
2d940493a3 beginning course scraping from row, and created assets folder with departments.json 2023-03-03 23:53:54 -06:00
Sriram Hariharan
94e74deb24 updated Course schema 2023-03-03 23:43:44 -06:00
Sriram Hariharan
f47ad8272f refactoring 2023-03-03 23:29:06 -06:00
Sriram Hariharan
39016c93aa infinite scroll support 2023-03-03 23:13:31 -06:00
Sriram Hariharan
e9c420a873 injecting into table, created table header, and buttons for each row 2023-03-03 21:57:00 -06:00
Sriram Hariharan
beb61176c1 refactoring, using different pattern for page injection and reusing same pattern for both popup and content scripts 2023-03-03 21:13:43 -06:00
Sriram Hariharan
4ed52a3c9f analyzing page types and populating search inputs 2023-03-03 19:58:53 -06:00
Sriram Hariharan
723caca417 fixed some bugs, and updated dev dashboard useffect" 2023-03-03 18:58:19 -06:00
Sriram Hariharan
57d704b759 added colors and design system to docs 2023-03-03 15:47:35 -06:00
Sriram Hariharan
e9acddfa16 added colors and 2 main components for 2 different contexts 2023-03-03 11:40:54 -06:00
Sriram Hariharan
f3ee5a0854 added license and fixed webpack plugin 2023-03-03 11:08:16 -06:00
Sriram Hariharan
5203d3acf8 clean-webpack-plugin 2023-03-03 11:02:46 -06:00
Sriram Hariharan
b0eba78697 fixing some compiler issues 2023-02-22 22:59:18 -06:00
Sriram Hariharan
bce2717088 using my boilerplate yuh 2023-02-22 22:51:38 -06:00
Sriram Hariharan
21d7056aae deleted everything lmao 2023-02-22 22:35:37 -06:00
253 changed files with 47869 additions and 13590 deletions

1
.env.development Normal file
View File

@@ -0,0 +1 @@
MANIFEST_KEY=

0
.env.production Normal file
View File

96
.eslintignore Normal file
View File

@@ -0,0 +1,96 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories and management
node_modules/
jspm_packages/
package.json
package-lock.json
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# next.js build output
.next
# Webpack-built output
/dist
# Extension archives
/build
# VsCode
.vscode/*
!.vscode/launch.json
!.vscode/tasks.json
# macOS
.DS_Store
# Terraform
.terraform
# development dependencies
.dev/vue-devtools
.dev/browser-profiles
# IntelliJ
.idea
# Sylelint IntelliJ Plugin Requirement
.stylelintrc.json
# Local environment settings
.env.local
*.svg
config
.eslintrc.js
!.storybook

216
.eslintrc Normal file
View File

@@ -0,0 +1,216 @@
{
"root": true,
"env": {
"browser": true,
"es6": true,
"node": true,
"webextensions": true
},
"ignorePatterns": [
"*.html",
"tsconfig.json"
],
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:storybook/recommended",
"airbnb-base",
"airbnb/rules/react",
"airbnb-typescript",
"@unocss",
"prettier",
],
"plugins": [
"import",
"jsdoc",
"react-prefer-function-component"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly",
"debugger": true,
"browser": true,
"context": true,
"JSX": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"ecmaVersion": 2022,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true,
"modules": true,
"experimentalObjectRestSpread": true
}
},
"settings": {
"react": {
"version": "detect"
},
"jsdoc": {
"mode": "typescript"
},
"import/parsers": {
"@typescript-eslint/parser": [
".ts",
".tsx"
]
},
"import/resolver": {
"typescript": {
"alwaysTryTypes": true,
"project": "./tsconfig.json"
}
}
},
"rules": {
"prefer-const": [
"off",
{
"destructuring": "any",
"ignoreReadBeforeAssign": false
}
],
"no-plusplus": "off",
"no-inner-declarations": "off",
"sort-imports": "off",
"no-case-declarations": "off",
"no-unreachable": "warn",
"no-constant-condition": "error",
"space-before-function-paren": "off",
"no-undef": "off",
"no-return-await": "off",
"@typescript-eslint/return-await": "off",
"@typescript-eslint/no-shadow": [
"off"
],
"@typescript-eslint/no-use-before-define": [
"off"
],
"class-methods-use-this": "off",
"react-hooks/exhaustive-deps": "warn",
"@typescript-eslint/lines-between-class-members": "off",
"no-param-reassign": [
"error",
{
"props": false
}
],
"no-console": "off",
"consistent-return": "off",
"react/destructuring-assignment": "off",
"import/prefer-default-export": "off",
"no-promise-executor-return": "off",
"import/no-cycle": "off",
"import/no-extraneous-dependencies": "off",
"react/jsx-props-no-spreading": "off",
"keyword-spacing": [
"error",
{
"before": true,
"after": true
}
],
"no-continue": "off",
"space-before-blocks": [
"error",
{
"functions": "always",
"keywords": "always",
"classes": "always"
}
],
"react/jsx-filename-extension": [
1,
{
"extensions": [
".tsx"
]
}
],
"react/no-deprecated": "warn",
"react/prop-types": "off",
"react-prefer-function-component/react-prefer-function-component": [
"warn",
{
"allowComponentDidCatch": false
}
],
"react/function-component-definition": "off",
"react/button-has-type": "off",
"jsdoc/require-param-type": "off",
"jsdoc/require-returns-type": "off",
"jsdoc/newline-after-description": "off",
"react/require-default-props": "off",
"jsdoc/require-jsdoc": [
"warn",
{
"enableFixer": false,
"publicOnly": true,
"checkConstructors": false,
"require": {
"ArrowFunctionExpression": true,
"ClassDeclaration": true,
"ClassExpression": true,
"FunctionExpression": true
},
"contexts": [
"MethodDefinition:not([key.name=\"componentDidMount\"]):not([key.name=\"render\"])",
"ArrowFunctionExpression",
"ClassDeclaration",
"ClassExpression",
"ClassProperty:not([key.name=\"state\"]):not([key.name=\"componentDidMount\"])",
"FunctionDeclaration",
"FunctionExpression",
"TSDeclareFunction",
"TSEnumDeclaration",
"TSInterfaceDeclaration",
"TSMethodSignature",
"TSModuleDeclaration",
"TSTypeAliasDeclaration"
]
}
],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/space-before-function-paren": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-empty-interface": "warn",
"import/no-restricted-paths": [
"error",
{
"zones": [
{
"target": "./src/background",
"from": "./src/views",
"message": "You cannot import into the `background` directory from the `views` directory (i.e. content script files) because it will break the build!"
},
{
"target": "./src/views",
"from": "./src/background",
"message": "You cannot import into the `views` directory from the `background` directory (i.e. background script files) because it will break the build!"
},
{
"target": "./src/shared",
"from": "./",
"except": [
"./src/shared",
"./node_modules"
],
"message": "You cannot import into `shared` from an external directory."
}
]
}
],
"import/extensions": "off",
"no-restricted-syntax": [
"error",
"ForInStatement",
"LabeledStatement",
"WithStatement"
]
}
}

26
.github/workflows/chromatic.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: "Chromatic"
on: [push, pull_request_target]
jobs:
chromatic:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 8
- name: Install dependencies
run: pnpm install
- name: Publish to Chromatic
uses: chromaui/action@latest
with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
exitZeroOnChanges: true
autoAcceptChanges: "main"

25
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Create Release
on:
push:
branches:
- production
- preview
jobs:
build:
name: build extension & create release
runs-on: ubuntu-latest
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
steps:
- uses: actions/checkout@master
- name: Get file permission
run: chmod -R 777 .
- name: Install dependencies
run: npm ci
- name: Release with semantic-release
id: semantic-release
run: npx --no-install semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

43
.github/workflows/validate-pr.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: Validate PR Title
# thank you ben limmer for this workflow:
# https://github.com/blimmer/semantic-release-demo-2/blob/main/.github/workflows/lint-pr.yml
on:
pull_request_target:
types:
- opened
- reopened
- edited
- synchronize
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v3.2.6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Post Conventional Commit Comment (on failure)
uses: jungwinter/comment@v1
id: conventional-commit-help
with:
type: create
issue_number: ${{ github.event.pull_request.number }}
token: ${{ secrets.GITHUB_TOKEN }}
body: |
Your pull request title did not conform to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) standards. Our upcoming automated release pipeline will automatically determine
the proper release version based on your pull request title.
**Cheat Sheet**
- feat: A new feature
- fix: A bug fix
- docs: Documentation only changes
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
- refactor: A code change that neither fixes a bug nor adds a feature
- perf: A code change that improves performance
- test: Adding missing tests or correcting existing tests
- build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
- ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
- chore: Other changes that don't modify src or test files
- revert: Reverts a previous commit
if: ${{ failure() }}

211
.gitignore vendored Normal file
View File

@@ -0,0 +1,211 @@
# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,node,react,storybookjs
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,macos,node,react,storybookjs
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
### react ###
.DS_*
**/*.backup.*
**/*.back.*
node_modules
*.sublime*
psd
thumb
sketch
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,node,react,storybookjs
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
v18.12.1

19
.prettierignore Normal file
View File

@@ -0,0 +1,19 @@
*.css
# macOS
.DS_Store
# Webpack-built output
/dist
# Extension archives
/build
# Optional npm cache directory
.npm
# Dependency directories and management
node_modules/
jspm_packages/
package.json
package-lock.json
# Coverage directory used by tools like istanbul
coverage

12
.prettierrc.json Normal file
View File

@@ -0,0 +1,12 @@
{
"useTabs": false,
"printWidth": 120,
"tabWidth": 4,
"singleQuote": true,
"trailingComma": "es5",
"arrowParens": "avoid",
"bracketSpacing": true,
"bracketSameLine": false,
"semi": true,
"jsxSingleQuote": true
}

37
.releaserc.json Normal file
View File

@@ -0,0 +1,37 @@
{
"branches": [
"production",
{
"name": "preview",
"channel": "alpha",
"prerelease": "alpha"
}
],
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits"
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits"
}
],
[
"@semantic-release/exec",
{
"prepareCmd": "SEMANTIC_VERSION=${nextRelease.version} npm run build"
}
],
[
"@semantic-release/github",
{
"assets": "build/**/artifacts/*.*",
"failComment": false
}
]
]
}

18
.storybook/main.ts Normal file
View File

@@ -0,0 +1,18 @@
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-designs'],
framework: {
name: '@storybook/react-vite',
options: {
builder: {
viteConfigPath: '.storybook/vite-storybook.config.ts',
},
},
},
docs: {
autodocs: 'tag',
},
};
export default config;

24
.storybook/preview.tsx Normal file
View File

@@ -0,0 +1,24 @@
import type { Preview } from '@storybook/react';
import React from 'react';
import ExtensionRoot from 'src/views/components/common/ExtensionRoot/ExtensionRoot';
const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
decorators: [
Story => (
<ExtensionRoot>
<Story />
</ExtensionRoot>
),
],
};
export default preview;

View File

@@ -0,0 +1,28 @@
import react from '@vitejs/plugin-react-swc';
import { resolve } from 'path';
import UnoCSS from 'unocss/vite';
import Icons from 'unplugin-icons/vite';
import { defineConfig } from 'vite';
const root = resolve(__dirname, '../src');
const pagesDir = resolve(root, 'pages');
const assetsDir = resolve(root, 'assets');
const publicDir = resolve(__dirname, '../public');
console.log(root);
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), UnoCSS(), Icons({ compiler: 'jsx', jsx: 'react' })],
resolve: {
alias: {
src: root,
'@assets': assetsDir,
'@pages': pagesDir,
'@public': publicDir,
'@shared': resolve(root, 'shared'),
'@background': resolve(pagesDir, 'background'),
'@views': resolve(root, 'views'),
},
},
});

5
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"recommendations": [
"clinyong.vscode-css-modules"
]
}

18
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Run current script",
"runtimeExecutable": "npx",
"runtimeArgs": [
"tsx"
],
"program": "${file}",
"skipFiles": [
"<node_internals>/**"
],
}
]
}

40
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,40 @@
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[yaml]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[svg]": {
"editor.defaultFormatter": "jock.svg"
},
"material-icon-theme.activeIconPack": "react",
"material-icon-theme.folders.associations": {
"analytics": "Json",
"background": "Delta",
"navigation": "Routes",
"logging": "log",
"popup": "Layout",
"storage": "Database",
},
"material-icon-theme.files.associations": {
"tsconfig.extension.json": "tsconfig",
"tsconfig.build.json": "tsconfig",
"tsconfig.test.json": "tsconfig"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"typescript.tsdk": "node_modules/typescript/lib",
}

13
@types/css-imports.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
declare module '*.module.css' {
const classes: { [key: string]: string };
export default classes;
}
declare module '*.module.scss' {
const classes: { [key: string]: string };
export default classes;
}
declare module '*.mp3' {
const src: string;
export default src;
}

22
@types/environment.d.ts vendored Normal file
View File

@@ -0,0 +1,22 @@
declare global {
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production';
CI?: string;
/** set this to make sure the extension id is the same for unpacked extensions
* @see https://developer.chrome.com/docs/apps/app_identity/#copy_key */
MANIFEST_KEY?: string;
/**
* The Node semantic versioning-compatible version of the extension. For preview-style releases, this variable
* converts versions like 1.0.0.100 to 1.0.0-beta.1.
*/
SEMANTIC_VERSION?: string;
}
}
type Environment = typeof process.env.NODE_ENV;
}
// If this file has no import/export statements (i.e. is a script)
// convert it into a module by adding an empty export statement.
export {};

6
@types/svg-import.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
declare module "*.svg" {
import { ReactElement, SVGProps } from "react";
const ReactComponent: (props: SVGProps<SVGElement>) => ReactElement;
export default ReactComponent;
}

21
LICENSE.md Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Sriram Hariharan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,40 +1,30 @@
# UT Registration Plus
# UT Registration Plus ## Built Using
## (or Sriram's Sexy Scheduling Script)
[Try it for yourself on the Chrome Web Store](https://chrome.google.com/webstore/detail/hboadpjkoaieogjimneceaahlppnipaa)
- React 18
- TypeScript
- Vite 5
- ESLint
- Prettier
- Semantic-Release
- Custom Messaging & Storage Wrappers
We've all been there. 20 tabs of Rate My Professor, Catalyst, and the UT Course Catalog open and you still don't know what classes to take. ## Getting Started
This extension tries to streamline most of the unnecessary steps and headaches of registering for classes at UT Austin.
1. Clone this repo
2. Run `pnpm install` to install and patch all the required dependencies
- For each class on the UT Course Catalog it provides a "breakdown" popup, with quick and easy links to the RateMyProfessor and eCIS pages of the professor, as well as syllabi from when the professor taught the class in the past. - If you want to run the development build:
- Gets the course description and highlight the important information like prerequisites, restrictions, etc. - Run `pnpm run dev`
- Shows an aggregate graph of the grade distributions for when the professor taught the class in the past. - If you want to build the extension for production:
- Gives you the ability to "Save Courses" and view them in the extension popup. This lets you see any schedule conflicts, and makes copy-pasting the unique code much easier when you're actually registering. - Run `pnpm build`
- Highlights and crosses-out what courses on the UT Course Catalog would conflict with your currently saved courses, making selecting courses that fit with your schedule so much easier. You may have to rename the `__uno.css.js` to `uno.css.js` in dist
- Display's a weekly schedule based on your saved courses Go to chrome://extensions, ensure you have "Developer Mode" enabled, and click 'Load unpacked'
<p align="center">
<img src="https://lh3.googleusercontent.com/X5hqHGPU-F2lF3_shT2injxd40eFYXLJfZVxpU1v2w1YvFRW1jQMEXu2yzWHKKpqn5huJL-NEHY=w640-h400-e365">
</p>
<p align="center">
<img src="https://lh3.googleusercontent.com/ZCRxTFKFjpGm5ZRMv2iHzMqdnrQHUx_Ih_XhGhy2O4Yn29YccvU5yXXrWXKuVKsNAmEJJ0As4xc=w640-h400-e365">
</p>
<p align="center">
<img src="https://lh3.googleusercontent.com/3iRi25wDnVqgzc7pnYUXQq1TvdPpAeDjCmIF9hLU-WKmlchEYQUh_xU-XV00fEbKUr2XVKGkOw=w640-h400-e365">
</p>
<p align="center">
<img src="https://lh3.googleusercontent.com/x95blI5D1mseNPLOtHETlLmoVtHm0eeye9uyeWSDd5W6m6fSoZxMMMyQTGUFo5swoTgRivGVyw=w640-h400-e365">
</p>
<p align="center">
<img src="https://lh3.googleusercontent.com/bbey8OGOTtJWUaHGVIU5wewbWg6X6s-gjD15RwXHhvgH_9kax2mE4bcrjem_iZGH-q5z6NT7g94=w640-h400-e365">
</p>
# 2.0 coming soon....
Navigate to the 'dist' folder and click 'select' to import the extension

View File

@@ -1,42 +0,0 @@
<!DOCTYPE html>
<!-- This file is the html file serving the "My Schedule / Calendar" page. -->
<head>
<link rel='stylesheet' href='css/fullcalendar.min.css' />
<link rel='stylesheet' href='css/_materialFullCalendar.css' />
<script src='js/lib/jquery-3.3.1.min.js'></script>
<script src='js/lib/moment.min.js'></script>
<script src='js/lib/fullcalendar.min.js'></script>
</head>
<body>
<div style='display:flex'>
<div id='calendar' style="flex-grow: 1"></div>
<div class="card" id="header"
style="text-align:center;margin:5px 0px 0px 15px;display: inline-table;padding-bottom: 5px;">
<h1 id='hours'
style="font-size:30px;font-weight:500; border-bottom: 3px solid black;display: inline-block;padding-bottom: 5px;margin-bottom: 5px;">
0
hours</h1>
<h1 id='num' style="font-size:20px;font-weight:500; margin: 2px;">0
Courses</h1>
<br>
<div style="margin:5px;display: flex;flex-direction: column;">
<button id="clear" class="matbut"
style="font-size:medium; background:#4CAF50;margin: 10px;white-space: nowrap;text-align: center;">Clear
All</button>
<button id="save" class="matbut"
style="font-size:medium; background:#FF9800;margin: 10px;white-space: nowrap;text-align: center;">Save
as PNG</button>
<button id="export" class="matbut"
style="font-size:medium; background:#FF0000;margin: 10px;white-space: nowrap;text-align: center;">Export
Cal</button>
</div>
</div>
</div>
</body>
<script src='js/config.js'></script>
<script src='js/lib/html2canvas.min.js'></script>
<script src='js/lib/ics.min.js'></script>
<script src='js/Template.js'></script>
<script src='js/util.js'></script>
<script src='js/calendar.js'></script>

View File

@@ -1,344 +0,0 @@
/*
This is the Material Design theme for FullCalendar Weekly Agenda view
Creation Date: Aug 19th 2015
Author: Jacky Liang
Version: FullCalendar 2.4.0
Tested Using the Following FC Settings:
editable: false,
handleWindowResize: true,
weekends: false, // Hide weekends
defaultView: 'agendaWeek', // Only show week view
header: false, // Hide buttons/titles
minTime: '07:30:00', // Start time for the calendar
maxTime: '22:00:00', // End time for the calendar
columnFormat: {
week: 'ddd' // Only show day of the week names
},
displayEventTime: true,
allDayText: 'Online/TBD'
Note: This has NOT been tested on Monthly or Daily views.
Colors: Use the following - https://www.google.com/design/spec/style/color.html#color-color-palette
at the 700 level. An opacity of 0.65 is automatically applied to the
700 level colors to generate a soft and pleasing look.
Color were applied to each event using the following code:
events.push({
title: 'This is a Material Design event!',
start: 'someStartDate',
end: 'someEndDate',
color: '#C2185B'
});
*/
.fc-state-highlight {
opacity: 0;
border: none;
}
/* Styling for each event from Schedule */
.fc-time-grid-event.fc-v-event.fc-event {
border-radius: 4px;
border: none;
padding: 5px;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.3);
transition: 0.3s;
opacity: 1;
}
.html2canvas-container {
width: 3000px !important;
height: 3000px !important;
}
.fc-time-grid-event.fc-v-event.fc-event:hover {
box-shadow: 0 8px 12px 0 rgba(0, 0, 0, 0.3);
}
/* Bolds the name of the event and inherits the font size */
.fc-event {
font-size: small !important;
font-weight: bold !important;
}
/* Remove the header border from Schedule */
.fc td,
.fc th {
border-style: ridge !important;
border-width: 1px !important;
padding: 4px 3px 0px 3px !important;
vertical-align: top !important;
border-left-width: 0;
}
.fc-row fc-widget-header {
border-color: transparent;
}
.fc td {
border-top-width: 0;
padding: 3px !important;
}
.fc-widget-header {
background-color: #cc5500;
color: white;
}
/* Inherits background for each event from Schedule. */
.fc-event .fc-bg {
z-index: 1 !important;
background: inherit !important;
opacity: 0.25 !important;
}
/* Normal font weight for the time in each event */
.fc-time-grid-event .fc-time {
font-weight: normal !important;
}
/* Apply same opacity to all day events */
.fc-ltr .fc-h-event.fc-not-end,
.fc-rtl .fc-h-event.fc-not-start {
opacity: 0.65 !important;
margin-left: 12px !important;
padding: 5px !important;
}
/* Apply same opacity to all day events */
.fc-day-grid-event.fc-h-event.fc-event.fc-not-start.fc-end {
opacity: 0.65 !important;
margin-left: 12px !important;
padding: 5px !important;
}
/* Material design button */
.matbut {
border: none;
outline: none;
cursor: pointer;
color: white;
margin: 10px 10px 10px 0px;
padding: 10px 10px;
border-radius: 10px;
font-size: medium;
font-style: bold;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4);
}
.matbut {
position: relative;
overflow: hidden;
}
.matbut:after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 5px;
height: 5px;
background: rgba(255, 255, 255, 0.5);
opacity: 0;
border-radius: 100%;
transform: scale(1, 1) translate(-50%);
transform-origin: 50% 50%;
}
@keyframes ripple {
0% {
transform: scale(0, 0);
opacity: 1;
}
20% {
transform: scale(25, 25);
opacity: 1;
}
100% {
opacity: 0;
transform: scale(40, 40);
}
}
.matbut:focus:not(:active)::after {
animation: ripple 1s ease-out;
}
.fc-button {
display: inline-block;
position: relative;
cursor: pointer;
min-height: 36px;
min-width: 88px;
line-height: 36px;
vertical-align: middle;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
text-align: center;
border-radius: 2px;
box-sizing: border-box;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
outline: none;
border: 0;
padding: 0 6px;
margin: 6px 8px;
letter-spacing: 0.01em;
background: transparent;
color: currentColor;
white-space: nowrap;
text-transform: uppercase;
font-weight: 500;
font-size: 14px;
font-style: inherit;
font-variant: inherit;
font-family: inherit;
text-decoration: none;
overflow: hidden;
-webkit-transition: box-shadow 0.4s cubic-bezier(0.25, 0.8, 0.25, 1), background-color 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
transition: box-shadow 0.4s cubic-bezier(0.25, 0.8, 0.25, 1), background-color 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.fc-button:hover {
background-color: rgba(158, 158, 158, 0.2);
}
.fc-button:focus,
.fc-button:hover {
text-decoration: none;
}
/* The active button box is ugly so the active button will have the same appearance of the hover */
.fc-state-active {
background-color: rgba(158, 158, 158, 0.2);
}
/* Not raised button */
.fc-state-default {
box-shadow: None;
}
.modal {
display: none;
position: fixed;
z-index: 1;
padding-top: 300px;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgb(0, 0, 0);
background-color: rgba(0, 0, 0, 0.4);
}
#classname {
font-weight: bold;
margin-bottom: -10px;
font-size: large;
}
.modal-content {
background-color: #fefefe;
margin: auto;
max-height: 85%;
overflow-y: auto;
padding: 15px;
border: 1px solid #888;
width: 35%;
}
#prof {
font-size: medium;
margin-bottom: -5px;
}
#info {
margin: 10px;
}
body a:link,
body a:visited {
font-weight: bold;
color: #3c87a3;
}
.time {
margin-left: auto;
margin-right: auto;
margin-bottom: 5px;
font-size: medium;
}
.fc td,
.fc th {
border-color: #E7E7E7;
}
.fc-time-grid .fc-slats td {
height: 1.5em;
border-bottom: initial;
border-color: #E7E7E7;
}
/* The Close Button */
.close {
color: #aaaaaa;
float: right;
padding: 5px;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}
.card {
transition: 0.3s;
margin-bottom: 5px;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
}
.card:hover {
box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.2), 0 4px 20px 0 rgba(0, 0, 0, 0.19);
}
.cardcontainer {
padding: 2px 16px;
display: block;
transition: width 300ms ease-in-out, height 300ms ease-in-out;
}
tbody {
border-width: 0px;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,81 +0,0 @@
.version {
padding: 0px 5px 5px 0px;
text-align: right;
}
.creator-tag {
margin: 10px 5px 5px 0px;
text-align: center;
}
.options-card {
width: 400px;
margin-left: auto;
margin-right: auto;
height: auto;
padding-bottom: 5px;
}
#version-container {
margin-left: auto;
margin-right: auto;
width: 400px;
}
.options-header {
padding: 16px 16px 0px 16px;
font-size: 20px;
}
#contributors_container {
text-align: center;
margin-top: 30;
padding: 5px 20px 20px 20px;
width: auto;
margin-right: 20%;
margin-left: 20%;
margin-top: 20px;
}
#contributor-list {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.contributor-card {
cursor: pointer;
padding: 5px;
max-width: 1200px;
margin: 0 auto;
text-align: center;
display: grid;
grid-gap: 1rem;
}
.contributor-card img {
width: 200px;
height: 200px;
}
.contributor-name {
font-weight: bold;
margin: 0;
}
.contributor-username {
margin: 0 0 5px 0;
font-style: italic;
}
.contributor-title {
margin-bottom: 0;
font-size: 15px;
}
.open-source-tag {
padding: 10px;
font-size: 16px;
}

View File

@@ -1,563 +0,0 @@
.card {
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
transition: 0.3s;
margin: 5px;
}
.card:hover {
box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.2), 0 4px 20px 0 rgba(0, 0, 0, 0.19);
}
.container {
padding: 3px 16px 3px 12px;
}
h2 {
padding: 10px 0px 0 10px;
}
li {
width: 350px;
}
body {
min-width: 370px;
min-height: 400px;
position: relative;
}
.conflict_message{
font-size:small;
font-weight:bold;
color:red;
margin:5px 5px 5px 10px;
}
.course_list {
list-style-type: none;
padding: 5px;
margin-bottom: 30px;
}
.course_list_card {
cursor: pointer;
}
.course_list_item {
padding: 0px 5px 5px 5px;
overflow-y: auto;
max-height:400px;
}
.course_list_item_subtext {
font-size:medium;
}
.empty_message {
font-weight: normal;
font-size: large;
margin: 60px 30px 200px 30px;
text-align: center;
}
#empty #main {
margin-bottom: 5px;
}
#empty span {
font-size: small;
display: table;
margin: 0 auto;
font-weight: bold;
}
.time_line_days {
display:inline-block;
width: 20%;
}
.time_line_hours {
margin-left:10px;
display:inline-block;
width: 50%;
text-align:center;
}
.time_line_location {
float:right;
display:inline-block;
text-align:right;
width: 25%;
}
.time_line_location_link {
color:#3c87a3;
text-decoration:none;
}
.more_info_button{
background: #2196F3;
}
.remove_button {
background: #F44336;
}
.register_button {
background: #4CAF50;
}
.settings_button {
margin-right: 2px;
border: 0px;
border-radius: 50%;
transition: 0.3s;
opacity: 0.5;
}
.settings_button:focus {
outline: 0;
}
.settings_button:hover {
box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.16), 0 4px 15px 0 rgba(0, 0, 0, 0.12);
opacity: 1;
}
.selected {
box-shadow: 0 0 0 1pt #FF9800;
}
.settings_button:focus:after:hover {
outline: 0;
transition: 0.3s;
}
.copy_button {
background-color: transparent;
padding: 0px;
border: none;
font-size: 15px;
cursor: pointer;
border-radius: 50%;
transition: .2s;
}
.copy_button:focus {
outline: 0;
}
.shadow {
box-shadow: 0 2px 20px 0 rgba(0, 0, 0, 0.2), 0 4px 20px 0 rgba(0, 0, 0, 0.16);
}
i {
padding: 4px 0px;
}
.copy_button_icon {
color:white;
float:left;
border-radius: 50%;
padding: 3px;
text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.16);
font-size: x-large;
}
.header_container{
border-radius: 7px;
}
.header_buttons {
padding: 5px 10px 5px 10px;
display: flex;
justify-content: space-between;
}
.header_button {
font-size:15px !important;
margin: 7px !important;
}
.clear_button {
background:#4CAF50;
}
.ris_button {
background:#FF9800;
}
.schedule_button {
background: #FF0000;
}
.settings {
position: absolute;
bottom: 0px;
right: 0px;
display: flex;
vertical-align: middle;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
transition: 0.3s;
padding: 7px 5px 5px 7px;
margin: 0px 5px 0px 0px;
border-radius: 7px;
}
.material_button {
border: none;
outline: none;
cursor: pointer;
color: white;
margin: 10px 10px 10px 0px;
padding: 10px 10px;
border-radius: 10px;
font-size: medium;
font-style: bold;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4);
position: relative;
overflow: hidden;
}
.course_name_truncate_box {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 80%;
color:white;
margin:5px;
display:inline-block;
font-size:large;
align-items:center;
}
.settings_divider {
margin-bottom: 0px;
display:inline-block;
}
.search_button {
background-color:white;
margin-left: 5px;
margin-right: 3px;
}
.course_list_item_options {
display: none;
}
.import_button {
background-color:white;
}
.options_button {
background-color:white;
margin-right: 0px;
}
.settings_icon {
color:#FF9800;
}
.course_list_item_time_box {
font-weight:bold;
padding:10px;
margin:0px 5px 0px 15px;
font-size:small;
}
.course_list_item_options_button_container {
border-radius:0px;
}
.course_list_item_options_buttons{
float:right;
margin:5px;
}
.arrow {
float:right;
font-size:small;
display:inline-block;
margin-top:10px;
color:white;
font-family: sans-serif;
}
.material_button:after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 5px;
height: 5px;
background: rgba(255, 255, 255, 0.5);
opacity: 0;
border-radius: 100%;
transform: scale(1, 1) translate(-50%);
transform-origin: 50% 50%;
}
.card:after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 5px;
height: 5px;
background: rgba(255, 255, 255, 0.5);
opacity: 0;
border-radius: 100%;
transform: scale(1, 1) translate(-50%);
transform-origin: 50% 50%;
}
.modal {
display: none;
position: fixed;
z-index: 1;
padding-top: 100px;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgb(0, 0, 0);
background-color: rgba(0, 0, 0, 0.4);
}
.modal-content {
background-color: #fefefe;
margin: auto;
max-height: 85%;
overflow-y: auto;
padding: 20px;
border: 1px solid #888;
width: 50%;
}
/* The Close Button */
.close {
color: #aaaaaa;
float: right;
padding: 5px;
font-size: 28px;
font-weight: bold;
}
@keyframes ripple {
0% {
transform: scale(0, 0);
opacity: 1;
}
20% {
transform: scale(25, 25);
opacity: 1;
}
100% {
opacity: 0;
transform: scale(40, 40);
}
}
.material_button:focus:not(:active)::after {
animation: ripple 1s ease-out;
}
.card:focus:not(:active)::after {
animation: ripple 1s ease-out;
}
input {
border-radius: 5px;
color: rgba(0, 0, 45, 0.48)
}
input:focus {
outline: 0;
}
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
margin: 0;
}
#search-popup {
background: white;
color: #747474;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
position: absolute;
margin: auto;
bottom: 42px;
right: 20px;
padding: 10px;
border-radius: inherit;
width: 120px;
}
.flex-container {
display: flex;
flex-direction: column;
}
.item {
flex: 1 1 auto;
}
.select-style {
border: 1px solid #979797;
margin: 5px 0px;
border-radius: 5px;
display: none;
background: transparent no-repeat 90% 50%;
position: relative;
}
.select-style:after {
content: '\e5c5';
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
color: rgba(0, 0, 45, 0.48);
position: absolute;
pointer-events: none;
bottom: 0px;
right: 0px;
}
.select-style select {
padding: 5px 8px;
border: none;
color: rgba(0, 0, 45, 0.48);
box-shadow: none;
background: transparent;
background-image: none;
-webkit-appearance: none;
appearance: none;
width: 120px;
word-break: normal;
-ms-word-break: normal;
overflow: visible;
}
.select-style select:focus {
outline: none;
}
.class_id_input {
display: none;
border: 1px solid #979797;
border-radius: 5px;
margin: 10px 0px;
padding: 5px 8px;
}
.class_id_input::placeholder {
color: rgba(0, 0, 45, 0.48);
}
.search-button {
float: right;
font-size: inherit;
padding: 7px 11px;
margin: 8px 0px;
background-color: #FF9800;
}
#import-export-popup {
background: white;
color: #747474;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
position: absolute;
margin: auto;
bottom: 42px;
right: 10px;
border-radius: inherit;
width: 140px;
}
.simple-menu-option {
display: inline-block;
color: rgba(0, 0, 45, 0.48);
border: none;
background-color: white;
font-size: 15px;
overflow: hidden;
vertical-align: middle;
cursor: pointer;
text-align: left;
padding: 10px 0px 5px 5px;
border-radius: 7px;
}
.simple-menu-option i {
font-size: 19px;
vertical-align: middle;
margin-top: 0px;
margin-left: 0px;
margin-right: 3px;
margin-bottom: 3px;
}
.simple-menu-option:hover {
background-color: rgba(177, 175, 175, 0.200);
transition-duration: 0.4s;
/* color: #FF9800; */
}
.simple-menu-option:focus {
outline: none;
}
.hide {
display: none !important;
}
.meta{
margin:0;
color:#FF9800;
position: absolute;
bottom: 0px;
left: 0px;
vertical-align: middle;
padding: 7px 5px 5px 7px;
margin: 0px 5px 0px 5px;
font-size: 10px;
}
.meta-metric{
font-size: 20px;
font-weight: bold;
}
.meta-container{
margin: 5px 5px 10px 5px;
}
.input-box{
color: rgba(0, 0, 45, 0.48);
border: 1px solid #8C8C8C;
font-size: 11px;
padding: 5px;
border-radius: 7px;
width: 90%;
}
.input-box::placeholder {
color: rgba(0, 0, 45, 0.345);
}

View File

@@ -1,308 +0,0 @@
.modal {
display: none;
position: fixed;
z-index: 1;
padding-top: 75px;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgb(0, 0, 0);
background-color: rgba(0, 0, 0, 0.4);
}
.modal-content {
background-color: #fefefe;
margin: auto;
max-height: 85%;
overflow-y: auto;
padding: 20px;
border: 1px solid #888;
width: 50%;
}
.loader {
border: 10px solid #f3f3f3;
border-top: 10px solid #FF9800;
border-radius: 50%;
width: 50px;
height: 50px;
display: none;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.close {
color: #aaaaaa;
float: right;
padding: 5px;
font-size: 28px;
font-weight: bold;
}
.title {
font-size: x-large;
font-weight: bold;
padding-top: 5px;
line-height: 1;
padding-left: 5px;
margin: 5px 0px 5px 0px;
}
.distButton {
vertical-align: bottom;
}
.description {
padding: 5px;
font-size: 15px;
font-weight: normal;
}
.chartloader {
position: absolute;
margin-left: auto;
margin-right: auto;
margin-top: 50px;
left: 0;
right: 0;
}
.profname {
margin-left: 5px;
padding-bottom: 5px;
font-size: medium;
margin-top: 5px;
}
.dateTimePlace {
margin-left: 5px;
margin-bottom: 0px;
margin-top: 0px;
font-size: smaller;
font-weight: bold;
}
#chartcontainer {
max-width: 100%;
height: 250px;
}
#chart {
min-width: auto;
max-width: 100%;
height: 250px;
margin: 0 auto;
z-index: 1;
}
.card {
transition: 0.3s;
margin-bottom: 10px;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
}
.card:hover {
box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.2), 0 4px 20px 0 rgba(0, 0, 0, 0.19);
}
.cardcontainer {
padding: 2px 16px;
transition: width 300ms ease-in-out, height 300ms ease-in-out;
}
.description {
padding: 10px;
}
.close:hover,
.close:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}
.topbuttons .material-button {
display: inline-block;
}
.rmp-button {
}
.ecis-button {
}
.textbook-button{
}
.material-button {
border: none;
outline: none;
cursor: pointer;
color: white;
margin: 10px 10px 10px 0px;
padding: 10px 10px;
border-radius: 10px;
font-size: medium;
font-style: bold;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4);
background: #ff9800;
position: relative;
overflow: hidden;
}
.material-button:after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 5px;
height: 5px;
background: rgba(255, 255, 255, 0.5);
opacity: 0;
border-radius: 100%;
transform: scale(1, 1) translate(-50%);
transform-origin: 50% 50%;
}
@keyframes ripple {
0% {
transform: scale(0, 0);
opacity: 1;
}
15% {
transform: scale(25, 25);
opacity: 1;
}
100% {
opacity: 0;
transform: scale(40, 40);
}
}
.material-button:focus:not(:active)::after {
animation: ripple 1s ease-out;
}
#snackbar {
visibility: hidden;
min-width: 250px;
margin-left: -200px;
background-color: #333;
color: #fff;
border-radius: 2px;
padding: 16px;
position: fixed;
z-index: 1;
left: 50%;
bottom: 30px;
}
.descriptionli {
padding: 0px 5px 5px 5px;
}
#snackbar.show {
visibility: visible;
-webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;
animation: fadein 0.5s, fadeout 0.5s 2.5s;
}
#semesters {
padding: 5px;
margin-right: 10px;
margin-top: 10px;
}
#semesters:focus {
outline: 0;
}
.tooltip {
position: relative;
}
.tooltip .tooltiptext {
visibility: hidden;
background-color: black;
background:rgba(1,1,1,0.5);
color: #fff;
text-align: left;
border-radius: 6px;
font-size: 10px;
max-width: 100px;
margin-left: 5px;
padding: 5px 10px;
z-index: 2;
position: absolute;
}
.tooltip:hover .tooltiptext {
visibility: visible;
}
@-webkit-keyframes fadein {
from {
bottom: 0;
opacity: 0;
}
to {
bottom: 30px;
opacity: 1;
}
}
@keyframes fadein {
from {
bottom: 0;
opacity: 0;
}
to {
bottom: 30px;
opacity: 1;
}
}
@-webkit-keyframes fadeout {
from {
bottom: 30px;
opacity: 1;
}
to {
bottom: 0;
opacity: 0;
}
}
@keyframes fadeout {
from {
bottom: 30px;
opacity: 1;
}
to {
bottom: 0;
opacity: 0;
}
}

Binary file not shown.

View File

@@ -1 +0,0 @@
theme: jekyll-theme-cayman

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -1,196 +0,0 @@
class Template {}
Template.Main = class {
static modal() {
return `<div class=modal id=myModal>
<div class=modal-content>
<span class=close>×</span>
<div class=card>
<div class=cardcontainer>
<h2 class=title id="title">Computer Fluency (C S 302)</h2>
<h2 class=profname id="profname">with <a id="professor_link">Bruce Porter</a></h2>
<div id="topbuttons" class=topbuttons>
<button class=material-button id="rateMyProf" style="background: #4CAF50;"> RMP </button>
<button class=material-button id="eCIS" style="background: #CDDC39;"> eCIS </button>
<button class=material-button id="textbook" style="background: #FFC107;"> Textbook </button>
<button class=material-button id="Syllabi"> Past Syllabi </button>
<button class=material-button id="saveCourse" value="add" style="background: #F44336;"> Save Course +</button>
</div>
</div>
</div>
<div class=card>
<div class=cardcontainer style="">
<div class="chartloader">
<div class="loader" id='descload'></div>
</div>
<ul class=description id="description" style="list-style-type:disc"></ul>
</div>
</div>
<div class=card style='text-align:center'>
<select id="semesters" style='text-align-last:center;color:#666666;fill:#666666;'>
</select>
<div class="chartloader">
<div class="loader" id='chartload'></div>
</div>
<div id="chartcontainer" class=cardcontainer>
<div id=chart></div>
</div>
</div>
</div>
</div>`;
}
static extension_button() {
return `<td data-th="Plus"><input type="image" class="distButton" id="distButton" width="20" height="20" src='${chrome.extension.getURL("images/disticon.png")}'/></td>`;
}
};
Template.Catalog = class {
static loading() {
return `<div style="text-align:center">
<div class="loader" id='loader'></div>
<br>
<h1 id="nextlabel"style="color: #FF9800;display:none;">Loading Courses</h1>
<h1 id="retrylabel"style="color: #F44336;display:none;">Failed to Load Courses</h1>
<br>
<button class=material-button id="retry" style="background: #F44336;display:none;">Retry</button>
</div>`;
}
};
Template.UTPlanner = class {
static modal() {
return `<div class=modal id=myModal>
<div class=modal-content>
<span class=close>×</span>
<div class=card>
<div class=cardcontainer>
<h2 class=title id="title">Computer Fluency (C S 302)</h2>
<h2 class=profname id="profname" style="margin-bottom:0px;">with Bruce Porter</h2>
<div id="topbuttons" class=topbuttons>
<button class=material-button id="moreInfo" style="background: #2196F3;"> More Info </button>
<button class=material-button id="textbook" style="background: #FFC107;"> Textbook </button>
<button class=material-button id="Syllabi"> Past Syllabi </button>
<button class=material-button id="saveCourse" value="add" style="background: #F44336;opacity:.4;"> Unable to Save</button>
</div>
</div>
</div>
<div class=card style='text-align:center'>
<select id="semesters" style='text-align-last:center;color:#666666;fill:#666666;'>
</select>
<div class="chartloader">
<div class="loader" id='chartload'></div>
</div>
<div id="chartcontainer" class=cardcontainer>
<div id=chart></div>
</div>
</div>
</div>
</div>`;
}
};
Template.Calendar = class {
static line(line) {
let { days, start_time, end_time, location_link, location_full } = line;
return `<p class='time' style='font-size:large;'>
<span style='display:inline-block;'>${days}:</span>
<span style='margin-left:10px;display:inline-block;text-align:center;'>${start_time} to ${end_time}</span>
<span style='float:right;display:inline-block;text-align:right;width: 25%;'>
<a target='_blank' style='color:#3c87a3;text-decoration:none;'href='${location_link}'>${location_full}</a>
</span>
</p>`;
}
static modal() {
return `<div id="myModal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<div class="card">
<div id="colorStrip" style="height:10px;"></div>
<div class="cardcontainer">
<div id='header'>
<div style="display:flex;">
<h2 id="classname">Classname</h2>
</div>
<p id="prof">Prof</p>
</div>
<button id="info" class="matbut" style="font-size:medium; margin-right: auto; margin-left:auto; background: #2196F3;">More Info</button>
<button id="register" class="matbut" style="font-size:medium; margin-right: auto; margin-left:10px; background: #4CAF50;">Register</button>
<button id="remove" class="matbut" style="font-size:medium;margin:10px;background: #FF0000;">Remove</button>
</div>
</div>
</div>`;
}
};
Template.Popup = class {
static list_item(i, list_tile_color, unique, department, number, profname, list_sub_color, line) {
return `<li id='${i}' class='course_list_item'>
<div class='card course_list_card'>
<div class='container' style='background:${list_tile_color}'>
<button class='copy_button' title='Copy Unique #' value='${unique}'>
<i id='copyicon' class="material-icons copy_button_icon">content_copy</i>
</button>
<h4 class='course_name_truncate_box'>
<b>${department} ${number} <span class='course_list_item_subtext'> with ${profname} (${unique})</span></b>
</h4>
<p id='arrow' class='arrow'>&#9658;</p>
</div>
</div>
<div id='moreInfo' class='course_list_item_options'>
<p style='background-color:${list_sub_color};' class='course_list_item_time_box'>${line}</p>
<div id='infoButtons' class='course_list_item_options_button_container'>
<button class='material_button course_list_item_options_buttons remove_button' id='listRemove'>Remove</button>
<button class='material_button course_list_item_options_buttons register_button' id='register'>Register</button>
<button class='material_button course_list_item_options_buttons more_info_button' id='listMoreInfo'>More Info</button>
</div>
</div>
</li>`;
}
static conflict_message(conflict_message) {
return `<p id='conflict' class='conflict_message'>${conflict_message}</>`;
}
static line(line) {
let { days, start_time, end_time, location_link, location_full } = line;
return `<span class='time_line_days'>${days}:</span>
<span class='time_line_hours'>${start_time} to ${end_time}</span>
<span class='time_line_location'>
<a target='_blank' class= 'time_line_location_link' href='${location_link}'>${location_full}</a>
</span>
<br>`;
}
};
Template.Import = class {
static import_button() {
return `<button class='material-button' id='import' style='margin:15px 0px;'>${Text.button_text_default}</button><br>`;
}
static waitlist_import_button() {
return `<button class='material-button' id='import_waitlist' style='margin:0px'>${Text.waitlist_button_text_default}</button><br>`;
}
static store_waitlist_message() {
return `<h1 id="nextlabel"style="color: #FF9800;display:none;"></h1>`;
}
};
Template.Options = class {
static options_row(key, enabled) {
let button_text = enabled ? "Turn Off" : "Turn On";
let button_color = enabled ? Colors.closed : Colors.open;
let label_text = capitalizeString(key.replace(/([A-Z]+)*([A-Z][a-z])/g, "$1 $2"));
return `<h2 style="padding: 5px 16px 5px 16px; font-weight: normal;display: inline-block;text-align:left;">
${label_text}
</h2>
<button id="${key}" value=${enabled} class="material-button" style="display:inline-block;font-size:medium; float:right; background:${button_color}">
${button_text}
</button>
<br>`;
}
static contributor_card(username, name, image_url, profile_url) {
return `<div class='card contributor-card' id="${username}" data-url="${profile_url}">
<img class='contributor-image' src="${image_url}"></img>
${name ? `<p class='contributor-name'>${name}</p>` : ""}
<p class='contributor-username'>${username}</p>
</div>`;
}
};

View File

@@ -1,487 +0,0 @@
console.log(`UT Registration Plus background page: ${window.location.href}`);
var grades; // caching the grades database in memory for faster queries
var current_semesters = {};
var departments = [];
var should_open = false; // toggled flag for automatically opening popup on new pages when 'more info' hit
// these are the default options that the extension currently supports
const default_options = {
loadAll: true,
courseConflictHighlight: true,
storeWaitlist: true,
};
onStartup();
function onStartup() {
updateBadge(true);
loadDataBase();
getCurrentSemesters();
getCurrentDepartments();
}
/* Handle messages and their commands from content and popup scripts*/
chrome.runtime.onMessage.addListener(function (request, sender, response) {
switch (request.command) {
case "courseStorage":
if (request.action == "add") {
add(request, sender, response);
}
if (request.action == "remove") {
remove(request, sender, response);
}
break;
case "isSingleConflict":
isSingleConflict(request.dtarr, request.unique, response);
break;
case "checkConflicts":
checkConflicts(response);
break;
case "updateBadge":
updateBadge();
break;
case "updateStatus":
updateStatus(response);
break;
case "alreadyContains":
alreadyContains(request.unique, response);
break;
case "updateCourseList":
updateTabs();
break;
case "gradesQuery":
executeQuery(request.query, response);
break;
case "currentSemesters":
response({ semesters: current_semesters });
getCurrentSemesters();
break;
case "currentDepartments":
response({ departments: departments });
break;
case "setOpen":
should_open = true;
chrome.tabs.create({ url: request.url });
break;
case "shouldOpen":
response({ open: should_open });
should_open = false;
break;
case "getOptionsValue":
getOptionsValue(request.key, response);
break;
case "setOptionsValue":
setOptionsValue(request.key, request.value, response);
break;
default:
const xhr = new XMLHttpRequest();
const method = request.method ? request.method.toUpperCase() : "GET";
xhr.open(method, request.url, true);
console.log(request);
xhr.onload = () => {
console.log(xhr.responseUrl);
response(xhr.responseText);
};
xhr.onerror = () => response(xhr.statusText);
if (method == "POST") {
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
}
xhr.send(request.data);
break;
}
return true;
});
/* Initially set the course data in storage */
chrome.runtime.onInstalled.addListener(function (details) {
if (details.reason == "install") {
setDefaultOptions();
chrome.storage.sync.get("savedCourses", function (data) {
if (!data.savedCourses) {
chrome.storage.sync.set({
savedCourses: [],
});
}
});
} else if (details.reason == "update") {
// if there's been an update, call setDefaultOptions in case their settings have gotten wiped
setDefaultOptions();
console.log("updated");
}
});
chrome.storage.onChanged.addListener(function (changes) {
for (key in changes) {
if (key === "savedCourses") {
updateBadge(false, changes.savedCourses.newValue); // update the extension popup badge whenever the savedCourses have been changed
}
}
});
// get the value of an option if it exists
function getOptionsValue(key, sendResponse) {
chrome.storage.sync.get("options", function (data) {
if (!data.options) {
setDefaultOptions();
} else {
sendResponse({
value: data.options[key],
});
}
});
}
// set the value of an option if it exists
function setOptionsValue(key, value, sendResponse) {
chrome.storage.sync.get("options", function (data) {
let new_options = data.options;
if (!data.options) {
// if there are no options set, set the defaults
setDefaultOptions();
new_options = default_options;
}
new_options[key] = value;
chrome.storage.sync.set(
{
options: new_options,
},
function () {
sendResponse({
value: new_options[key],
});
}
);
});
}
// set the default options if the options haven't been set before
function setDefaultOptions() {
chrome.storage.sync.get("options", function (data) {
if (!data.options) {
chrome.storage.sync.set(
{
options: default_options,
},
function () {
console.log("default options:", default_options);
}
);
}
});
}
async function getCurrentSemesters() {
let webData;
if(Object.keys(current_semesters).length > 0) {
chrome.storage.local.set({
semesterCache: current_semesters
});
}
async function goFetch(linkend="") {
console.log("lk " + linkend)
return fetch("https://registrar.utexas.edu/schedules/" + linkend)
.then((response) => {
return response.text()
.then((data) => {
return data;
}).catch((err) => {
console.log(err);
})
});
}
await goFetch().then((data) => {webData = data});
if(webData == null) {
webData = ""
}
let arr = webData.split("\n");
let i = 0
for(let row=0; row<arr.length; row++) {
let currentRow = arr[row]
if(currentRow.startsWith('<li><a href="https://registrar.utexas.edu/schedules/') && currentRow[52] != "a") {
let newWebData;
// let start = currentRow.indexOf('Schedule">')+10;
let start = Math.max(currentRow.lastIndexOf('Summer'), Math.max(currentRow.lastIndexOf('Spring'), currentRow.lastIndexOf('Fall')))
let end = currentRow.indexOf('</a></li>');
console.log(currentRow)
console.log(start + " " + end)
let name = currentRow.substring(start,end);
console.log("my name: " + name)
let num = currentRow.indexOf('"https://registrar.utexas.edu/schedules/">')+53;
let numend = currentRow.indexOf('" target');
let short_sem_num = currentRow.substring(num,numend);
current_semesters[name] = "code";
await goFetch(short_sem_num).then((data) => {newWebData = data});
arr2 = newWebData.split("\n")
for(let row2=0; row2<arr2.length; row2++) {
if(arr2[row2].startsWith('<div class="gobutton"><a href="')) {
let start2 = arr2[row2].indexOf('<div class="gobutton"><a href="')+31;
let end2 = arr2[row2].indexOf('" target="');
var scheduleLink = arr2[row2].substring(start2,end2);
var sem_num = scheduleLink.substring(scheduleLink.lastIndexOf("/") + 1).trim();
if (current_semesters[name] != sem_num) {
current_semesters[name] = sem_num;
}
}
}
}
}
}
// use the utexas review api for getting the list of departments
function getCurrentDepartments() {
$.get("https://raw.githubusercontent.com/sghsri/UT-Registration-Plus/master/docs/departments.json", function (response) {
if (response) {
departments = JSON.parse(response);
}
});
}
// update the badge text to reflect the new changes
function updateBadge(first, new_changes) {
if (new_changes) {
updateBadgeText(first, new_changes);
} else {
chrome.storage.sync.get("savedCourses", function (data) {
let courses = data.savedCourses;
updateBadgeText(first, courses);
});
}
}
// update the badge text to show the number of courses that have been saved by the user
function updateBadgeText(first, courses) {
let badge_text = courses.length > 0 ? `${courses.length}` : "";
let flash_time = !first ? 200 : 0;
chrome.browserAction.setBadgeText({
text: badge_text,
});
if (!first) {
// if isn't the first install of the extension, flash the badge to bring attention to it
chrome.browserAction.setBadgeBackgroundColor({
color: Colors.badge_flash,
});
}
setTimeout(function () {
chrome.browserAction.setBadgeBackgroundColor({
color: Colors.badge_default,
});
}, flash_time);
}
/* Find all the conflicts in the courses and send them out/ if there is even a conflict*/
function checkConflicts(sendResponse) {
chrome.storage.sync.get("savedCourses", function (data) {
var conflicts = [];
var courses = data.savedCourses;
for (let i = 0; i < courses.length; i++) {
for (let j = i + 1; j < courses.length; j++) {
let course_a = courses[i];
let course_b = courses[j];
if (isConflict(course_a.datetimearr, course_b.datetimearr)) conflicts.push([course_a, course_b]);
}
}
sendResponse({
isConflict: conflicts.length !== 0,
between: conflicts.length ? conflicts : undefined,
});
});
}
/* Find if the course at unique and with currdatearr is contained in the saved courses and if it conflicts with any other courses*/
function isSingleConflict(currdatearr, unique, sendResponse) {
chrome.storage.sync.get("savedCourses", function (data) {
var courses = data.savedCourses;
var conflict_list = [];
var conflict = false;
var contains = false;
for (let i = 0; i < courses.length; i++) {
let course = courses[i];
if (isConflict(currdatearr, course.datetimearr)) {
conflict = true;
conflict_list.push(course);
}
if (!contains && isSameCourse(course, unique)) {
contains = true;
}
}
sendResponse({
isConflict: conflict,
alreadyContains: contains,
conflictList: conflict_list,
});
});
}
/* Check if conflict between two date-time-arrs*/
function isConflict(adtarr, bdtarr) {
for (var i = 0; i < adtarr.length; i++) {
var current_day = adtarr[i][0];
var current_times = adtarr[i][1];
for (var j = 0; j < bdtarr.length; j++) {
var next_day = bdtarr[j][0];
var next_times = bdtarr[j][1];
if (next_day == current_day) {
if (current_times[0] < next_times[1] && current_times[1] > next_times[0]) {
return true;
}
}
}
}
return false;
}
/* Add the requested course to the storage*/
function add(request, sender, sendResponse) {
chrome.storage.sync.get("savedCourses", function (data) {
var courses = data.savedCourses;
if (!contains(courses, request.course.unique)) {
courses.push(request.course);
console.log(courses);
chrome.storage.sync.set({
savedCourses: courses,
});
}
sendResponse({
done: "Added: (" + request.course.unique + ") " + request.course.coursename,
label: "Remove Course -",
value: "remove",
});
});
}
/* Find and Remove the requested course from the storage*/
function remove(request, sender, sendResponse) {
chrome.storage.sync.get("savedCourses", function (data) {
var courses = data.savedCourses;
console.log(courses);
var index = 0;
while (index < courses.length && courses[index].unique != request.course.unique) {
index++;
}
courses.splice(index, 1);
chrome.storage.sync.set({
savedCourses: courses,
});
sendResponse({
done: "Removed: (" + request.course.unique + ") " + request.course.coursename,
label: "Add Course +",
value: "add",
});
});
}
/* Find if the unique is already contained within the storage*/
function alreadyContains(unique, sendResponse) {
chrome.storage.sync.get("savedCourses", function (data) {
var courses = data.savedCourses;
sendResponse({
alreadyContains: contains(courses, unique),
});
});
}
// find if a course with the current unique number exists in the user's saved courses
function contains(courses, unique) {
var i = 0;
while (i < courses.length) {
if (isSameCourse(courses[i], unique)) {
return true;
}
i++;
}
return false;
}
// does it have the same unique number as provided
function isSameCourse(course, unique) {
return course.unique == unique;
}
// send a message to every tab open to updateit's course list (and thus recalculate its conflicts highlighting)
function updateTabs() {
chrome.tabs.query({}, function (tabs) {
for (var i = 0; i < tabs.length; i++) {
chrome.tabs.sendMessage(tabs[i].id, {
command: "updateCourseList",
});
}
});
}
// const UPDATE_INTERVAL = 1000 * 60 * 16;
// setInterval(updateStatus, UPDATE_INTERVAL);
// // updateStatus();
// function updateStatus(sendResponse) {
// chrome.storage.sync.get("savedCourses", function (data) {
// var courses = data.savedCourses;
// var no_change = true;
// for (let i = 0; i < courses.length; i++) {
// try {
// let c = courses[i];
// let old_status = c.status;
// let old_link = c.link;
// $.ajax({
// url: old_link,
// success: function (result) {
// if (result) {
// console.log(result);
// var object = $("<div/>").html(result).contents();
// let new_status = object.find('[data-th="Status"]').text();
// let register_link = object.find('td[data-th="Add"] a');
// if (register_link) register_link = register_link.attr("href");
// var haschanged = new_status == old_status && register_link == old_link;
// if (!haschanged) console.log(c.unique + " updated from " + old_status + " to " + new_status + " and " + old_link + " to " + register_link);
// no_change &= haschanged;
// c.registerlink = register_link;
// c.status = new_status;
// }
// },
// });
// } catch (e) {
// console.log(e);
// console.log("Not logged into UT Coursebook. Could not update class statuses.");
// }
// }
// if (!no_change) {
// chrome.storage.sync.set({
// savedCourses: courses,
// });
// console.log("updated status");
// }
// });
// }
// execute a query on the grades database
function executeQuery(query, sendResponse) {
var res = grades.exec(query)[0];
sendResponse({
data: res,
});
}
/* Load the database*/
function loadDataBase() {
sql = window.SQL;
loadBinaryFile("grades.db", function (data) {
var sqldb = new SQL.Database(data);
grades = sqldb;
});
}
/* load the database from file */
function loadBinaryFile(path, success) {
var xhr = new XMLHttpRequest();
xhr.open("GET", chrome.extension.getURL(path), true);
xhr.responseType = "arraybuffer";
xhr.onload = function () {
var data = new Uint8Array(xhr.response);
var arr = new Array();
for (var i = 0; i != data.length; ++i) arr[i] = String.fromCharCode(data[i]);
success(arr.join(""));
};
xhr.send();
}

View File

@@ -1,291 +0,0 @@
var color_counter = 0;
var {
calendar_fade_time,
button_delay
} = Timing;
var saved_courses = [];
var curr_course = {}
$("#calendar").after(Template.Calendar.modal());
chrome.storage.sync.get("savedCourses", function (data) {
// Iterate through each saved course and add to 'event'
saved_courses = data.savedCourses;
console.log(saved_courses);
let event_source = buildEventSource(saved_courses);
$("#calendar").fullCalendar({
editable: false, // Don't allow editing of events
handleWindowResize: true,
weekends: false, // will hide Saturdays and Sundays
slotDuration: "00:30:00", // 15 minute intervals on vertical column
slotEventOverlap: false, // No overlapping between events
defaultView: "agendaWeek", // Only show week view
header: false, // Hide buttons/titles
minTime: "08:00:00", // Start time
maxTime: "21:00:01", // End time
columnHeaderFormat: "ddd", // Only show day of the week names
displayEventTime: true, // Display event time
allDaySlot: false,
Duration: {
hours: 1
},
height: 'auto',
events: event_source,
slotLabelFormat: [
'h:mm A' // lower level of text
],
eventRender: function (event, element, view) {
$(element).css("padding", "5px").css("margin-bottom", "5px");
},
eventClick: function (data, event, view) {
displayModal(data)
}
});
});
function displayModal(data) {
$("#myModal").fadeIn(calendar_fade_time);
$("#colorStrip").css('background-color', data.color);
curr_course = saved_courses[data.index];
setUpModal()
}
function setUpModal() {
let {
coursename,
unique,
datetimearr,
profname,
status,
registerlink
} = curr_course;
$("#classname").html(`${coursename} <span style='font-size:small'>(${unique})</span>`);
buildTimeTitle(datetimearr);
$("#prof").html(`with <span style='font-weight:bold;'>${capitalizeString(profname)}</span>`);
setRegisterButton(status, registerlink)
}
function setRegisterButton(status, registerlink) {
if (canNotRegister(status, registerlink))
$("#register").text("Can't Register").css("background-color", Colors.closed);
else if (status.includes("waitlisted"))
$("#register").text("Join Waitlist").css("background-color", Colors.waitlisted);
else
$("#register").text("Register").css("background-color", Colors.open);
}
function buildTimeTitle(datetimearr) {
$('#timelines').remove();
var arr = convertDateTimeArrToLine(datetimearr)
var output = "";
for (let i = 0; i < arr.length; i++) {
let line = arr[i];
output += Template.Calendar.line(line);
}
$("#header").after(`<div id='timelines'>${output}</div`);
}
// Iterate through each saved course and add to 'event'
function buildEventSource(saved_courses) {
color_counter = 0;
let event_source = [];
var hours = 0;
for (let i = 0; i < saved_courses.length; i++) {
let {
coursename,
datetimearr
} = saved_courses[i];
let number = separateCourseNameParts(coursename).number;
let class_length = parseInt(number.charAt(0));
let multi_semester_code = number.slice(-1);
if (["A","B"].includes(multi_semester_code)) {
hours += Math.floor(class_length/2);
} else if (["X","Y","Z"].includes(multi_semester_code)) {
hours += Math.floor(class_length/3);
} else {
hours += class_length;
}
for (let j = 0; j < datetimearr.length; j++) {
let session = datetimearr[j]; // One single session for a class
let event_obj = setEventForSection(session, color_counter, i);
event_source.push(event_obj);
}
color_counter++;
}
displayMetaData(hours, saved_courses);
return event_source;
}
function displayMetaData(hours, saved_courses) {
$("#hours").text(hours + " Hours");
$("#num").text(saved_courses.length + " Courses");
}
//create the event object for every section
function setEventForSection(session, colorCounter, i) {
let full_day = days.get(session[0]);
let course = saved_courses[i];
let {
coursename,
profname,
} = course;
let {
department,
number
} = separateCourseNameParts(coursename)
beg_day = calculateBeginningDate(full_day)
start_date = formatCalculateDate(beg_day, full_day, session[1][0]);
end_date = formatCalculateDate(beg_day, full_day, session[1][1]);
event_obj = {
title: `${department}-${number} with ${capitalizeString(profname)}`,
start: start_date,
end: end_date,
color: Colors.material_colors[colorCounter],
building: session[2],
index: i,
allday: false
};
return event_obj;
}
function formatCalculateDate(beg_day, full_day, hour) {
return beg_day + moment().day(full_day)._d.toString().split(" ")[2] + "T" + hour + ":00";
}
function calculateBeginningDate(full_day) {
var year = moment().day(full_day)._d.toString().split(" ")[3];
var month_num = moment(moment().day(full_day)._d.toString().split(" ")[1], "MMM").format('MM');
return `${year}-${month_num}-`;
}
function updateCalendar() {
chrome.storage.sync.get("savedCourses", function (data) {
saved_courses = data.savedCourses
let event_source = buildEventSource(saved_courses);
$('#calendar').fullCalendar('removeEventSources');
$("#calendar").fullCalendar('addEventSource', event_source, true);
});
}
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.command == "updateCourseList" || request.command == "courseAdded") {
updateCalendar();
}
}
);
$("#info").click(() => {
openMoreInfoWithOpenModal(curr_course.link);
});
$("#save").click(() => {
takePicture();
});
$("#clear").click(() => {
/*Clear the list and the storage of courses*/
chrome.storage.sync.set({
savedCourses: []
});
updateAllTabsCourseList();
updateCalendar();
});
$("#remove").click(() => {
setTimeout(() => {
chrome.runtime.sendMessage({
command: "courseStorage",
course: curr_course,
action: "remove"
}, function () {
$("#myModal").fadeOut(calendar_fade_time);
updateCalendar();
updateAllTabsCourseList();
});
}, button_delay);
});
$("#register").click(function () {
let {
registerlink,
status
} = curr_course;
if (!canNotRegister(status, registerlink)) {
setTimeout(() => {
window.open(registerlink);
}, button_delay);
}
});
$("#export").click(function () {
var cal = ics();
var calendarEvents = $('#calendar').fullCalendar('clientEvents');
for (i in calendarEvents) {
var event = calendarEvents[i];
buildICSFile(cal, event);
}
cal.download("My_Course_Calendar");
});
function buildICSFile(cal, event) {
let {
title,
start,
end,
building
} = event;
let class_name = title.split('with')[0];
let description = `with ${title.split('with')[1]}`;
let time = start._d.toUTCString();
cal.addEvent(class_name, description, building, start._i, end._i, {
rrule: `RRULE:FREQ=WEEKLY;BYDAY=${time.substring(0, time.indexOf(",") - 1).toUpperCase()};INTERVAL=1`
});
}
function takePicture() {
var width = $("#calendar").width() * window.devicePixelRatio;
var height = $("#calendar").height() * window.devicePixelRatio;
let cropper = document.createElement('canvas').getContext('2d');
html2canvas(document.querySelector("#calendar"), Export.png_options).then(c => {
cropper.canvas.width = width;
cropper.canvas.height = height;
cropper.drawImage(c, 0, 0);
var a = document.createElement('a');
a.href = cropper.canvas.toDataURL("image/png");
a.download = 'mySchedule.png';
a.click();
});
}
/*Close Modal when hit escape*/
$(document).keydown((e) => {
if (e.keyCode == 27) {
$("#myModal").fadeOut(calendar_fade_time);
}
});
$('.close').click(function () {
close();
});
$('#myModal').click(function (event) {
if (event.target.id == 'myModal') {
close();
}
});
function close() {
$("#myModal").fadeOut(calendar_fade_time);
}

View File

@@ -1,93 +0,0 @@
class Timing {}
Timing.fade_time = 100;
Timing.calendar_fade_time = 100;
Timing.button_delay = 75;
class Colors {}
Colors.material_colors = [
"#4CAF50",
"#CDDC39",
"#FFC107",
"#2196F3",
"#F57C00",
"#9C27B0",
"#FF5722",
"#673AB7",
"#FF5252",
"#E91E63",
"#009688",
"#00BCD4",
"#4E342E",
"#424242",
"#9E9E9E",
];
Colors.open = "#4CAF50";
Colors.waitlisted = "#FF9800";
Colors.closed = "#FF5722";
Colors.no_status = "#607D8B";
Colors.open_light = "#C8E6C9";
Colors.waitlisted_light = "#FFE0B2";
Colors.closed_light = "#FFCCBC";
Colors.no_status_light = "#CFD8DC";
Colors.highlight_conflict = "#F44336";
Colors.highlight_default = "#333333";
Colors.highlight_saved = "#4CAF50";
Colors.badge_flash = "#FF5722";
Colors.badge_default = "#bf5700";
class Export {}
Export.png_options = {
foreignObjectRendering: true,
logging: true,
removeContainer: true,
async: true,
};
class Popup {}
Popup.num_semesters = 2;
/*
* Funny comments that popup when no classes have been chosen
*/
class Text {}
Text.emptyText = function () {
let arr = [
"Doesn't Look Like Anything To Me.",
"You Can't Fail Classes You're Not In.",
"Pro-Tip: Don't Take O-Chem.",
"Jendy's Fofofo™",
"Fine Dining at Jester City Limits",
"Rec Sports is full and it's only 2pm.",
"Hope Domino is doing well rn &#129402;",
"The year is 2055 and Welch still isn't finished.",
"Wear a Mask.",
"Motivation dropping faster than ur GPA",
"No Work Happens On PCL 5th Floor.",
"Sophomore But Freshman By Credit.",
"Pain is temporary, GPA is forever.",
"You've Yee'd Your Last Haw.",
"lol everything is already waitlisted.",
"At Least You're Not At A&M.",
`It's ${moment().format("h:mm")} and OU Still Sucks.`,
"TeXAs iS BaCK GuYZ",
"'Academically Challenged'",
"Does McCombs teach Parseltongue?",
"Feel bad if you say Wampus.",
"No Cruce Enfrente Del Bus.",
"Midterm 1 has been Unmuted",
"Omae Wa Mou Shindeiru...",
"Bevo Bucks are the new Bitcoin",
"Every day, another brick disappears from Speedway",
"The GDC will annex the EER one day",
"To hike to Kins, or not to hike to Kins...",
];
let index = Math.floor(Math.random() * arr.length);
return arr[index];
};
Text.button_text_default = "<span style='font-size:small'>Import to </span><b>UT Reg +<b>";
Text.waitlist_button_text_default = "<span style='font-size:small'>Import Waitlists to </span><b>UT Reg +<b>";
Text.button_success = "Courses Saved!";

View File

@@ -1,627 +0,0 @@
console.log(`UT Registration Plus is running on this page: ${window.location.href}`);
var curr_course = {}
var semester_code = new URL(window.location.href).pathname.split('/')[4];
var done_loading = true;
var next = $("#next_nav_link");
if (next) {
chrome.runtime.sendMessage({
command: "getOptionsValue",
key: "loadAll",
}, function (response) {
if(response.value){
$('[title*="next listing"]').remove();
}
});
}
//This extension may be super lit, but you know what's even more lit?
//Matthew Tran's twitter and insta: @MATTHEWTRANN and @matthew.trann
if (document.querySelector('#fos_fl')) {
let params = (new URL(document.location)).searchParams;
let dep = params.get("fos_fl");
let level = params.get("level");
if (dep && level) {
if (dep.length == 3 && (level == 'U' || level == 'L' || level == 'G')) {
document.querySelector('#fos_fl').value = dep;
document.querySelector('#level').value = level;
}
}
}
//make heading and modal
if (!$("#kw_results_table").length) {
$("table").after(Template.Catalog.loading());
$("#container").prepend(Template.Main.modal());
$("#myModal").prepend("<div id='snackbar'>save course popup...</div>");
// now add to the table
$("table thead th:last-child").after('<th scope=col>Plus</th>');
$('table').find('tr').each(function () {
if (!($(this).find('td').hasClass("course_header")) && $(this).has('th').length == 0) {
$(this).append(Template.Main.extension_button());
}
});
}
if(isIndividualCoursePage()){
chrome.runtime.sendMessage({
command: "shouldOpen",
}, function (response) {
if(response.open){
$("#distButton").click();
}
});
}
updateListConflictHighlighting();
$("body").on('click', '#distButton', function () {
var row = $(this).closest('tr');
$('.modal-content').stop().animate({
scrollTop: 0
}, 500);
$(this).blur();
curr_course = getCourseInfo(row);
getDistribution(curr_course);
});
function updateLinks(course_info, first_name) {
let {
prof_name,
number
} = course_info;
course_info["first_name"] = first_name;
course_info["links"]["rate_my_prof"] = `http://www.ratemyprofessors.com/search.jsp?queryBy=teacherName&schoolName=university+of+texas+at+austin&queryoption=HEADER&query=${first_name} ${prof_name};&facetSearch=true`;
course_info["links"]["ecis"] = profname ? `http://utdirect.utexas.edu/ctl/ecis/results/index.WBX?&s_in_action_sw=S&s_in_search_type_sw=N&s_in_search_name=${prof_name}%2C%20${first_name}` :
`http://utdirect.utexas.edu/ctl/ecis/results/index.WBX?s_in_action_sw=S&s_in_search_type_sw=C&s_in_max_nbr_return=10&s_in_search_course_dept=${department}&s_in_search_course_num=${number}`;
}
function buildCourseLinks(course_info) {
let {
department,
number,
unique,
prof_name
} = course_info
links = {
"textbook": `https://www.universitycoop.com/adoption-search-results?sn=${semester_code}__${department}__${number}__${unique}`,
"syllabi": `https://utdirect.utexas.edu/apps/student/coursedocs/nlogon/?year=&semester=&department=${department}&course_number=${number}&course_title=&unique=&instructor_first=&instructor_last=${prof_name}&course_type=In+Residence&search=Search`,
//default ones (before first name can be used)
"rate_my_prof": "http://www.ratemyprofessors.com/campusRatings.jsp?sid=1255",
"ecis": "http://utdirect.utexas.edu/ctl/ecis/results/index.WBX?"
}
course_info["links"] = links;
return course_info;
}
function buildBasicCourseInfo(row, course_name, individual) {
let {
name,
department,
number
} = separateCourseNameParts(course_name);
let instructor_text = $(row).find('td[data-th="Instructor"]').text();
let has_initial = instructor_text.indexOf(',') > 0;
course_info = {
"full_name": course_name,
"name": name,
"department": department,
"number": number,
"individual": individual ? individual : $(row).find('td[data-th="Unique"] a').prop('href'),
"register": $(row).find('td[data-th="Add"] a').prop('href'),
"unique": $(row).find('td[data-th="Unique"]').text(),
"status": $(row).find('td[data-th="Status"]').text(),
"prof_name": instructor_text ? has_initial ? capitalizeString(instructor_text.split(', ')[0]) : capitalizeString(instructor_text) : "Undecided",
"initial": instructor_text && has_initial ? instructor_text.split(', ')[1].substring(0, 1) : "",
"time_data": {
"days": $(row).find('td[data-th="Days"]>span').toArray().map(x => $(x).text().trim()),
"times": $(row).find('td[data-th="Hour"]>span').toArray().map(x => $(x).text().trim()),
"places": $(row).find('td[data-th="Room"]>span').toArray().map(x => $(x).text().trim())
},
"links": {}
}
return buildCourseLinks(course_info);
}
/*For a row, get all the course information and add the date-time-lines*/
function getCourseInfo(row) {
let course_name = "";
let course_row = {}
let individual = undefined;
if (isIndividualCoursePage()) {
course_name = $("#details h2").text();
course_row = $('table');
individual = document.URL;
} else {
$('table').find('tr').each(function () {
if ($(this).find('td').hasClass("course_header")) {
course_name = $(this).find('td').text() + "";
}
if ($(this).is(row)) {
course_row = row;
return false;
}
});
}
curr_course = buildBasicCourseInfo(course_row, course_name, individual);
getDescription(curr_course);
return curr_course;
}
function saveCourse() {
console.log(curr_course);
console.log(JSON.stringify(curr_course));
let {
full_name,
unique,
prof_name,
status,
individual,
register
} = curr_course;
let dtarr = getDayTimeArray(undefined, curr_course);
var c = new Course(full_name, unique, prof_name, dtarr, status, individual, register);
chrome.runtime.sendMessage({
command: "courseStorage",
course: c,
action: $("#saveCourse").val()
}, function (response) {
$("#saveCourse").text(response.label);
$("#saveCourse").val(response.value);
$("#snackbar").text(response.done);
toggleSnackbar();
chrome.runtime.sendMessage({
command: "updateCourseList"
});
});
}
/* Update the course list to show if the row contains a course that conflicts with the saved course is one of the saved courses */
function updateListConflictHighlighting(start = 0) {
chrome.runtime.sendMessage({
command: "getOptionsValue",
key: "courseConflictHighlight",
}, function (response) {
let canHighlight = response.value;
$('table').find('tr').each(function (i) {
if (i >= start) {
if (!($(this).find('td').hasClass("course_header")) && $(this).has('th').length == 0) {
var unique = $(this).find('td[data-th="Unique"]').text();
chrome.runtime.sendMessage({
command: "isSingleConflict",
dtarr: getDayTimeArray(this),
unique: unique
}, (response) => {
let {
isConflict,
alreadyContains,
conflictList
} = response
updateTextHighlighting($(this).find('td'), canHighlight, isConflict, alreadyContains, conflictList, $(this), unique);
});
}
}
});
});
}
function updateTextHighlighting(tds, canHighlight, isConflict, alreadyContains, conflictList, row, unique) {
conflict_texts = row.find('.tooltiptext');
let unique_list = conflictList.filter(function(course){
if(course.unique != unique){
return true;
}
return false;
}).map(function(course){
let { name, department, number} = separateCourseNameParts(course.coursename);
return `${department} ${number} (${course.unique})`;
});
if(isConflict && unique_list.length){
if(conflict_texts){
row.find('.tooltiptext').remove();
}
row.addClass('tooltip');
row.append(`<span class='tooltiptext'><span style='text-decoration: underline;'>Conflicts:<br></span> ${unique_list.join('<br>')}</span>`);
} else {
row.removeClass('tooltip');
conflict_texts.remove();
}
let current_color = rgb2hex(tds.css('color'));
if (isConflict && canHighlight && !alreadyContains) {
if (current_color != Colors.highlight_conflict){
tds.css('color', Colors.highlight_conflict).css('text-decoration', 'line-through').css('font-weight', 'normal')
}
} else if (!alreadyContains) {
if (tds.css('color') != Colors.highlight_default)
tds.css('color', Colors.highlight_default).css('text-decoration', 'none').css('font-weight', 'normal');
}
if (alreadyContains) {
if (tds.css('color') != Colors.highlight_saved)
tds.css('color', Colors.highlight_saved).css('text-decoration', 'none').css('font-weight', 'bold');
}
}
/* For a row, get the date-time-array for checking conflicts*/
function getDayTimeArray(row, course_info) {
var day_time_array = []
let days = course_info ? course_info["time_data"]["days"] : $(row).find('td[data-th="Days"]>span').toArray().map(x => $(x).text().trim());
let times = course_info ? course_info["time_data"]["times"] : $(row).find('td[data-th="Hour"]>span').toArray().map(x => $(x).text().trim());
let places = course_info ? course_info["time_data"]["places"] : $(row).find('td[data-th="Room"]>span').toArray().map(x => $(x).text().trim());
for (var i = 0; i < days.length; i++) {
let date = days[i];
let time = times[i];
let place = places[i];
for (var j = 0; j < date.length; j++) {
let letter = date.charAt(j);
if (letter == "T" && j < date.length - 1 && date.charAt(j + 1) == "H") {
day_time_array.push(["TH", convertTime(time), place]);
} else {
if (letter != "H")
day_time_array.push([letter, convertTime(time), place]);
}
}
}
return day_time_array;
}
function convertDateTimeArrToLine(date, time, place) {
let arr = separateDays(date)
let output = prettifyDaysText(arr)
let building = place.substring(0, place.search(/\d/) - 1);
building = building ? building : "Undecided Location";
return `${output} at ${time.replace(/\./g, '').replace(/\-/g, ' to ')} in <a style='font-size:medium' target='_blank' href='https://maps.utexas.edu/buildings/UTM/${building}'>${building}</>`;
}
function badData(course_data, res) {
return typeof res == 'undefined' || course_data["prof_name"] == "Undecided";
}
/*Query the grades database*/
function getDistribution(course_data, sem) {
toggleChartLoading(true);
let query = buildQuery(course_data, sem);
chrome.runtime.sendMessage({
command: "gradesQuery",
query: query
}, function (response) {
var res = response.data;
if (!sem) {
openDialog(course_data, res);
} else {
var data = badData(course_data, res) ? [] : res.values[0];
setChart(data);
}
});
}
function buildTitle(course_data) {
return `${course_data["name"]} (${course_data["department"]} ${course_data["number"]})`
}
function buildTimeTitle(course_info) {
$("h2.dateTimePlace").remove();
let {
days,
times,
places
} = course_info["time_data"]
var lines = [];
for (let i = 0; i < days.length; i++) {
var date = days[i];
var time = times[i];
var place = places[i];
lines.push($(`<h2 class="dateTimePlace">${convertDateTimeArrToLine(date, time, place)}</th>`));
}
return lines;
}
function buildProfTitle(course_data) {
const {
initial,
prof_name
} = course_data;
return `with ${initial?initial+". ":""}${prof_name}`;
}
function buildSemestersDropdown(course_data, res) {
$("#semesters").empty();
if (badData(course_data, res)) {
$("#semesters").append("<option>No Data</option>")
} else {
var semesters = res.values[0][18].split(",");
semesters.sort(semesterSort);
semesters.reverse().unshift('Aggregate');
var sems = [];
for (var i = 0; i < semesters.length; i++) {
sems.push($(`<option value="${semesters[i]}">${semesters[i]}</option>`));
}
$("#semesters").append(sems);
}
}
function displayBasicCourseInfo(course_info){
$("#title").text(buildTitle(course_info))
$("#topbuttons").before(buildTimeTitle(course_info));
$("#profname").text(buildProfTitle(course_info));
$("#myModal").fadeIn(Timing.fade_time);
console.log(course_info);
}
/*Open the modal and show all the data*/
function openDialog(course_info, res) {
displayBasicCourseInfo(course_info);
//initial text on the "save course button"
chrome.runtime.sendMessage({
command: "alreadyContains",
unique: course_info["unique"]
}, function (response) {
let button_text = response.alreadyContains ? "Remove Course -" : "Add Course +";
let button_val = response.alreadyContains ? "remove" : "add";
$("#saveCourse").text(button_text);
$("#saveCourse").val(button_val);
});
buildSemestersDropdown(course_info, res)
var data = []
if (!badData(course_info, res))
data = res.values[0];
allowClosing();
setChart(data);
}
function setChart(data) {
// set up the chart
toggleChartLoading(false);
Highcharts.chart('chart', buildChartConfig(data), function (chart) { // on complete
if (data.length == 0) {
//if no data, then show the message and hide the series
chart.renderer.text('Could not find data for this Instructor teaching this Course.', 100, 120)
.css({
fontSize: '20px',
width: '300px',
align: 'center',
left: '160px'
})
.add();
$.each(chart.series, function (i, ser) {
ser.hide();
});
}
});
}
var error_message = "<p style='color:red;font-style:bold'>You have been logged out. Please refresh the page and log back in using your UT EID and password.</p>";
function buildFormattedDescription(description_lines) {
let description = ""
for (let i in description_lines) {
let sentence = description_lines[i];
if (sentence.indexOf("Prerequisite") == 0)
sentence = `<li style='font-weight: bold;' class='descriptionli'>${sentence}</li>`;
else if (sentence.indexOf("May be") >= 0)
sentence = `<li style='font-style: italic;' class='descriptionli'>${sentence}</li>`;
else if (sentence.indexOf("Restricted to") == 0)
sentence = `<li style='color:red;' class='descriptionli'>${sentence}</li>`;
else
sentence = `<li class='descriptionli'>${sentence}</li>`;
description += sentence;
}
if (!description)
description = error_message;
return description;
}
function extractFirstName(response_node) {
let full_name = response_node.find('td[data-th="Instructor"]').text().split(', ');
let first = full_name[full_name.length - 1];
first = first.indexOf(' ') > 0 ? first.split(' ')[0] : first;
return capitalizeString(first);
}
function displayDescription(description) {
toggleDescriptionLoading(false);
$("#description").animate({
'opacity': 0
}, 200, function () {
$(this).html(description).animate({
'opacity': 1
}, 200);
});
}
/*Get the course description from the profurl and highlight the important elements, as well as set the eCIS, and rmp links.*/
function getDescription(course_info) {
toggleDescriptionLoading(true);
$.ajax({
url: course_info["individual"],
success: function (response) {
if (response) {
let response_node = htmlToNode(response);
description_lines = response_node.find('#details > p').toArray().map(x => $(x).text());
displayDescription(buildFormattedDescription(description_lines));
let first_name = extractFirstName(response_node);
updateLinks(course_info, first_name);
} else {
displayDescription(error_message);
}
}
});
}
function loadNextPages(num_pages) {
if (num_pages === undefined) num_pages = 1;
if (num_pages == 0) return;
chrome.runtime.sendMessage({
command: "getOptionsValue",
key: "loadAll",
}, function (response) {
if(response.value){
let link = next.prop('href');
if (done_loading && next && link) {
toggleLoadingPage(true);
$.get(link, function (response) {
if (response) {
var next_page = htmlToNode(response);
var current = $('tbody');
var old_length = $('tbody tr').length;
var last = current.find('.course_header>h2:last').text();
next = next_page.find("#next_nav_link");
toggleLoadingPage(false);
var new_rows = [];
next_page.find('tbody>tr').each(function () {
let has_course_header = $(this).find('td').hasClass("course_header");
if (!(has_course_header && $(this).has('th').length == 0))
$(this).append(Template.Main.extension_button());
if (!(has_course_header && last == $(this).find('td').text()))
new_rows.push($(this));
});
current.append(new_rows);
updateListConflictHighlighting(old_length + 1)
}
loadNextPages(num_pages-1);
}).fail(function () {
toggleLoadingPage(false);
$("#retrylabel").css('display', 'inline-block');
$('#retry').css('display', 'inline-block');
});
}
}
});
}
$("#myModal").on('click', '#saveCourse', function () {
setTimeout(function () {
saveCourse();
}, 0);
});
$("#Syllabi").click(function () {
setTimeout(function () {
window.open(curr_course["links"]["syllabi"]);
}, Timing.button_delay);
});
$("#rateMyProf").click(function () {
setTimeout(function () {
window.open(curr_course["links"]["rate_my_prof"]);
}, Timing.button_delay);
});
$("#eCIS").click(function () {
setTimeout(function () {
window.open(curr_course["links"]["ecis"]);
}, Timing.button_delay);
});
$("#textbook").click(function () {
setTimeout(function () {
window.open(curr_course["links"]["textbook"]);
}, Timing.button_delay);
});
$("#semesters").on('change', function () {
let sem = $(this).val();
sem = sem == "Aggregate" ? undefined : sem;
getDistribution(curr_course, sem);
});
$("#retry").click(function () {
$("#retrylabel").hide();
$(this).hide();
loadNextPages();
});
function toggleLoadingPage(loading) {
if (loading) {
done_loading = false;
$('#loader').css('display', 'inline-block');
$("#nextlabel").css('display', 'inline-block');
} else {
done_loading = true;
$('#loader').hide();
$("#nextlabel").hide();
}
}
function toggleChartLoading(loading) {
if (loading) {
$('#chartload').css('display', 'inline-block');
$("#chart").hide();
} else {
$('#chartload').hide();
$("#chart").show();
}
}
function toggleDescriptionLoading(loading) {
if (loading) {
$('#descload').css('display', 'inline-block');
} else {
$('#descload').hide();
}
}
function toggleSnackbar() {
setTimeout(function () {
$("#snackbar").attr("class", "show");
}, 200);
setTimeout(function () {
$("#snackbar").attr("class", "");
}, 3000);
}
function allowClosing() {
$('.close').click(function () {
close();
});
$('#myModal').click(function (event) {
if (event.target.id == 'myModal') {
close();
}
});
}
function close() {
$("#myModal").fadeOut(Timing.fade_time);
$("#snackbar").attr("class", "");
}
/*Listen for update mssage coming from popup or calendar or other course catalog pages*/
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.command == "updateCourseList") {
updateListConflictHighlighting(0);
}
}
);
$(document).keydown(function (e) {
/*Close Modal when hit escape*/
if (e.keyCode == 27) {
close();
} else if (e.keyCode == 13 && $('#myModal').is(':visible')) {
saveCourse();
}
});
$(window).scroll(function () {
if ($(document).height() <= $(window).scrollTop() + $(window).height() + 150)
loadNextPages();
});
$(window).on('load', function () {
loadNextPages(3);
});

View File

@@ -1,164 +0,0 @@
var waitlist;
var sem;
$(function () {
waitlist = !(window.location.href.includes('https://utdirect.utexas.edu/registration/classlist.WBX'));
sem = waitlist ? $('[name="s_ccyys"]').val() : $("option[selected='selected']").val();
if (waitlist) {
$("[href='#top']").before(Template.Import.import_button());
$("[name='wl_see_my_waitlists']").before(Template.Import.store_waitlist_message());
$("[name='wl_see_my_waitlists']").after(Template.Import.waitlist_import_button());
extractWaitlistInfo();
} else {
$("table").after(Template.Import.import_button());
}
$("#import").prepend("<div id='snackbar'>import snackbar..</div>");
$("#import").click(function () {
search_nodes = waitlist ? $(".tbg").last().find(".tbon>td:first-child") : $("tr>td:first-child");
$(search_nodes).each(function () {
importCourse($(this), true);
})
importButtonAnimation($(this));
});
$("#import_waitlist").click(function () {
search_nodes = $("tr.tb span:first-child");
$(search_nodes).each(function () {
importCourse($(this), false);
})
importButtonAnimation($(this));
});
});
function extractWaitlistInfo(){
let class_boxes = $("[name='wl_see_my_waitlists']>table");
let waitlist_info = [];
$(class_boxes).each(function(){
let data = $(this).find('tr.tb span');
let unique_num = $(data[0]).text().trim();
let class_name = $(data[1]).text().trim().split('\n').filter(part => part.trim() != '').map(part => part.trim()).join(' ');
let waitlist_size = $(this).find('tr.tbon:eq(2) td:eq(1)').text().trim().split(' of ')[1];
waitlist_info.push({
"id": unique_num,
"class": class_name,
"wait": waitlist_size,
"time": moment().format('DD-MM-YYYY HH:mm:ss')
});
});
console.log(waitlist_info);
return waitlist_info;
}
function importButtonAnimation(button) {
let is_waitlisted_button = $(button).attr('id') == "import_waitlist";
let return_text = is_waitlisted_button ? Text.waitlist_button_text_default : Text.button_text_default;
$(button).text(Text.button_success).css("background-color", Colors.open);
setTimeout(function () {
$(button).html(return_text).css('background-color', Colors.waitlisted);
}, 1000);
}
function importCourse(unique_node, force) {
let unique = $(unique_node).text().replace(/\s/g, '').substring(0,5);
link = `https://utdirect.utexas.edu/apps/registrar/course_schedule/${sem}/${unique}/`;
buildAddCourse(link, force)
}
function buildAddCourse(link, force) {
$.get(link, function (response) {
if (response) {
let simp_course = buildSimplifiedCourseObject(response, link, force);
chrome.runtime.sendMessage({
command: "courseStorage",
course: simp_course,
action: "add"
}, function () {
chrome.runtime.sendMessage({
command: "updateCourseList"
});
});
}
})
}
function buildSimplifiedCourseObject(response, link, force) {
let imported_course = getCourseObject(htmlToNode(response), link);
let {
full_name,
unique,
prof_name,
individual,
status,
register
} = curr_course;
let dtarr = getDayTimeArray(undefined, curr_course);
if(force === true) {
status = "open" //forces the green status for courses a user is already registered for
}
return new Course(full_name, unique, prof_name, dtarr, status, individual, register);
}
/*For a row, get all the course information and add the date-time-lines*/
function getCourseObject(response_node, individual) {
let course_name = $(response_node).find("#details h2").text();
let course_row = $(response_node).find('table');
curr_course = buildBasicCourseInfo(course_row, course_name, individual);
}
function buildBasicCourseInfo(row, course_name, individual) {
let {
name,
department,
number
} = separateCourseNameParts(course_name);
let instructor_text = $(row).find('td[data-th="Instructor"]').text();
let has_initial = instructor_text.indexOf(',') > 0;
course_info = {
"full_name": course_name,
"name": name,
"department": department,
"number": number,
"individual": individual ? individual : $(row).find('td[data-th="Unique"] a').prop('href'),
"register": $(row).find('td[data-th="Add"] a').prop('href'),
"unique": $(row).find('td[data-th="Unique"]').text(),
"status": $(row).find('td[data-th="Status"]').text(),
"prof_name": instructor_text ? has_initial ? capitalizeString(instructor_text.split(', ')[0]) : capitalizeString(instructor_text) : "Undecided",
"initial": instructor_text && has_initial ? instructor_text.split(', ')[1].substring(0, 1) : "",
"time_data": {
"days": $(row).find('td[data-th="Days"]>span').toArray().map(x => $(x).text().trim()),
"times": $(row).find('td[data-th="Hour"]>span').toArray().map(x => $(x).text().trim()),
"places": $(row).find('td[data-th="Room"]>span').toArray().map(x => $(x).text().trim())
},
"links": {}
}
return course_info;
}
/* For a row, get the date-time-array for checking conflicts*/
function getDayTimeArray(row, course_info) {
var day_time_array = []
let days = course_info ? course_info["time_data"]["days"] : $(row).find('td[data-th="Days"]>span').toArray().map(x => $(x).text().trim());
let times = course_info ? course_info["time_data"]["times"] : $(row).find('td[data-th="Hour"]>span').toArray().map(x => $(x).text().trim());
let places = course_info ? course_info["time_data"]["places"] : $(row).find('td[data-th="Room"]>span').toArray().map(x => $(x).text().trim());
for (var i = 0; i < days.length; i++) {
let date = days[i];
let time = times[i];
let place = places[i];
for (var j = 0; j < date.length; j++) {
let letter = date.charAt(j);
if (letter == "T" && j < date.length - 1 && date.charAt(j + 1) == "H") {
day_time_array.push(["TH", convertTime(time), place]);
} else {
if (letter != "H")
day_time_array.push([letter, convertTime(time), place]);
}
}
}
return day_time_array;
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

231
js/lib/ics.min.js vendored
View File

@@ -1,231 +0,0 @@
/*! ics.js Wed Aug 20 2014 17:23:02 */
var saveAs = saveAs || function (e) {
"use strict";
if (typeof e === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
return
}
var t = e.document,
n = function () {
return e.URL || e.webkitURL || e
},
r = t.createElementNS("http://www.w3.org/1999/xhtml", "a"),
o = "download" in r,
a = function (e) {
var t = new MouseEvent("click");
e.dispatchEvent(t)
},
i = /constructor/i.test(e.HTMLElement) || e.safari,
f = /CriOS\/[\d]+/.test(navigator.userAgent),
u = function (t) {
(e.setImmediate || e.setTimeout)(function () {
throw t
}, 0)
},
s = "application/octet-stream",
d = 1e3 * 40,
c = function (e) {
var t = function () {
if (typeof e === "string") {
n().revokeObjectURL(e)
} else {
e.remove()
}
};
setTimeout(t, d)
},
l = function (e, t, n) {
t = [].concat(t);
var r = t.length;
while (r--) {
var o = e["on" + t[r]];
if (typeof o === "function") {
try {
o.call(e, n || e)
} catch (a) {
u(a)
}
}
}
},
p = function (e) {
if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)) {
return new Blob([String.fromCharCode(65279), e], {
type: e.type
})
}
return e
},
v = function (t, u, d) {
if (!d) {
t = p(t)
}
var v = this,
w = t.type,
m = w === s,
y, h = function () {
l(v, "writestart progress write writeend".split(" "))
},
S = function () {
if ((f || m && i) && e.FileReader) {
var r = new FileReader;
r.onloadend = function () {
var t = f ? r.result : r.result.replace(/^data:[^;]*;/, "data:attachment/file;");
var n = e.open(t, "_blank");
if (!n) e.location.href = t;
t = undefined;
v.readyState = v.DONE;
h()
};
r.readAsDataURL(t);
v.readyState = v.INIT;
return
}
if (!y) {
y = n().createObjectURL(t)
}
if (m) {
e.location.href = y
} else {
var o = e.open(y, "_blank");
if (!o) {
e.location.href = y
}
}
v.readyState = v.DONE;
h();
c(y)
};
v.readyState = v.INIT;
if (o) {
y = n().createObjectURL(t);
setTimeout(function () {
r.href = y;
r.download = u;
a(r);
h();
c(y);
v.readyState = v.DONE
});
return
}
S()
},
w = v.prototype,
m = function (e, t, n) {
return new v(e, t || e.name || "download", n)
};
if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
return function (e, t, n) {
t = t || e.name || "download";
if (!n) {
e = p(e)
}
return navigator.msSaveOrOpenBlob(e, t)
}
}
w.abort = function () {};
w.readyState = w.INIT = 0;
w.WRITING = 1;
w.DONE = 2;
w.error = w.onwritestart = w.onprogress = w.onwrite = w.onabort = w.onerror = w.onwriteend = null;
return m
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content);
if (typeof module !== "undefined" && module.exports) {
module.exports.saveAs = saveAs
} else if (typeof define !== "undefined" && define !== null && define.amd !== null) {
define("FileSaver.js", function () {
return saveAs
})
}
var ics = function (e, t) {
"use strict"; {
if (!(navigator.userAgent.indexOf("MSIE") > -1 && -1 == navigator.userAgent.indexOf("MSIE 10"))) {
void 0 === e && (e = "default"), void 0 === t && (t = "Calendar");
var r = -1 !== navigator.appVersion.indexOf("Win") ? "\r\n" : "\n",
n = [],
i = ["BEGIN:VCALENDAR", "PRODID:" + t, "VERSION:2.0"].join(r),
o = r + "END:VCALENDAR",
a = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"];
return {
events: function () {
return n
},
calendar: function () {
return i + r + n.join(r) + o
},
addEvent: function (t, i, o, l, u, s) {
if (void 0 === t || void 0 === i || void 0 === o || void 0 === l || void 0 === u) return !1;
if (s && !s.rrule) {
if ("YEARLY" !== s.freq && "MONTHLY" !== s.freq && "WEEKLY" !== s.freq && "DAILY" !== s.freq) throw "Recurrence rrule frequency must be provided and be one of the following: 'YEARLY', 'MONTHLY', 'WEEKLY', or 'DAILY'";
if (s.until && isNaN(Date.parse(s.until))) throw "Recurrence rrule 'until' must be a valid date string";
if (s.interval && isNaN(parseInt(s.interval))) throw "Recurrence rrule 'interval' must be an integer";
if (s.count && isNaN(parseInt(s.count))) throw "Recurrence rrule 'count' must be an integer";
if (void 0 !== s.byday) {
if ("[object Array]" !== Object.prototype.toString.call(s.byday)) throw "Recurrence rrule 'byday' must be an array";
if (s.byday.length > 7) throw "Recurrence rrule 'byday' array must not be longer than the 7 days in a week";
s.byday = s.byday.filter(function (e, t) {
return s.byday.indexOf(e) == t
});
for (var c in s.byday)
if (a.indexOf(s.byday[c]) < 0) throw "Recurrence rrule 'byday' values must include only the following: 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'"
}
}
var g = new Date(l),
d = new Date(u),
f = new Date,
S = ("0000" + g.getFullYear().toString()).slice(-4),
E = ("00" + (g.getMonth() + 1).toString()).slice(-2),
v = ("00" + g.getDate().toString()).slice(-2),
y = ("00" + g.getHours().toString()).slice(-2),
A = ("00" + g.getMinutes().toString()).slice(-2),
T = ("00" + g.getSeconds().toString()).slice(-2),
b = ("0000" + d.getFullYear().toString()).slice(-4),
D = ("00" + (d.getMonth() + 1).toString()).slice(-2),
N = ("00" + d.getDate().toString()).slice(-2),
h = ("00" + d.getHours().toString()).slice(-2),
I = ("00" + d.getMinutes().toString()).slice(-2),
R = ("00" + d.getMinutes().toString()).slice(-2),
M = ("0000" + f.getFullYear().toString()).slice(-4),
w = ("00" + (f.getMonth() + 1).toString()).slice(-2),
L = ("00" + f.getDate().toString()).slice(-2),
O = ("00" + f.getHours().toString()).slice(-2),
p = ("00" + f.getMinutes().toString()).slice(-2),
Y = ("00" + f.getMinutes().toString()).slice(-2),
U = "",
V = "";
y + A + T + h + I + R != 0 && (U = "T" + y + A + T, V = "T" + h + I + R);
var B, C = S + E + v + U,
j = b + D + N + V,
m = M + w + L + ("T" + O + p + Y);
if (s)
if (s.rrule) B = s.rrule;
else {
if (B = "rrule:FREQ=" + s.freq, s.until) {
var x = new Date(Date.parse(s.until)).toISOString();
B += ";UNTIL=" + x.substring(0, x.length - 13).replace(/[-]/g, "") + "000000Z"
}
s.interval && (B += ";INTERVAL=" + s.interval), s.count && (B += ";COUNT=" + s.count), s.byday && s.byday.length > 0 && (B += ";BYDAY=" + s.byday.join(","))
}(new Date).toISOString();
var H = ["BEGIN:VEVENT", "UID:" + n.length + "@" + e, "CLASS:PUBLIC", "DESCRIPTION:" + i, "DTSTAMP;VALUE=DATE-TIME:" + m, "DTSTART;VALUE=DATE-TIME:" + C, "DTEND;VALUE=DATE-TIME:" + j, "LOCATION:" + o, "SUMMARY;LANGUAGE=en-us:" + t, "TRANSP:TRANSPARENT", "END:VEVENT"];
return B && H.splice(4, 0, B), H = H.join(r), n.push(H), H
},
download: function (e, t) {
if (n.length < 1) return !1;
t = void 0 !== t ? t : ".ics", e = void 0 !== e ? e : "calendar";
var a, l = i + r + n.join(r) + o;
if (-1 === navigator.userAgent.indexOf("MSIE 10")) a = new Blob([l]);
else {
var u = new BlobBuilder;
u.append(l), a = u.getBlob("text/x-vCalendar;charset=" + document.characterSet)
}
return saveAs(a, e + t), l
},
build: function () {
return !(n.length < 1) && i + r + n.join(r) + o
}
}
}
console.log("Unsupported Browser")
}
};

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
(function($){"use strict";var combinators=[" ",">","+","~"];var fraternisers=["+","~"];var complexTypes=["ATTR","PSEUDO","ID","CLASS"];function grok(msobserver){if(!$.find.tokenize){msobserver.isCombinatorial=true;msobserver.isFraternal=true;msobserver.isComplex=true;return}msobserver.isCombinatorial=false;msobserver.isFraternal=false;msobserver.isComplex=false;var token=$.find.tokenize(msobserver.selector);for(var i=0;i<token.length;i++){for(var j=0;j<token[i].length;j++){if(combinators.indexOf(token[i][j].type)!=-1)msobserver.isCombinatorial=true;if(fraternisers.indexOf(token[i][j].type)!=-1)msobserver.isFraternal=true;if(complexTypes.indexOf(token[i][j].type)!=-1)msobserver.isComplex=true}}}var MutationSelectorObserver=function(selector,callback,options){this.selector=selector.trim();this.callback=callback;this.options=options;grok(this)};var msobservers=[];msobservers.initialize=function(selector,callback,options){var seen=[];var callbackOnce=function(){if(seen.indexOf(this)==-1){seen.push(this);$(this).each(callback)}};$(options.target).find(selector).each(callbackOnce);var msobserver=new MutationSelectorObserver(selector,callbackOnce,options);this.push(msobserver);var observer=new MutationObserver(function(mutations){var matches=[];for(var m=0;m<mutations.length;m++){if(mutations[m].type=="attributes"){if(mutations[m].target.matches(msobserver.selector))matches.push(mutations[m].target);if(msobserver.isFraternal)matches.push.apply(matches,mutations[m].target.parentElement.querySelectorAll(msobserver.selector));else matches.push.apply(matches,mutations[m].target.querySelectorAll(msobserver.selector))}if(mutations[m].type=="childList"){for(var n=0;n<mutations[m].addedNodes.length;n++){if(!(mutations[m].addedNodes[n]instanceof Element))continue;if(mutations[m].addedNodes[n].matches(msobserver.selector))matches.push(mutations[m].addedNodes[n]);if(msobserver.isFraternal)matches.push.apply(matches,mutations[m].addedNodes[n].parentElement.querySelectorAll(msobserver.selector));else matches.push.apply(matches,mutations[m].addedNodes[n].querySelectorAll(msobserver.selector))}}}for(var i=0;i<matches.length;i++)$(matches[i]).each(msobserver.callback)});var defaultObeserverOpts={childList:true,subtree:true,attributes:msobserver.isComplex};observer.observe(options.target,options.observer||defaultObeserverOpts);return observer};$.fn.initialize=function(callback,options){return msobservers.initialize(this.selector,callback,$.extend({},$.initialize.defaults,options))};$.initialize=function(selector,callback,options){return msobservers.initialize(selector,callback,$.extend({},$.initialize.defaults,options))};$.initialize.defaults={target:document.documentElement,observer:null}})(jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,60 +0,0 @@
var manifestData = chrome.runtime.getManifest();
$("#version").text(manifestData.version);
chrome.storage.sync.get("options", function (data) {
if (data.options) {
console.log(data.options);
Object.keys(data.options).forEach(key => {
let enabled = data.options[key];
$("#options_container").append(Template.Options.options_row(key, enabled));
});
}
});
$("body").on("click", "button", function () {
let key = $(this).attr("id");
let old_status = $(this).val() === "true";
let new_status = !old_status;
chrome.runtime.sendMessage(
{
command: "setOptionsValue",
key: key,
value: new_status,
},
function (response) {
console.log(response.value);
toggle(key, response.value);
updateAllTabsCourseList();
}
);
});
$.get("https://api.github.com/repos/sghsri/UT-Registration-Plus/stats/contributors", data => {
data = data.sort((a, b) => b.total - a.total);
console.log("data", data);
for (var contributorData of data) {
$.get(`https://api.github.com/users/${contributorData.author.login}`, userData => {
let fullData = { ...contributorData, ...userData };
let { login, avatar_url, html_url, name } = fullData;
if(name){
$("#contributor-list").append(Template.Options.contributor_card(login, name, avatar_url, html_url));
}
else{
$("#contributor-list").append(Template.Options.contributor_card("", login, avatar_url, html_url));
}
});
}
});
$("body").on("click", ".contributor-card", function () {
console.log("hello world");
window.open($(this).data("url"), "_blank");
});
function toggle(key, value) {
let button_text = value ? "Turn Off" : "Turn On";
let button_color = value ? Colors.closed : Colors.open;
$(`#${key}`).text(button_text);
$(`#${key}`).css("background", button_color);
$(`#${key}`).val(value);
}

View File

@@ -1,437 +0,0 @@
var courses;
setCourseList();
getSemesters();
getDepartments();
var can_remove = true;
function setCourseList() {
$("#courseList").empty();
chrome.storage.sync.get("savedCourses", function (data) {
updateConflicts();
courses = data.savedCourses;
handleEmpty();
let hours = 0;
// build and append the course list element
for (var i = 0; i < courses.length; i++) {
let { coursename, unique, profname, status, datetimearr } = courses[i];
profname = capitalizeString(profname);
let line = buildTimeLines(datetimearr);
let list_tile_color = getStatusColor(status);
let list_sub_color = getStatusColor(status, true);
let { department, number } = separateCourseNameParts(coursename);
let class_length = parseInt(number.charAt(0));
let multi_semester_code = number.slice(-1);
if (["A", "B"].includes(multi_semester_code)) {
hours += Math.floor(class_length / 2);
} else if (["X", "Y", "Z"].includes(multi_semester_code)) {
hours += Math.floor(class_length / 3);
} else {
hours += class_length;
}
let list_html = Template.Popup.list_item(i, list_tile_color, unique, department, number, profname, list_sub_color, line);
$("#courseList").append(list_html);
}
$("#meta-metric").text(hours);
});
}
/* convert from the dtarr and maek the time lines*/
function buildTimeLines(datetimearr) {
let lines = convertDateTimeArrToLine(datetimearr);
let output = "";
if (lines.length == 0) {
output = "<span style='font-size:medium;'>This class has no meeting times.</span>";
} else {
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
output += Template.Popup.line(line);
}
}
return output;
}
/* Update the conflict messages */
function updateConflicts() {
chrome.runtime.sendMessage(
{
command: "checkConflicts",
},
function (response) {
console.log("updateConflicts -> response", response);
if (response.isConflict) {
var between = response.between;
let conflict_message = "";
for (var i = 0; i < between.length; i++) {
let courseA = between[i][0];
let courseB = between[i][1];
conflict_message += `CONFLICT: ${formatShortenedCourseName(courseA)} and ${formatShortenedCourseName(courseB)}`;
if (i != between.length - 1) conflict_message += "<br>";
}
$(Template.Popup.conflict_message(conflict_message)).prependTo("#courseList").hide().fadeIn(200);
}
}
);
}
/* prettify the name for the conflict messages*/
function formatShortenedCourseName(course) {
let { number, department } = separateCourseNameParts(course.coursename);
return `${department} ${number} (${course.unique})`;
}
$(document).click(function (event) {
$target = $(event.target);
// If we're not clicking on search button or search popup, and popup is visible, hide it
if (!$target.closest("#search").length && !$target.closest("#search-popup").length && $("#search-popup").is(":visible")) {
hideSearchPopup();
}
// If we're not clicking on import/export button or imp/exp popup, and popup is visible, hide it
if (!$target.closest("#impexp").length && !$target.closest("#import-export-popup").length && $("#import-export-popup").is(":visible")) {
hideImportExportPopup();
}
});
$("#clear").click(function () {
chrome.storage.sync.set({
savedCourses: [],
});
$("#courseList").empty();
updateAllTabsCourseList();
showEmpty();
});
$("#RIS").click(function () {
chrome.tabs.create({
url: "https://utdirect.utexas.edu/registrar/ris.WBX",
});
});
$("#calendar").click(function () {
chrome.tabs.create({
url: "calendar.html",
});
});
$("#impexp").click(function () {
if ($("#impexp>i").text() == "close") {
hideImportExportPopup();
} else {
if ($("#search>i").text() == "close") {
hideSearchPopup();
}
showImportExportPopup();
}
});
$("#search").click(function () {
if ($("#search>i").text() == "close") {
hideSearchPopup();
} else {
if ($("#impexp>i").text() == "close") {
hideImportExportPopup();
}
showSearchPopup();
}
});
$("#import-class").click(function () {
$("#import_input").click();
console.log("back to improting");
});
function isImportedValid(imported_courses) {
return imported_courses && imported_courses.length && (imported_courses.length == 0 || validateCourses(imported_courses));
}
$("#import_input").change(function (e) {
console.log("hello");
var files = e.target.files;
var reader = new FileReader();
reader.onload = function () {
try {
var imported_courses = JSON.parse(this.result);
if (isImportedValid(imported_courses)) {
chrome.storage.sync.set({
savedCourses: imported_courses,
});
updateAllTabsCourseList();
setCourseList();
hideImportExportPopup();
$("#import_input").val("");
} else {
Alert("There was an error.");
}
} catch (err) {
console.log(err);
}
};
reader.readAsText(files[0]);
});
function exportCourses(url) {
var exportlink = document.createElement("a");
exportlink.setAttribute("href", url);
exportlink.setAttribute("download", "my_courses.json");
exportlink.click();
}
function createBlob(export_courses) {
return new Blob([JSON.stringify(export_courses, null, 4)], {
type: "octet/stream",
});
}
$("#export-class").click(function () {
chrome.storage.sync.get("savedCourses", function (data) {
let export_courses = data.savedCourses;
if (export_courses.length > 0) {
let url = window.URL.createObjectURL(createBlob(export_courses));
exportCourses(url);
} else {
alert("No Saved Courses to Export.");
}
hideImportExportPopup();
});
});
function openSearch(semester, department, level, courseCode) {
var link = "";
if (courseCode) {
link = `https://utdirect.utexas.edu/apps/registrar/course_schedule/${semester}/results/?search_type_main=COURSE&fos_cn=${department}&course_number=${courseCode}`;
} else {
link = `https://utdirect.utexas.edu/apps/registrar/course_schedule/${semester}/results/?fos_fl=${department}&level=${level}&search_type_main=FIELD`;
}
chrome.tabs.create({ url: link });
}
$("#search-class").click(() => {
let semester = $("#semesters").find(":selected").val();
let department = $("#department").find(":selected").val();
let level = $("#level").find(":selected").val();
let courseCode = $("#courseCode").val();
openSearch(semester, department, level, courseCode);
});
$("#options_button").click(function () {
chrome.tabs.create({
url: "options.html",
});
});
$("#courseList")
.on("mouseover", ".copy_button", function () {
$(this).addClass("shadow");
})
.on("mouseleave", ".copy_button", function () {
$(this).removeClass("shadow");
});
$("#courseList").on("click", ".copy_button", function (e) {
e.stopPropagation();
copyButtonAnimation($(this));
let unique = $(this).val();
copyUnique(unique);
});
function copyUnique(unique) {
var temp = $("<input>");
$("body").append(temp);
temp.val(unique).select();
document.execCommand("copy");
temp.remove();
}
$("#courseList").on("click", "li", function () {
let clicked_item = $(this).closest("li");
let curr_course = courses[$(clicked_item).attr("id")];
handleMoreInfo(clicked_item, curr_course);
handleRegister(clicked_item, curr_course);
handleRemove(clicked_item, curr_course);
toggleTimeDropdown(clicked_item);
});
function handleRegister(clicked_item, curr_course) {
let { status, registerlink } = curr_course;
let register_button = $(clicked_item).find("#register");
let can_not_register = canNotRegister(status, registerlink);
let register_text = can_not_register ? "Can't Register" : status.includes("waitlisted") ? "Join Waitlist" : "Register";
let register_color = can_not_register ? Colors.closed : status.includes("waitlisted") ? Colors.waitlisted : Colors.open;
if (!status) {
register_text = "No Status";
register_color = Colors.no_status;
}
$(register_button).text(register_text).css("background-color", register_color);
if (!can_not_register) {
$(register_button).click(function () {
setCurrentTabUrl(registerlink);
});
}
}
function handleRemove(clicked_item, curr_course) {
let list = $(clicked_item).closest("ul");
$(clicked_item)
.find("#listRemove")
.click(function () {
if (can_remove) {
can_remove = false;
$(list)
.find("#conflict")
.fadeOut(300, function () {
$(clicked_item).remove();
});
subtractHours(curr_course);
chrome.runtime.sendMessage(
{
command: "courseStorage",
course: curr_course,
action: "remove",
},
() => {
$(clicked_item).fadeOut(200);
if ($(list).children(":visible").length === 1) showEmpty();
can_remove = true;
updateConflicts();
updateAllTabsCourseList();
}
);
}
});
}
function subtractHours(curr_course) {
let curr_total_hours = parseInt($("#meta-metric").text());
let curr_course_number = separateCourseNameParts(curr_course.coursename).number;
let class_length = parseInt(curr_course_number.charAt(0));
let multi_semester_code = curr_course_number.slice(-1);
if (["A", "B"].includes(multi_semester_code)) {
$("#meta-metric").text(curr_total_hours - Math.floor(class_length / 2));
} else if (["X", "Y", "Z"].includes(multi_semester_code)) {
$("#meta-metric").text(curr_total_hours - Math.floor(class_length / 3));
} else {
$("#meta-metric").text(curr_total_hours - class_length);
}
}
function handleMoreInfo(clicked_item, curr_course) {
$(clicked_item)
.find("#listMoreInfo")
.click(function () {
openMoreInfoWithOpenModal(curr_course.link);
});
}
function handleEmpty() {
if (courses.length != 0) {
$("#empty").hide();
$("#courseList").show();
} else {
showEmpty();
}
}
function copyButtonAnimation(copy_button) {
$(copy_button).find("i").text("check");
$(copy_button).stop(true, false).removeAttr("style").removeClass("shadow", {
duration: 200,
});
$(copy_button)
.find("i")
.delay(400)
.queue(function (n) {
$(this).text("content_copy");
$(this).parent().removeClass("shadow");
if ($(this).parent().is(":hover")) {
$(this).parent().addClass("shadow");
}
n();
});
}
function toggleTimeDropdown(clicked_item) {
let more_info_button = $(clicked_item).find("#moreInfo");
let arrow = $(clicked_item).find("#arrow");
if ($(more_info_button).is(":hidden")) {
$(more_info_button).fadeIn(200);
$(arrow).css("transform", "rotate(90deg)");
} else {
$(more_info_button).fadeOut(200);
$(arrow).css("transform", "");
}
}
function showEmpty() {
$("#courseList").hide();
$("#empty").fadeIn(200);
$("#main").html(Text.emptyText());
$("#meta-metric").text("0");
}
function hideSearchPopup() {
$("#search>i").text("search");
$("#semcon").hide();
$("#depcon").hide();
$("#semesters").hide();
$("#levcon").hide();
$("#search-popup").addClass("hide");
}
function showSearchPopup() {
$("#search>i").text("close");
$("#class_id_input").show();
$("#semesters").show();
$("#semcon").show();
$("#depcon").show();
$("#levcon").show();
$("#search-popup").removeClass("hide");
}
function hideImportExportPopup() {
$("#import-export-popup").addClass("hide");
$("#impexp>i").text("import_export");
}
function showImportExportPopup() {
$("#impexp>i").text("close");
$("#import-export-popup").removeClass("hide");
}
function getSemesters() {
chrome.runtime.sendMessage(
{
command: "currentSemesters",
},
function (response) {
let { semesters } = response;
let semester_names = Object.keys(semesters);
for (let i = 0; i < semester_names.length; i++) {
let name = semester_names[i];
$("#semesters").append(`<option value='${semesters[name]}'>${name}</option>`);
}
}
);
}
function getDepartments() {
chrome.runtime.sendMessage(
{
command: "currentDepartments",
},
function (response) {
let { departments } = response;
console.log(departments);
for (let i = 0; i < departments.length; i++) {
let abv = departments[i];
$("#department").append(`<option value='${abv}'>${abv}</option>`);
}
// $("#department").val('C S');
}
);
}

View File

@@ -1,242 +0,0 @@
let semester_code = "";
curr_course = {}
chrome.runtime.sendMessage({
command: "currentSemesters"
}, function(response){
let semester_text = $('.row:contains(Semester)').find('span').text();
let key = semester_text.split(' ').reverse().join(' ');
semester_code = response.semesters[key];
});
$.initialize("table.section-detail-grid", function () {
$(this).find('thead>tr').append('<th> Plus</th')
$(this).find('tbody>tr').each(function () {
$(this).append(Template.Main.extension_button());
})
});
$("body").prepend(Template.UTPlanner.modal());
$("body").on('click', '#distButton', function () {
var row = $(this).closest('tr');
$('.modal-content').stop().animate({ scrollTop: 0 }, 500);
$(this).blur();
getCourseInfo(row)
});
function getCourseInfo(row) {
let rowdata = $(row).find('td').slice(3).toArray().map(x => $(x).text().trim());
let [uniquenum, department, coursenum, coursename, profname, notes, rawtime] = rowdata
let profinit = ""
if (profname !== undefined && profname != "Staff") {
profinit = profname.split(',')[1].trim();
profname = profname.split(',')[0].trim();
}
let times = rawtime.split('\n').map(x => x.trim());
var course_data = {
"unique": uniquenum,
"department": department,
"number": coursenum,
"name": coursename,
"prof_name": profname,
"initial": profinit,
"notes": notes,
"individual": `https://utdirect.utexas.edu/apps/registrar/course_schedule/${semester_code}/${uniquenum}/`,
"times": times,
}
curr_course = buildCourseLinks(course_data);
getDistribution(course_data);
var modal = document.getElementById('myModal');
window.onclick = function (event) {
if (event.target == modal) {
close();
}
}
}
function buildCourseLinks(course_info) {
console.log(semester_code);
let {
department,
number,
unique,
prof_name
} = course_info
links = {
"textbook": `https://www.universitycoop.com/adoption-search-results?sn=${semester_code}__${department}__${number}__${unique}`,
"syllabi": `https://utdirect.utexas.edu/apps/student/coursedocs/nlogon/?year=&semester=&department=${department}&course_number=${number}&course_title=&unique=&instructor_first=&instructor_last=${prof_name}&course_type=In+Residence&search=Search`,
}
course_info["links"] = links;
return course_info;
}
function badData(course_data, res) {
return typeof res == 'undefined' || course_data["prof_name"] == "Staff";
}
$("#semesters").on('change', function () {
var sem = $(this).val();
sem = sem == "Aggregate" ? undefined : sem;
getDistribution(curr_course, sem);
});
$("#Syllabi").click(function () {
setTimeout(function () {
window.open(curr_course["links"]["syllabi"]);
}, Timing.button_delay);
});
$("#textbook").click(function () {
setTimeout(function () {
window.open(curr_course["links"]["textbook"]);
}, Timing.button_delay);
});
$("#moreInfo").click(function () {
openMoreInfoWithOpenModal(curr_course["individual"]);
});
function toggleChartLoading(loading) {
if (loading) {
$('#chartload').css('display', 'inline-block');
$("#chart").hide();
} else {
$('#chartload').hide();
$("#chart").show();
}
}
function openDialog(course_data, res) {
console.log(course_data);
$("#title").text(buildTitle(course_data))
$("#topbuttons").before(buildTimeTitle(course_data["times"]));
$("#profname").text(buildProfTitle(course_data));
$("#myModal").fadeIn(Timing.fade_time);
buildSemestersDropdown(course_data, res)
var data = []
if (!badData(course_data, res))
data = res.values[0];
setChart(data);
allowClosing();
}
function buildProfTitle(course_data) {
const {
initial,
prof_name
} = course_data;
return `with ${initial?initial+". ":""}${prof_name}`;
}
function buildSemestersDropdown(course_data, res) {
$("#semesters").empty();
if (badData(course_data, res)) {
$("#semesters").append("<option>No Data</option>")
} else {
var semesters = res.values[0][18].split(",");
semesters.sort(semesterSort);
semesters.reverse().unshift('Aggregate');
var sems = [];
for (var i = 0; i < semesters.length; i++) {
sems.push($(`<option value="${semesters[i]}">${semesters[i]}</option>`));
}
$("#semesters").append(sems);
}
}
/*Query the grades database*/
function getDistribution(course_data, sem) {
toggleChartLoading(true);
let query = buildQuery(course_data, sem);
chrome.runtime.sendMessage({
command: "gradesQuery",
query: query
}, function (response) {
var res = response.data;
if (!sem) {
openDialog(course_data, res);
} else {
var data = badData(course_data, res) ? [] : res.values[0];
setChart(data);
}
});
}
function buildTitle(course_data) {
return `${course_data["name"]} (${course_data["department"]} ${course_data["number"]})`
}
function buildTimeTitle(times) {
$("h2.dateTimePlace").remove();
var lines = []
for (var i = 0; i < times.length; i++) {
date = times[i].substring(0, times[i].indexOf(' ')).toUpperCase();
time = times[i].substring(times[i].indexOf(' ') + 1, times[i].lastIndexOf('-')).trim();
place = times[i].substring(times[i].lastIndexOf('-') + 1).trim();
lines.push($(`<h2 class="dateTimePlace">${makeLine(date, time, place)}</th>`));
}
return lines
}
function makeLine(date, time, place) {
var arr = separateDays(date)
var output = prettifyDaysText(arr)
var building = place.substring(0, place.search(/\d/) - 1);
building = building == "" ? "Undecided Location" : building;
return `${output} at ${time.replace(/\./g, '').replace(/\-/g, ' to ')} in <a style='font-size:medium' target='_blank' href='https://maps.utexas.edu/buildings/UTM/${building}'>${building}</>`;
}
function setChart(data) {
//set up the chart
toggleChartLoading(false);
chart = Highcharts.chart('chart', buildChartConfig(data), function (chart) { // on complete
if (data.length == 0) {
//if no data, then show the message and hide the series
chart.renderer.text('Could not find data for this Instructor teaching this Course.', 100, 120)
.css({
fontSize: '20px',
width: '300px',
align: 'center',
left: '160px'
})
.add();
$.each(chart.series, function (i, ser) {
ser.hide();
});
}
});
}
function standardizeName(department, number, name){
return `${department} ${number} ${name}`
}
function allowClosing() {
$('.close').click(function () {
close();
});
$('#myModal').click(function (event) {
if (event.target.id == 'myModal') {
close();
}
});
}
function close() {
$("#myModal").fadeOut(Timing.fade_time);
$("#snackbar").attr("class", "");
}
$(document).keydown(function (e) {
/*Close Modal when hit escape*/
if (e.keyCode == 27) {
close();
}
});

View File

@@ -1,386 +0,0 @@
const days = new Map([
["M", "Monday"],
["T", "Tuesday"],
["W", "Wednesday"],
["TH", "Thursday"],
["F", "Friday"]
]);
function getStatusColor(status, sub = false) {
let color = "black";
if (status.includes("open")) {
color = sub ? Colors.open_light : Colors.open;
} else if (status.includes("waitlisted")) {
color = sub ? Colors.waitlisted_light : Colors.waitlisted;
} else if (status.includes("closed") || status.includes("cancelled")) {
color = sub ? Colors.closed_light : Colors.closed;
} else {
color = sub ? Colors.no_status_light : Colors.no_status;
}
return color;
}
function buildQuery(course_data, sem) {
let query = !sem ? "select * from agg" : "select * from grades";
query += " where dept like '%" + course_data["department"] + "%'";
query += " and prof like '%" + course_data["prof_name"].replace(/'/g, "") + "%'";
query += " and course_nbr like '%" + course_data["number"] + "%'";
if (sem) {
query += "and sem like '%" + sem + "%'";
}
return query + "order by a1+a2+a3+b1+b2+b3+c1+c2+c3+d1+d2+d3+f desc";
}
/*Course object for passing to background*/
function Course(coursename, unique, profname, datetimearr, status, link, registerlink) {
this.coursename = coursename;
this.unique = unique;
this.profname = profname;
this.datetimearr = datetimearr;
this.status = status;
this.link = link;
this.registerlink = registerlink;
}
function capitalizeString(string) {
//if one word, and if multiple words:
let output = "";
words = string.split(/[. ,\/ -]/);
for (let i in words) {
word = words[i];
capitalizedWord = word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
output += capitalizedWord + " ";
}
return output.trim();
}
function separateCourseNameParts(name) {
let num_index = name.search(/\d/);
department = name.substring(0, num_index).trim();
number = name.substring(num_index, name.indexOf(" ", num_index)).trim();
name = capitalizeString(name.substring(name.indexOf(" ", num_index)).trim());
return {
name: name,
department: department,
number: number
}
}
function separateDays(date, simple=false) {
let arr = [];
for (var i = 0; i < date.length; i++) {
let letter = date.charAt(i);
let separated_letter = letter;
if (letter == "T" && i < date.length - 1 && date.charAt(i + 1) == "H") {
arr.push(simple ? "TH" : days.get("TH"));
} else {
if (letter != "H") {
arr.push(simple ? letter : days.get(letter));
}
}
}
return arr;
}
/*Convert time to 24hour format*/
function convertTime(time) {
var converted = time.replace(/\./g, '').split("-");
for (var i = 0; i < 2; i++) {
converted[i] = moment(converted[i], ["h:mm A"]).format("HH:mm");
}
return converted;
}
function prettifyDaysText(arr) {
var output = "";
if (arr.length > 2) {
for (var i = 0; i < arr.length; i++) {
if (i < arr.length - 1)
output += arr[i] + ", "
if (i == arr.length - 2)
output += "and ";
if (i == arr.length - 1)
output += arr[i];
}
} else if (arr.length == 2) {
output = arr[0] + " and " + arr[1];
} else {
output = arr[0];
}
return output
}
function isIndividualCoursePage(){
return $("#textbook_button").length != 0;
}
function updateAllTabsCourseList() {
chrome.tabs.query({}, function (tabs) {
for (var i = 0; i < tabs.length; i++) {
chrome.tabs.sendMessage(tabs[i].id, {
command: "updateCourseList"
});
}
});
}
function htmlToNode(response) {
return $('<div/>').html(response).contents();
}
function setCurrentTabUrl(link) {
chrome.tabs.query({
currentWindow: true,
active: true
}, function (tab) {
chrome.tabs.update(tab.id, {
url: link
});
});
}
function openMoreInfoWithOpenModal(link){
chrome.runtime.sendMessage({ command: "setOpen", url: link });
}
function semesterSort(semA, semB) {
let semOrder = {
"Spring": 0,
"Fall": 1,
"Summer": 2,
"Winter": 3
}
let aName = semA.split(' ')[0];
let aYear = parseInt(semA.split(' ')[1]);
let bName = semB.split(' ')[0];
let bYear = parseInt(semB.split(' ')[1]);
if (aYear < bYear)
return -1;
if (aYear > bYear)
return 1;
if (semOrder[aName] < semOrder[bName])
return -1;
if (semOrder[aName] > semOrder[bName])
return 1;
return 0;
}
/* convert from the dtarr and maek the time lines*/
function convertDateTimeArrToLine(datetimearr) {
var output = [];
var dtmap = makeDateTimeMap(datetimearr);
var timearr = Array.from(dtmap.keys());
var temporary = Array.from(dtmap.values())
var dayarr = []
var locarr = []
for(x in temporary) {
dayarr.push(temporary[x][0])
locarr.push(temporary[x][1])
}
for (var i = 0; i < dayarr.length; i++) {
//var place = findLocation(dayarr[i], timearr[i], datetimearr);
var place = locarr[i]
var building = place.substring(0, place.search(/\d/)).trim();
building = building ? building : "Undecided Location"
var timearrsplit = timearr[i].split(',')
output.push({
"days": dayarr[i],
"start_time": timearrsplit[0],
"end_time": timearrsplit[1],
"location_link": `https://maps.utexas.edu/buildings/UTM/${building}`,
"location_full": place
})
}
return output;
}
function makeDateTimeMap(datetimearr) {
var dtmap = new Map([]);
for (var i = 0; i < datetimearr.length; i++) {
datetimearr[i][1][0] = moment(datetimearr[i][1][0], ["HH:mm A"]).format("h:mm A");
datetimearr[i][1][1] = moment(datetimearr[i][1][1], ["HH:mm A"]).format("h:mm A");
}
for (var i = 0; i < datetimearr.length; i++) {
var instance = datetimearr[i]
var day = String(instance[0])
var timeslot = String(instance[1])
var location = String(instance[2])
var key = timeslot + "," + location
if (dtmap.has(key) && dtmap.get(key)[1] === location) {
dtmap.set(key, [dtmap.get(key)[0] + day, location]);
} else {
dtmap.set(key, [day, location]);
}
}
return dtmap
}
//find the location of a class given its days and timearrs.
function findLocation(day, timearr, datetimearr) {
for (let i = 0; i < datetimearr.length; i++) {
var dtl = datetimearr[i];
if (day.includes(dtl[0])) {
if (JSON.stringify(timearr) == JSON.stringify(reformatDateTime(dtl[1]))) {
return dtl[2];
}
}
}
}
function validateCourses(courses) {
for (var i = 0; i < courses.length; i++) {
if (!validateCourseObject(courses[i])) {
return false;
}
}
return true;
}
function validateCourseObject(course) {
var is_valid = true;
var props = ["coursename", "datetimearr", "link", "profname", "status", "unique"];
for (let j = 0; j < props.length; j++) {
is_valid &= course.hasOwnProperty(props[j]);
}
return is_valid;
}
function reformatDateTime(dtl1) {
let output = "";
for (let i = 0; i < dtl1.length; i++) {
output += dtl1[i];
if (i != dtl1.length - 1) {
output += ",";
}
}
return output;
}
function rgb2hex(rgb) {
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2);
}
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}
function buildChartConfig(data) {
return {
chart: {
type: 'column',
backgroundColor: ' #fefefe',
spacingLeft: 10
},
title: {
text: null
},
subtitle: {
text: null
},
legend: {
enabled: false
},
xAxis: {
title: {
text: 'Grades'
},
categories: [
'A',
'A-',
'B+',
'B',
'B-',
'C+',
'C',
'C-',
'D+',
'D',
'D-',
'F'
],
crosshair: true
},
yAxis: {
min: 0,
title: {
text: 'Students'
}
},
credits: {
enabled: false
},
lang: {
noData: "The professor hasn't taught this class :("
},
tooltip: {
headerFormat: '<span style="font-size:small; font-weight:bold">{point.key}</span><table>',
pointFormat: '<td style="color:{black};padding:0;font-size:small; font-weight:bold;"><b>{point.y:.0f} Students</b></td>',
footerFormat: '</table>',
shared: true,
useHTML: true
},
plotOptions: {
bar: {
pointPadding: 0.2,
borderWidth: 0
},
series: {
animation: {
duration: 700
}
}
},
series: [{
name: 'Grades',
data: [{
y: data[6],
color: '#4CAF50'
}, {
y: data[7],
color: '#8BC34A'
}, {
y: data[8],
color: '#CDDC39'
}, {
y: data[9],
color: '#FFEB3B'
}, {
y: data[10],
color: '#FFC107'
}, {
y: data[11],
color: '#FFA000'
}, {
y: data[12],
color: '#F57C00'
}, {
y: data[13],
color: '#FF5722'
}, {
y: data[14],
color: '#FF5252'
}, {
y: data[15],
color: '#E64A19'
}, {
y: data[16],
color: '#F44336'
}, {
y: data[17],
color: '#D32F2F'
}]
}]
}
}
function canNotRegister(status, register_link) {
return status.includes("closed") || status.includes("cancelled") || !status || !register_link
}

View File

@@ -1,70 +0,0 @@
{
"manifest_version": 2,
"name": "UT Registration Plus",
"version": "1.2.2.7",
"options_page": "options.html",
"description": "Improves the course registration process at the University of Texas at Austin!",
"permissions": [
"storage",
"*://*.utdirect.utexas.edu/apps/registrar/course_schedule/*",
"*://*.utexas.collegescheduler.com/*",
"*://*.catalog.utexas.edu/ribbit/",
"*://*.registrar.utexas.edu/schedules/*",
"*://*.login.utexas.edu/login/*"
],
"content_scripts": [
{
"css": ["css/styles.css"],
"js": [
"js/config.js",
"js/lib/moment.min.js",
"js/lib/highcharts.js",
"js/lib/jquery-3.3.1.min.js",
"js/lib/jquery.initialize.min.js",
"js/util.js",
"js/Template.js",
"js/courseCatalog.js"
],
"matches": ["https://utdirect.utexas.edu/apps/registrar/course_schedule/*"]
},
{
"css": ["css/styles.css"],
"js": [
"js/config.js",
"js/lib/moment.min.js",
"js/lib/highcharts.js",
"js/lib/jquery-3.3.1.min.js",
"js/lib/jquery.initialize.min.js",
"js/util.js",
"js/Template.js",
"js/utPlanner.js"
],
"matches": ["https://utexas.collegescheduler.com/*"]
},
{
"css": ["css/styles.css"],
"js": ["js/config.js", "js/lib/moment.min.js", "js/lib/highcharts.js", "js/lib/jquery-3.3.1.min.js", "js/Template.js", "js/util.js", "js/import.js"],
"matches": ["https://utdirect.utexas.edu/registrar/waitlist/wl_see_my_waitlists.WBX", "https://utdirect.utexas.edu/registration/classlist.WBX*"]
}
],
"web_accessible_resources": ["grades.db", "images/disticon.png"],
"background": {
"scripts": ["js/lib/jquery-3.3.1.min.js", "js/lib/sql-memory-growth.js", "js/lib/moment.min.js", "js/config.js", "js/util.js", "js/background.js"],
"persistent": true
},
"browser_action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"32": "icons/icon32.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"icons": {
"16": "icons/icon16.png",
"32": "icons/icon32.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}

View File

@@ -1,30 +0,0 @@
<!DOCTYPE html>
<html>
<!-- This file is serving as the template for the options page -->
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="css/styles.css" />
<link rel="stylesheet" href="css/options.css" />
</head>
<body>
<div id="version-container">
<p class="version">(v<span id="version"></span>)</p>
</div>
<div class="card options-card" id="header">
<h2 class="options-header"><u>Options</u></h2>
<div id="options_container"></div>
<p class="creator-tag"><a href="https://sghsri.github.io">Sriram Hariharan</a> (2018)</p>
</div>
<div class="card options-card" id="contributors_container">
<h3 class="contributor-title">Amazing people who've contributed to the extension!</h3>
<p class="creator-tag open-source-tag">Code is open source here <a href="https://github.com/sghsri/UT-Registration-Plus">here</a> :)</p>
<div id="contributor-list"></div>
</div>
<script src="js/config.js"></script>
<script src="js/lib/jquery-3.3.1.min.js"></script>
<script src="js/util.js"></script>
<script src="js/Template.js"></script>
<script src="js/options.js"></script>
</body>
</html>

23593
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

105
package.json Normal file
View File

@@ -0,0 +1,105 @@
{
"name": "ut-registration-plus",
"displayName": "UT Registration Plus",
"version": "0.0.1",
"description": "The UT Registration Plus extension is a Chrome extension that allows students to easily register for classes at The University of Texas at Austin.",
"private": true,
"homepage": "sriramhariharan.com",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"devtools": "react-devtools",
"preinstall": "npx only-allow pnpm",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"dependencies": {
"@hello-pangea/dnd": "^16.5.0",
"@types/sql.js": "^1.4.9",
"@vitejs/plugin-react": "^4.2.1",
"chrome-extension-toolkit": "^0.0.51",
"clsx": "^2.1.0",
"highcharts": "^11.3.0",
"highcharts-react-official": "^3.2.1",
"html-to-image": "^1.11.11",
"react": "^18.2.0",
"react-devtools-core": "^5.0.0",
"react-dom": "^18.2.0",
"react-window": "^1.8.10",
"sass": "^1.70.0",
"sql.js": "1.10.2",
"styled-components": "^6.1.8",
"uuid": "^9.0.1"
},
"devDependencies": {
"@crxjs/vite-plugin": "2.0.0-beta.21",
"@iconify-json/material-symbols": "^1.1.72",
"@storybook/addon-designs": "^7.0.9",
"@storybook/addon-essentials": "^7.6.13",
"@storybook/addon-links": "^7.6.13",
"@storybook/blocks": "^7.6.13",
"@storybook/react": "^7.6.13",
"@storybook/react-vite": "^7.6.13",
"@storybook/test": "^7.6.13",
"@svgr/core": "^8.1.0",
"@svgr/plugin-jsx": "^8.1.0",
"@types/chrome": "^0.0.260",
"@types/node": "^20.11.17",
"@types/prompts": "^2.4.9",
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"@types/semver": "^7.5.6",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@unocss/eslint-config": "^0.58.5",
"@unocss/postcss": "^0.58.5",
"@unocss/preset-uno": "^0.58.5",
"@unocss/preset-web-fonts": "^0.58.5",
"@unocss/reset": "^0.58.5",
"@unocss/transformer-directives": "^0.58.5",
"@unocss/transformer-variant-group": "^0.58.5",
"@vitejs/plugin-react-swc": "^3.6.0",
"chromatic": "^10.9.1",
"cssnano": "^6.0.3",
"cssnano-preset-advanced": "^6.0.3",
"dotenv": "^16.4.1",
"es-module-lexer": "^1.4.1",
"eslint": "^8.56.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^17.1.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsdoc": "^48.0.6",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-prefer-function-component": "^3.3.0",
"eslint-plugin-react-refresh": "^0.4.5",
"eslint-plugin-storybook": "^0.6.15",
"path": "^0.12.7",
"postcss": "^8.4.35",
"prettier": "^3.2.5",
"react-dev-utils": "^12.0.1",
"react-devtools": "^5.0.0",
"storybook": "^7.6.13",
"typescript": "^5.3.3",
"unocss": "^0.58.5",
"unplugin-icons": "^0.18.5",
"vite": "^5.1.1",
"vite-plugin-inspect": "^0.8.3"
},
"pnpm": {
"patchedDependencies": {
"@crxjs/vite-plugin@2.0.0-beta.21": "patches/@crxjs__vite-plugin@2.0.0-beta.21.patch"
},
"overrides": {
"es-module-lexer": "^1.4.1"
}
}
}

View File

@@ -0,0 +1,105 @@
diff --git a/dist/index.mjs b/dist/index.mjs
index 5c3f6291168987c56b816428080e6f1fe9de7107..abaf6290fe9454ae036a81eacbe7dc3be2fdfbc3 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -499,16 +499,43 @@ ${sourceMap}
}),
mergeMap(async ({ target, code, deps }) => {
await lexer.init;
- const [imports] = lexer.parse(code, fileName);
+ const [imports, exports] = lexer.parse(code, fileName);
const depSet = new Set(deps);
const magic = new MagicString(code);
- for (const i of imports)
+ for (const i of imports) {
if (i.n) {
depSet.add(i.n);
const fileName2 = getFileName({ type: "module", id: i.n });
const fullImport = code.substring(i.s, i.e);
- magic.overwrite(i.s, i.e, fullImport.replace(i.n, `/${fileName2}`));
+ const hmrTimestamp = fullImport.match(/\bt=\d{13}&?\b/);
+ magic.overwrite(
+ i.s,
+ i.e,
+ fullImport.replace(
+ i.n,
+ `/${fileName2}${hmrTimestamp ? `?${hmrTimestamp[0]}` : ""}`
+ )
+ );
+ }
+ }
+ for (const e of exports) {
+ if (e.n === "default") {
+ const regex = /\s+['"](.*)['"]/y;
+ regex.lastIndex = e.e;
+ const fullExport = regex.exec(code)?.[1];
+ if (!fullExport)
+ continue;
+ const start = regex.lastIndex - fullExport.length - 1;
+ const end = regex.lastIndex - 1;
+ if (fullExport.startsWith("/node_modules")) {
+ magic.overwrite(
+ start,
+ end,
+ `http://localhost:5173${fullExport}`
+ );
+ }
}
+ }
return { target, source: magic.toString(), deps: [...depSet] };
})
);
@@ -1229,10 +1256,14 @@ const pluginHMR = () => {
handleHotUpdate({ modules, server }) {
const { root } = server.config;
const relFiles = /* @__PURE__ */ new Set();
- for (const m of modules)
+ function getRelFile(file) {
+ return file.startsWith(root) ? file.slice(server.config.root.length) : file;
+ }
+ for (const m of modules) {
if (m.id?.startsWith(root)) {
relFiles.add(m.id.slice(server.config.root.length));
}
+ }
if (inputManifestFiles.background.length) {
const background = prefix$1("/", inputManifestFiles.background[0]);
if (relFiles.has(background) || modules.some(isImporter(join(server.config.root, background)))) {
@@ -1244,7 +1275,14 @@ const pluginHMR = () => {
for (const [key, script] of contentScripts)
if (key === script.id) {
if (relFiles.has(script.id) || modules.some(isImporter(join(server.config.root, script.id)))) {
- relFiles.forEach((relFile) => update(relFile));
+ modules.filter((mod) => mod.id?.startsWith(root)).forEach((mod) => {
+ update(getRelFile(mod.id));
+ if (mod.file?.endsWith(".scss")) {
+ mod.importers.forEach((imp) => {
+ update(getRelFile(imp.id));
+ });
+ }
+ });
}
}
}
@@ -1882,7 +1920,7 @@ const pluginWebAccessibleResources = () => {
if (contentScripts.size > 0) {
const viteManifest = parseJsonAsset(
bundle,
- "manifest.json"
+ ".vite/manifest.json"
);
const viteFiles = /* @__PURE__ */ new Map();
for (const [, file] of Object.entries(viteManifest))
diff --git a/package.json b/package.json
index e0c47ae66ff399ad3a78abf38d8d93d1f038c55d..f84eb09ffbb5c41094935dd06e04ffe831e2d05a 100644
--- a/package.json
+++ b/package.json
@@ -70,7 +70,7 @@
"connect-injector": "^0.4.4",
"convert-source-map": "^1.7.0",
"debug": "^4.3.3",
- "es-module-lexer": "^0.10.0",
+ "es-module-lexer": "^1.4.1",
"fast-glob": "^3.2.11",
"fs-extra": "^10.0.1",
"jsesc": "^3.0.2",

13316
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,90 +0,0 @@
<!DOCTYPE html>
<html>
<!-- This file is serving as the page for the browser action popup -->
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/popup.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<div class="card" id="card-header" class='header_container'>
<div id="buttons" class="header_buttons">
<button id="clear" class="material_button header_button clear_button">Clear All</button>
<button id="RIS" class="material_button header_button ris_button">Registrar Info </button>
<button id="calendar" class="material_button header_button schedule_button">My Schedule</button>
</div>
</div>
<div>
<ul id="courseList" class='course_list'></ul>
<h2 id="empty" class='empty_message'>
<div id="main">Doesn't Look Like Anything To Me.</div>
<span>(No Courses Saved)</span>
</h2>
<div class="settings_divider"></div>
<input type="file" id="import_input" accept=".json" class="hide" />
<div>
<div id="meta-data" class="meta-container">
<p class="meta"> <span class="meta-metric" id="meta-metric">17</span> hr</p>
</div>
<div class="settings">
<button title='Search' class="settings_button search_button" id='search'>
<i class="material-icons settings_icon">search</i>
</button>
<div id="search-popup" class="hide">
<div class="flex-container">
<div id='semcon' class="select-style item">
<label>
<select id="semesters"></select>
</label>
</div>
<div id='depcon' class="select-style item">
<label>
<select id="department"></select>
</label>
</div>
<div id='levcon' class="select-style item">
<label>
<select id="level">
<option value="L">Lower</option>
<option value="U">Upper</option>
<option value="G">Grad</option>
</select>
</label>
</div>
<div>
<input class = "input-box" placeholder="Course # (optional)" type="text" id="courseCode"></input>
</div>
</div>
<button id="search-class" class="material_button search-button">Search</button>
</div>
<button title='Import/Export' class="settings_button import_button" id='impexp'>
<i class="material-icons settings_icon">import_export</i>
</button>
<div id="import-export-popup" class="hide">
<div class="flex-container">
<button id="import-class" class="simple-menu-option">
<i class="material-icons">file_upload</i>Import Classes
</button>
<button id="export-class" class="simple-menu-option">
<i class="material-icons">file_download</i>Export Classes
</button>
</div>
</div>
<button title='Options' class="settings_button options_button" id='options_button'>
<i class="material-icons settings_icon">settings</i>
</button>
</div>
</div>
</div>
<script src="js/lib/jquery-3.3.1.min.js"></script>
<script src="js/lib/moment.min.js"></script>
<script src="js/Template.js"></script>
<script src="js/config.js"></script>
<script src="js/util.js"></script>
<script src="js/popup.js"></script>
</body>
</html>

10
postcss.config.cjs Normal file
View File

@@ -0,0 +1,10 @@
/* eslint-disable global-require */
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
cssnano: process.env.NODE_ENV !== 'development' ? {} : false,
// '@unocss/postcss': {},
},
};
module.exports = config;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

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

8
src/assets/icons/cal.svg Normal file
View File

@@ -0,0 +1,8 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_3175_7842" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="25">
<rect y="0.5" width="24" height="24" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_3175_7842)">
<path d="M12 14.5C11.7167 14.5 11.4792 14.4042 11.2875 14.2125C11.0958 14.0208 11 13.7833 11 13.5C11 13.2167 11.0958 12.9792 11.2875 12.7875C11.4792 12.5958 11.7167 12.5 12 12.5C12.2833 12.5 12.5208 12.5958 12.7125 12.7875C12.9042 12.9792 13 13.2167 13 13.5C13 13.7833 12.9042 14.0208 12.7125 14.2125C12.5208 14.4042 12.2833 14.5 12 14.5ZM8 14.5C7.71667 14.5 7.47917 14.4042 7.2875 14.2125C7.09583 14.0208 7 13.7833 7 13.5C7 13.2167 7.09583 12.9792 7.2875 12.7875C7.47917 12.5958 7.71667 12.5 8 12.5C8.28333 12.5 8.52083 12.5958 8.7125 12.7875C8.90417 12.9792 9 13.2167 9 13.5C9 13.7833 8.90417 14.0208 8.7125 14.2125C8.52083 14.4042 8.28333 14.5 8 14.5ZM16 14.5C15.7167 14.5 15.4792 14.4042 15.2875 14.2125C15.0958 14.0208 15 13.7833 15 13.5C15 13.2167 15.0958 12.9792 15.2875 12.7875C15.4792 12.5958 15.7167 12.5 16 12.5C16.2833 12.5 16.5208 12.5958 16.7125 12.7875C16.9042 12.9792 17 13.2167 17 13.5C17 13.7833 16.9042 14.0208 16.7125 14.2125C16.5208 14.4042 16.2833 14.5 16 14.5ZM12 18.5C11.7167 18.5 11.4792 18.4042 11.2875 18.2125C11.0958 18.0208 11 17.7833 11 17.5C11 17.2167 11.0958 16.9792 11.2875 16.7875C11.4792 16.5958 11.7167 16.5 12 16.5C12.2833 16.5 12.5208 16.5958 12.7125 16.7875C12.9042 16.9792 13 17.2167 13 17.5C13 17.7833 12.9042 18.0208 12.7125 18.2125C12.5208 18.4042 12.2833 18.5 12 18.5ZM8 18.5C7.71667 18.5 7.47917 18.4042 7.2875 18.2125C7.09583 18.0208 7 17.7833 7 17.5C7 17.2167 7.09583 16.9792 7.2875 16.7875C7.47917 16.5958 7.71667 16.5 8 16.5C8.28333 16.5 8.52083 16.5958 8.7125 16.7875C8.90417 16.9792 9 17.2167 9 17.5C9 17.7833 8.90417 18.0208 8.7125 18.2125C8.52083 18.4042 8.28333 18.5 8 18.5ZM16 18.5C15.7167 18.5 15.4792 18.4042 15.2875 18.2125C15.0958 18.0208 15 17.7833 15 17.5C15 17.2167 15.0958 16.9792 15.2875 16.7875C15.4792 16.5958 15.7167 16.5 16 16.5C16.2833 16.5 16.5208 16.5958 16.7125 16.7875C16.9042 16.9792 17 17.2167 17 17.5C17 17.7833 16.9042 18.0208 16.7125 18.2125C16.5208 18.4042 16.2833 18.5 16 18.5ZM5 22.5C4.45 22.5 3.97917 22.3042 3.5875 21.9125C3.19583 21.5208 3 21.05 3 20.5V6.5C3 5.95 3.19583 5.47917 3.5875 5.0875C3.97917 4.69583 4.45 4.5 5 4.5H6V2.5H8V4.5H16V2.5H18V4.5H19C19.55 4.5 20.0208 4.69583 20.4125 5.0875C20.8042 5.47917 21 5.95 21 6.5V20.5C21 21.05 20.8042 21.5208 20.4125 21.9125C20.0208 22.3042 19.55 22.5 19 22.5H5ZM5 20.5H19V10.5H5V20.5Z" fill="#333F48"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

8
src/assets/icons/png.svg Normal file
View File

@@ -0,0 +1,8 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_3211_5369" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="25">
<rect y="0.5" width="24" height="24" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_3211_5369)">
<path d="M5 21.5C4.45 21.5 3.97917 21.3042 3.5875 20.9125C3.19583 20.5208 3 20.05 3 19.5V5.5C3 4.95 3.19583 4.47917 3.5875 4.0875C3.97917 3.69583 4.45 3.5 5 3.5H19C19.55 3.5 20.0208 3.69583 20.4125 4.0875C20.8042 4.47917 21 4.95 21 5.5V19.5C21 20.05 20.8042 20.5208 20.4125 20.9125C20.0208 21.3042 19.55 21.5 19 21.5H5ZM6 17.5H18L14.25 12.5L11.25 16.5L9 13.5L6 17.5Z" fill="#333F48"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 702 B

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

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

148
src/debug/index.tsx Normal file
View File

@@ -0,0 +1,148 @@
import { DevStore } from '@shared/storage/DevStore';
import React, { useEffect } from 'react';
import { createRoot } from 'react-dom/client';
const manifest = chrome.runtime.getManifest();
interface JSONEditorProps {
data: any;
onChange: (updates: any) => void;
}
function JSONEditor(props: JSONEditorProps) {
const { data, onChange } = props;
const [isEditing, setIsEditing] = React.useState(false);
const [json, setJson] = React.useState(JSON.stringify(data, null, 2));
useEffect(() => {
setJson(JSON.stringify(data, null, 2));
}, [data]);
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setJson(e.target.value);
};
const handleSave = () => {
try {
const updates = JSON.parse(json);
onChange(updates);
setIsEditing(false);
} catch (e) {
console.error(e);
alert('Invalid JSON');
}
};
return (
<div>
{isEditing ? (
<div>
<div style={{ flex: 1, marginBottom: 10, gap: 10, display: 'flex' }}>
<button style={{ color: 'green' }} onClick={handleSave}>
Save
</button>
<button style={{ color: 'red' }} onClick={() => setIsEditing(false)}>
Cancel
</button>
</div>
<textarea style={{ width: '100%', height: '300px' }} value={json} onChange={handleChange} />
</div>
) : (
<div>
<pre onClick={() => setIsEditing(true)}>{json}</pre>
</div>
)}
</div>
);
}
// const PrettyPrintJson = React.memo(({ data }: any) => (
// <div>
// <pre>{JSON.stringify(data, null, 2)}</pre>
// </div>
// ));
function DevDashboard() {
const [localStorage, setLocalStorage] = React.useState<any>({});
const [syncStorage, setSyncStorage] = React.useState<any>({});
const [sessionStorage, setSessionStorage] = React.useState<any>({});
useEffect(() => {
const onVisibilityChange = () => {
DevStore.set('wasDebugTabVisible', document.visibilityState === 'visible');
};
document.addEventListener('visibilitychange', onVisibilityChange);
return () => {
document.removeEventListener('visibilitychange', onVisibilityChange);
};
}, []);
useEffect(() => {
chrome.storage.local.get(null, result => {
setLocalStorage(result);
});
chrome.storage.sync.get(null, result => {
setSyncStorage(result);
});
chrome.storage.session.get(null, result => {
setSessionStorage(result);
});
}, []);
// listen for changes to the chrome storage to update the local storage state displayed in the dashboard
useEffect(() => {
const onChanged = (changes: chrome.storage.StorageChange, areaName: chrome.storage.AreaName) => {
let copy = {};
if (areaName === 'local') {
copy = { ...localStorage };
} else if (areaName === 'sync') {
copy = { ...syncStorage };
} else if (areaName === 'session') {
copy = { ...sessionStorage };
}
Object.keys(changes).forEach(key => {
copy[key] = changes[key].newValue;
});
if (areaName === 'local') {
setLocalStorage(copy);
}
if (areaName === 'sync') {
setSyncStorage(copy);
}
if (areaName === 'session') {
setSessionStorage(copy);
}
};
chrome.storage.onChanged.addListener(onChanged);
return () => {
chrome.storage.onChanged.removeListener(onChanged);
};
}, [localStorage, syncStorage, sessionStorage]);
const handleEditStorage = (areaName: string) => (changes: Record<string, any>) => {
chrome.storage[areaName].set(changes);
};
return (
<div>
<h1>
{manifest.name} {manifest.version} - {process.env.NODE_ENV}
</h1>
<p>This tab is used for hot reloading and debugging. We will update this tab further in the future.</p>
<h2>Local Storage</h2>
<JSONEditor data={localStorage} onChange={handleEditStorage('local')} />
<h2>Sync Storage</h2>
<JSONEditor data={syncStorage} onChange={handleEditStorage('sync')} />
<h2>Session Storage</h2>
<JSONEditor data={sessionStorage} onChange={handleEditStorage('session')} />
<br />
</div>
);
}
createRoot(document.getElementById('root')).render(<DevDashboard />);

View File

@@ -0,0 +1,24 @@
// this is a custom wrapper around react-devtools
// that changes it so that we only send messages to the devtools when the current tab is active;
import { connectToDevTools } from 'react-devtools-core';
// connect to the devtools server
let ws = new WebSocket('ws://localhost:8097');
connectToDevTools({
websocket: ws,
});
// when the tab's visibile state changes, we connect or disconnect from the devtools
const onVisibilityChange = () => {
if (document.visibilityState === 'visible') {
ws = new WebSocket('ws://localhost:8097');
connectToDevTools({
websocket: ws,
});
} else {
ws.close();
}
};
document.addEventListener('visibilitychange', onVisibilityChange);

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

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

54
src/manifest.ts Normal file
View File

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

View File

@@ -0,0 +1,36 @@
import { BACKGROUND_MESSAGES } from '@shared/messages';
import { MessageListener } from 'chrome-extension-toolkit';
import onInstall from './events/onInstall';
import onServiceWorkerAlive from './events/onServiceWorkerAlive';
import onUpdate from './events/onUpdate';
import browserActionHandler from './handler/browserActionHandler';
import tabManagementHandler from './handler/tabManagementHandler';
import userScheduleHandler from './handler/userScheduleHandler';
onServiceWorkerAlive();
/**
* will be triggered on either install or update
* (will also be triggered on a user's sync'd browsers (on other devices)))
*/
chrome.runtime.onInstalled.addListener(details => {
switch (details.reason) {
case 'install':
onInstall();
break;
case 'update':
onUpdate();
break;
default:
break;
}
});
// initialize the message listener that will listen for messages from the content script
const messageListener = new MessageListener<BACKGROUND_MESSAGES>({
...browserActionHandler,
...tabManagementHandler,
...userScheduleHandler,
});
messageListener.listen();

View File

@@ -0,0 +1,8 @@
import { ExtensionStore } from '@shared/storage/ExtensionStore';
/**
* Called when the extension is first installed or synced onto a new machine
*/
export default async function onInstall() {
await ExtensionStore.set('version', chrome.runtime.getManifest().version);
}

View File

@@ -0,0 +1,9 @@
import { openDebugTab } from '../util/openDebugTab';
/**
* Called whenever the background service worker comes alive
* (usually around 30 seconds to 5 minutes after it was last alive)
*/
export default function onServiceWorkerAlive() {
openDebugTab();
}

View File

@@ -0,0 +1,11 @@
import { ExtensionStore } from '@shared/storage/ExtensionStore';
/**
* Called when the extension is updated (or when the extension is reloaded in development mode)
*/
export default async function onUpdate() {
await ExtensionStore.set({
version: chrome.runtime.getManifest().version,
lastUpdate: Date.now(),
});
}

View File

@@ -0,0 +1,20 @@
import BrowserActionMessages from '@shared/messages/BrowserActionMessages';
import { MessageHandler } from 'chrome-extension-toolkit';
const browserActionHandler: MessageHandler<BrowserActionMessages> = {
disableBrowserAction({ sender, sendResponse }) {
// by setting the popup to an empty string, clicking the browser action will not open the popup.html.
// we can then add an onClickListener to it from the content script
chrome.action.setPopup({ tabId: sender.tab?.id, popup: '' }).then(sendResponse);
},
enableBrowserAction({ sender, sendResponse }) {
chrome.action
.setPopup({
tabId: sender.tab?.id,
popup: 'popup.html',
})
.then(sendResponse);
},
};
export default browserActionHandler;

View File

@@ -0,0 +1,24 @@
import HotReloadingMessages from '@shared/messages/HotReloadingMessages';
import { DevStore } from '@shared/storage/DevStore';
import { MessageHandler } from 'chrome-extension-toolkit';
const hotReloadingHandler: MessageHandler<HotReloadingMessages> = {
async reloadExtension({ sendResponse }) {
const [isExtensionReloading, isTabReloading] = await Promise.all([
DevStore.get('isExtensionReloading'),
DevStore.get('isTabReloading'),
]);
if (!isExtensionReloading) return sendResponse();
if (isTabReloading) {
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
const tabToReload = tabs[0];
await DevStore.set('reloadTabId', tabToReload?.id);
}
chrome.runtime.reload();
},
};
export default hotReloadingHandler;

View File

@@ -0,0 +1,20 @@
import TabManagementMessages from '@shared/messages/TabManagementMessages';
import { MessageHandler } from 'chrome-extension-toolkit';
import openNewTab from '../util/openNewTab';
const tabManagementHandler: MessageHandler<TabManagementMessages> = {
getTabId({ sendResponse, sender }) {
sendResponse(sender.tab?.id ?? -1);
},
openNewTab({ data, sender, sendResponse }) {
const { url } = data;
const nextIndex = sender.tab?.index ? sender.tab.index + 1 : undefined;
openNewTab(url, nextIndex).then(sendResponse);
},
removeTab({ data, sendResponse }) {
const { tabId } = data;
chrome.tabs.remove(tabId).then(sendResponse);
},
};
export default tabManagementHandler;

View File

@@ -0,0 +1,36 @@
import { UserScheduleMessages } from '@shared/messages/UserScheduleMessages';
import { Course } from '@shared/types/Course';
import { MessageHandler } from 'chrome-extension-toolkit';
import addCourse from '../lib/addCourse';
import clearCourses from '../lib/clearCourses';
import createSchedule from '../lib/createSchedule';
import deleteSchedule from '../lib/deleteSchedule';
import removeCourse from '../lib/removeCourse';
import renameSchedule from '../lib/renameSchedule';
import switchSchedule from '../lib/switchSchedule';
const userScheduleHandler: MessageHandler<UserScheduleMessages> = {
addCourse({ data, sendResponse }) {
addCourse(data.scheduleName, new Course(data.course)).then(sendResponse);
},
removeCourse({ data, sendResponse }) {
removeCourse(data.scheduleName, new Course(data.course)).then(sendResponse);
},
clearCourses({ data, sendResponse }) {
clearCourses(data.scheduleName).then(sendResponse);
},
switchSchedule({ data, sendResponse }) {
switchSchedule(data.scheduleName).then(sendResponse);
},
createSchedule({ data, sendResponse }) {
createSchedule(data.scheduleName).then(sendResponse);
},
deleteSchedule({ data, sendResponse }) {
deleteSchedule(data.scheduleName).then(sendResponse);
},
renameSchedule({ data, sendResponse }) {
renameSchedule(data.scheduleName, data.newName).then(sendResponse);
},
};
export default userScheduleHandler;

View File

@@ -0,0 +1,17 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
import { Course } from '@shared/types/Course';
/**
*
*/
export default async function addCourse(scheduleName: string, course: Course): Promise<void> {
const schedules = await UserScheduleStore.get('schedules');
const activeSchedule = schedules.find(s => s.name === scheduleName);
if (!activeSchedule) {
throw new Error('Schedule not found');
}
activeSchedule.courses.push(course);
await UserScheduleStore.set('schedules', schedules);
}

View File

@@ -0,0 +1,11 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
export default async function clearCourses(scheduleName: string): Promise<void> {
const schedules = await UserScheduleStore.get('schedules');
const schedule = schedules.find(schedule => schedule.name === scheduleName);
if (!schedule) {
throw new Error(`Schedule ${scheduleName} does not exist`);
}
schedule.courses = [];
await UserScheduleStore.set('schedules', schedules);
}

View File

@@ -0,0 +1,21 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
/**
* Creates a new schedule with the given name
* @param scheduleName the name of the schedule to create
* @returns undefined if successful, otherwise an error message
*/
export default async function createSchedule(scheduleName: string): Promise<string | undefined> {
const schedules = await UserScheduleStore.get('schedules');
if (schedules.find(schedule => schedule.name === scheduleName)) {
return `Schedule ${scheduleName} already exists`;
}
schedules.push({
name: scheduleName,
courses: [],
});
await UserScheduleStore.set('schedules', schedules);
return undefined;
}

View File

@@ -0,0 +1,20 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
export default async function deleteSchedule(scheduleName: string): Promise<string | undefined> {
const [schedules, activeIndex] = await Promise.all([
UserScheduleStore.get('schedules'),
UserScheduleStore.get('activeIndex'),
]);
const scheduleIndex = schedules.findIndex(schedule => schedule.name === scheduleName);
if (scheduleIndex === -1) {
return `Schedule ${scheduleName} does not exist`;
}
if (scheduleIndex === activeIndex) {
return 'Cannot delete active schedule';
}
schedules.splice(scheduleIndex, 1);
await UserScheduleStore.set('schedules', schedules);
return undefined;
}

View File

@@ -0,0 +1,17 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
import { Course } from '@shared/types/Course';
/**
*
*/
export default async function removeCourse(scheduleName: string, course: Course): Promise<void> {
const schedules = await UserScheduleStore.get('schedules');
const activeSchedule = schedules.find(s => s.name === scheduleName);
if (!activeSchedule) {
throw new Error('Schedule not found');
}
activeSchedule.courses = activeSchedule.courses.filter(c => c.uniqueId !== course.uniqueId);
await UserScheduleStore.set('schedules', schedules);
}

Some files were not shown because too many files have changed in this diff Show More