├── .prettierignore ├── .gitignore ├── public ├── logo.png └── logo.svg ├── src ├── schedule-visualizer │ ├── icons │ │ ├── OverlapIcon.css │ │ ├── style.css │ │ ├── ExpandIcon.tsx │ │ ├── CollapseIcon.tsx │ │ ├── FilterIcon.tsx │ │ ├── UWFlowIcon.tsx │ │ ├── CartIcon.tsx │ │ ├── MaximizeIcon.tsx │ │ ├── MinimizeIcon.tsx │ │ ├── GitHubIcon.tsx │ │ └── OverlapIcon.tsx │ ├── components │ │ ├── App.css │ │ ├── IconLink.css │ │ ├── BorderlessButton.css │ │ ├── Main.css │ │ ├── App.tsx │ │ ├── ClassSubCell.css │ │ ├── ScheduleActions.css │ │ ├── ClassToggleList.css │ │ ├── ClassToggle.css │ │ ├── Title.tsx │ │ ├── Title.css │ │ ├── IconLink.tsx │ │ ├── BorderlessButton.tsx │ │ ├── AsyncContent.tsx │ │ ├── UWFlowLink.tsx │ │ ├── ClassToggleList.tsx │ │ ├── ClassToggle.tsx │ │ ├── ScheduleGrid.css │ │ ├── ScheduleGrid.tsx │ │ ├── ScheduleActions.tsx │ │ ├── ColumnCell.tsx │ │ ├── Main.tsx │ │ ├── ColumnCell.css │ │ └── ClassSubCell.tsx │ ├── data │ │ ├── SupplementaryInfo.ts │ │ ├── getQuestParser.ts │ │ ├── ScheduleSlot.ts │ │ ├── QuestParser.ts │ │ ├── ClassTime.ts │ │ ├── ClassDate.ts │ │ ├── ClassesParser.ts │ │ ├── ScheduleParser.ts │ │ ├── Class.ts │ │ ├── Session.ts │ │ ├── ClassSlot.ts │ │ ├── CartParser.ts │ │ ├── SupplementaryParser.ts │ │ └── Schedule.tsx │ ├── helpers │ │ └── UseConfigBoolean.ts │ └── initScheduleVisualizer.tsx ├── inject.ts ├── helpers │ └── getInstructorUWFlow.ts ├── main.tsx ├── index.css └── flow-links │ └── initFlowLinks.ts ├── tsconfig.node.json ├── vite.config.ts ├── tsconfig.json ├── .eslintrc.cjs ├── manifest.json ├── README.md ├── package.json └── LICENSE /.prettierignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | assets -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrandonXLF/quest-plus/main/public/logo.png -------------------------------------------------------------------------------- /src/schedule-visualizer/icons/OverlapIcon.css: -------------------------------------------------------------------------------- 1 | .alert { 2 | color: var(--alert); 3 | } 4 | -------------------------------------------------------------------------------- /src/schedule-visualizer/components/App.css: -------------------------------------------------------------------------------- 1 | #container { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100%; 5 | } 6 | -------------------------------------------------------------------------------- /src/schedule-visualizer/components/IconLink.css: -------------------------------------------------------------------------------- 1 | .icon-link { 2 | white-space: nowrap; 3 | } 4 | 5 | .icon-link svg { 6 | padding-right: 0.2em; 7 | } 8 | -------------------------------------------------------------------------------- /src/schedule-visualizer/components/BorderlessButton.css: -------------------------------------------------------------------------------- 1 | .borderless-btn { 2 | border: none; 3 | background: none; 4 | padding: 0; 5 | font-size: inherit; 6 | cursor: pointer; 7 | } 8 | -------------------------------------------------------------------------------- /src/schedule-visualizer/components/Main.css: -------------------------------------------------------------------------------- 1 | .schedule-planner { 2 | display: flex; 3 | flex-direction: column; 4 | flex: 1; 5 | height: 0; 6 | padding-bottom: 1em; 7 | gap: 0.5em; 8 | } 9 | -------------------------------------------------------------------------------- /src/schedule-visualizer/components/App.tsx: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import Main from './Main'; 3 | 4 | export default function App() { 5 | return ( 6 |
7 |
8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/schedule-visualizer/icons/style.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | height: 1em; 3 | vertical-align: text-bottom; 4 | stroke-linecap: round; 5 | stroke-linejoin: round; 6 | stroke: currentColor; 7 | fill: none; 8 | stroke-width: 2.5; 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/schedule-visualizer/components/ClassSubCell.css: -------------------------------------------------------------------------------- 1 | .expanded-content:not(:last-child) { 2 | padding-bottom: 8px; 3 | } 4 | 5 | .slot-top span[tabIndex] { 6 | cursor: pointer; 7 | user-select: none; 8 | } 9 | 10 | .course-code { 11 | font-weight: bold; 12 | } 13 | -------------------------------------------------------------------------------- /src/schedule-visualizer/components/ScheduleActions.css: -------------------------------------------------------------------------------- 1 | .schedule-planner-header { 2 | display: flex; 3 | flex-wrap: wrap; 4 | justify-content: left; 5 | align-items: center; 6 | gap: 0.5em; 7 | } 8 | 9 | .header-external-links { 10 | margin-left: 1em; 11 | } 12 | -------------------------------------------------------------------------------- /src/schedule-visualizer/components/ClassToggleList.css: -------------------------------------------------------------------------------- 1 | .planner-toggles-container { 2 | display: flex; 3 | flex: 1; 4 | } 5 | 6 | .planner-toggles { 7 | display: flex; 8 | flex-wrap: wrap; 9 | row-gap: 0.5em; 10 | column-gap: 1em; 11 | width: 0; 12 | flex: 1; 13 | } 14 | -------------------------------------------------------------------------------- /src/schedule-visualizer/components/ClassToggle.css: -------------------------------------------------------------------------------- 1 | .schedule-class-toggle { 2 | display: flex; 3 | gap: 0.25em; 4 | align-items: center; 5 | } 6 | 7 | .schedule-class-toggle input[type='checkbox'] { 8 | margin: 0; 9 | } 10 | 11 | .schedule-class-toggle .icon { 12 | height: 0.85em; 13 | } 14 | -------------------------------------------------------------------------------- /src/schedule-visualizer/components/Title.tsx: -------------------------------------------------------------------------------- 1 | import './Title.css'; 2 | 3 | export default function Title() { 4 | const src = chrome.runtime.getURL('assets/logo.png'); 5 | 6 | return ( 7 |
8 | 9 |

Quest+ Schedule Planner

10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/schedule-visualizer/icons/ExpandIcon.tsx: -------------------------------------------------------------------------------- 1 | import './style.css'; 2 | 3 | export default function ExpandIcon() { 4 | return ( 5 | 10 | Expand 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/schedule-visualizer/icons/CollapseIcon.tsx: -------------------------------------------------------------------------------- 1 | import './style.css'; 2 | 3 | export default function CollapseIcon() { 4 | return ( 5 | 10 | Collapse 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/inject.ts: -------------------------------------------------------------------------------- 1 | { 2 | const old_showhide = ( 3 | window as unknown as { 4 | showhide: () => void; 5 | } 6 | ).showhide; 7 | 8 | ( 9 | window as unknown as { 10 | showhide: () => void; 11 | } 12 | ).showhide = function (...args) { 13 | old_showhide(...args); 14 | window.dispatchEvent(new Event('quest-plus-page-nav')); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/schedule-visualizer/data/SupplementaryInfo.ts: -------------------------------------------------------------------------------- 1 | import ClassDate from './ClassDate'; 2 | 3 | export class SupplementaryInfo { 4 | constructor( 5 | public desc: string, 6 | public type: string, 7 | public campus: string, 8 | public enrolled: number, 9 | public capacity: number, 10 | public slotDates: { 11 | start: ClassDate; 12 | end: ClassDate; 13 | }[] = [] 14 | ) {} 15 | } 16 | -------------------------------------------------------------------------------- /src/schedule-visualizer/components/Title.css: -------------------------------------------------------------------------------- 1 | .top-area { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | gap: 0.5em; 6 | } 7 | 8 | .top-area img { 9 | height: 2em; 10 | } 11 | 12 | .top-area h2 { 13 | display: inline-block; 14 | font-size: 1.5rem; 15 | margin: 0; 16 | } 17 | 18 | @media (min-width: 600px) { 19 | .top-area { 20 | justify-content: left; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/schedule-visualizer/data/getQuestParser.ts: -------------------------------------------------------------------------------- 1 | import CartParser from './CartParser'; 2 | import QuestParser from './QuestParser'; 3 | import ScheduleParser from './ScheduleParser'; 4 | 5 | export default function getQuestParser(): QuestParser | undefined { 6 | if (document.getElementById('SSR_SSENRL_LIST')) return new ScheduleParser(); 7 | if (document.getElementById('SSR_SSENRL_CART')) return new CartParser(); 8 | } 9 | -------------------------------------------------------------------------------- /src/schedule-visualizer/components/IconLink.tsx: -------------------------------------------------------------------------------- 1 | import './IconLink.css'; 2 | 3 | export default function IconLink( 4 | attrs: React.AnchorHTMLAttributes & { 5 | icon: React.ReactNode; 6 | } 7 | ) { 8 | const linkAttrs = { ...attrs, icon: undefined }; 9 | 10 | return ( 11 | 12 | {attrs.icon} 13 | {attrs.children} 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/schedule-visualizer/icons/FilterIcon.tsx: -------------------------------------------------------------------------------- 1 | import './style.css'; 2 | 3 | export default function FilterIcon() { 4 | return ( 5 | 10 | Filter 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/schedule-visualizer/icons/UWFlowIcon.tsx: -------------------------------------------------------------------------------- 1 | import './style.css'; 2 | 3 | export default function UWFlowIcon() { 4 | return ( 5 | 10 | UW Flow 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/schedule-visualizer/components/BorderlessButton.tsx: -------------------------------------------------------------------------------- 1 | import './BorderlessButton.css'; 2 | 3 | export default function BorderlessButton( 4 | props: Readonly> 5 | ) { 6 | return ( 7 |