Compare commits

...

26 Commits

Author SHA1 Message Date
DhruvArora-03
514fbc6081 fix: comment/fix build errors 2024-02-24 21:05:22 -06:00
DhruvArora-03
0dcf1f2c04 Merge branch 'dhruv/fix-popup-main-icons' into dhruv/change-schedule-calendar-page 2024-02-24 20:59:27 -06:00
DhruvArora-03
dee20c6523 feat: fix icons on PopupMain 2024-02-24 20:52:28 -06:00
doprz
e4a368fbb6 fix: vitest path alias bug 2024-02-22 23:25:08 -06:00
doprz
29247d5dfa chore: lint-format-docs-tests-bugfixes (#105)
* docs: add jsdoc

* feat: change enums to as const objects

* chore(test): add themeColors.test.ts

* fix: fix tests and bugs with strings.ts util

* fix: path alias imports and tsconfig file bug

* fix: remove --max-warnings 0
2024-02-22 22:42:58 -06:00
Samuel Gunter
bb2efc0b46 feat: updated divider component (#99)
* feat: updated divider component

* refactor: inlined Divider's classes, simplified stories

* fix: style to unocss in story

* style: renamed variant to orientation for buttons

* docs: updated comments in Divider after prop name change
2024-02-22 21:24:41 -06:00
doprz
d5a04c745f feat: Best Practices (#102)
* feat: best practices

* feat: add tests workflow

* feat: add best-practices workflow

* fix: wrong indentation in workflow
2024-02-21 15:54:21 -06:00
doprz
f01cb070b3 feat: Conventional Commits (#103)
* feat: add commitlint and husky hook

* chore: fix indentation
2024-02-21 13:36:25 -06:00
b2eac59ae7 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-20 22:06:52 -06:00
e99664fdae Old icon removed in .tsx 2024-02-20 22:06:48 -06:00
49c0d63f0b Icon added successfully 2024-02-20 22:06:26 -06:00
knownotunknown
8c82282467 Update CalendarGrid.tsx 2024-02-20 21:51:42 -06:00
knownotunknown
0a3c31ec09 Fixed some bugs 2024-02-20 21:39:11 -06:00
knownotunknown
ce7917b474 Merge branch 'hackathon' of https://github.com/UT-Developers/UT-Registration-Plus into hackathon 2024-02-20 21:20:40 -06:00
knownotunknown
a5fe6ec06b Can open tabs, updated injected popup heading. basically done 2024-02-20 21:20:37 -06:00
Samuel Gunter
5a2ee0d19a fix: change Chromatic action to build current branch, not base branch (#100) 2024-02-20 17:20:28 -06:00
knownotunknown
9ec0d106f5 Chrome extension works 2024-02-19 23:03:53 -06:00
knownotunknown
70a3f14e0a Squashed commit of the following:
commit c46e4a51c9
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Mon Feb 19 21:37:46 2024 -0600

    change from reducer pattern to state variables, remove chartData from state

commit 36bcdd2522
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Mon Feb 19 21:15:41 2024 -0600

    change grade distribution colors to match updated figma

commit 11a50df88d
Merge: c16b301 b4c96a9
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Mon Feb 19 17:57:13 2024 -0600

    Merge branch 'hackathon' into abhinavchadaga/course-catalog-popup

commit c16b301ff0
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Mon Feb 19 17:47:21 2024 -0600

    Kinda complete the handlers

commit 1ac1d9095a
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sun Feb 18 17:36:59 2024 -0600

    Bunch of renaming

commit 925829ad41
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sun Feb 18 17:24:53 2024 -0600

    Fix syllabi url

    Remove unused variable and unnecessary args to url

commit f2e5d51eb3
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sun Feb 18 17:24:22 2024 -0600

    Add TODO

    replace current grade colors with a tailwind palette

commit 747ee44440
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sun Feb 18 01:26:51 2024 -0600

    Minor tweaks

    change style in header

commit ddfe952a32
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sun Feb 18 01:26:38 2024 -0600

    Add Grade Distribution Stuff

commit c27bf3c390
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sun Feb 18 01:26:13 2024 -0600

    Modify story to use proper course info

commit 7afdbac1b8
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 16:37:01 2024 -0600

    description stuff done

commit 1a89432276
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 15:26:32 2024 -0600

    Rename CoursePopup

    Old one to "Old", remove "2" from new one

commit 4c2b31e61a
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 15:23:01 2024 -0600

    add todo for calendar button

commit 11b7a51ded
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 15:22:18 2024 -0600

    add course button onclick handlers

commit f2dfcec838
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 14:52:38 2024 -0600

    some unocss updates

commit f9f375514b
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 13:00:46 2024 -0600

    Add rmp callback

commit 122fc6dbdd
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 13:00:16 2024 -0600

    Change test course to 314

commit 19b124b3bd
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 12:19:21 2024 -0600

    complete CourseHeaderAndActions Component

    added course buttons, using proper subcomponents now.

commit 2eea01fc74
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 11:22:12 2024 -0600

    use chip component in header

commit 9cb13c8fd1
Merge: a62b718 9392085
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 11:21:12 2024 -0600

    Merge branch 'hackathon' into abhinavchadaga/course-catalog-popup

commit a62b718c43
Merge: 43d2675 7b7b858
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 10:57:24 2024 -0600

    Merge branch 'hackathon' into abhinavchadaga/course-catalog-popup

commit 43d2675be5
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Sat Feb 17 10:54:49 2024 -0600

    some work on course popup

    update the stories and create the header component

commit 31bcef3099
Merge: 874f8d5 fa1d737
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Wed Feb 14 14:33:16 2024 -0600

    Merge branch 'main' into abhinavchadaga/course-catalog-popup

    pulling from main

commit 874f8d56cb
Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu>
Date:   Wed Feb 14 14:30:24 2024 -0600

    some work
2024-02-19 22:39:26 -06:00
knownotunknown
d69707b8e8 Squashed commit of the following:
commit f6896e37e2
Author: knownotunknown <78577376+knownotunknown@users.noreply.github.com>
Date:   Mon Feb 19 20:46:57 2024 -0600

    Calendar Page mostly styled

commit a28422e6b0
Merge: 297601e 41e6d77
Author: knownotunknown <78577376+knownotunknown@users.noreply.github.com>
Date:   Mon Feb 19 18:46:59 2024 -0600

    Merge branch 'hackathon' into Som

commit 297601e715
Author: knownotunknown <78577376+knownotunknown@users.noreply.github.com>
Date:   Sun Feb 18 16:28:29 2024 -0600

    Grid works cleanly with up to two course conflicts. Prob needs refactoring

commit 313a9648c9
Merge: b0a95a6 0acd0b7
Author: knownotunknown <78577376+knownotunknown@users.noreply.github.com>
Date:   Sun Feb 18 15:45:57 2024 -0600

    Merge branch 'hackathon' into Som

commit b0a95a6153
Author: knownotunknown <78577376+knownotunknown@users.noreply.github.com>
Date:   Sun Feb 18 14:10:13 2024 -0600

    Made CourseCells and CalendarGridCells more responsive. CourseCells now rendering onto grid. Still, need to make course cells more responsive, and add edge cases

commit a1a0f00514
Merge: 7479004 ac71b83
Author: knownotunknown <78577376+knownotunknown@users.noreply.github.com>
Date:   Sat Feb 17 17:07:06 2024 -0600

    Merge branch 'hackathon' into Som

commit 7479004a65
Author: knownotunknown <78577376+knownotunknown@users.noreply.github.com>
Date:   Sat Feb 17 16:59:34 2024 -0600

    Need to add sorting
2024-02-19 20:48:00 -06:00
knownotunknown
41e6d77d02 Fixed import error 2024-02-19 18:34:42 -06:00
knownotunknown
11fece0595 Merged in finished CalendarGrid 2024-02-19 18:27:06 -06:00
knownotunknown
c676be4765 Fixed build errors and restructured Calendar page 2024-02-19 18:26:09 -06:00
knownotunknown
b4c96a9a10 Fixed build errors and merging in Casey's branch (driodiwb) 2024-02-19 12:03:23 -06:00
knownotunknown
adba5e1bbc Merge remote-tracking branch 'origin/PopupMain' into hackathon 2024-02-19 11:52:48 -06:00
DhruvArora-03
58dc706ece Merge branch 'feat-conflict-row' into hackathon 2024-02-19 11:29:56 -06:00
vivek12311
ff4ee494b6 Using React-icons. Probably need to take out later 2024-02-17 16:03:28 -06:00
165 changed files with 4964 additions and 3071 deletions

107
.eslintrc
View File

@@ -4,12 +4,9 @@
"browser": true,
"es6": true,
"node": true,
"webextensions": true
"webextensions": true,
},
"ignorePatterns": [
"*.html",
"tsconfig.json"
],
"ignorePatterns": ["*.html", "tsconfig.json"],
"extends": [
"eslint:recommended",
"plugin:react/recommended",
@@ -21,18 +18,14 @@
"@unocss",
"prettier",
],
"plugins": [
"import",
"jsdoc",
"react-prefer-function-component"
],
"plugins": ["import", "jsdoc", "react-prefer-function-component", "@typescript-eslint", "simple-import-sort"],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly",
"debugger": true,
"browser": true,
"context": true,
"JSX": true
"JSX": true,
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
@@ -42,36 +35,33 @@
"ecmaFeatures": {
"jsx": true,
"modules": true,
"experimentalObjectRestSpread": true
}
"experimentalObjectRestSpread": true,
},
},
"settings": {
"react": {
"version": "detect"
"version": "detect",
},
"jsdoc": {
"mode": "typescript"
"mode": "typescript",
},
"import/parsers": {
"@typescript-eslint/parser": [
".ts",
".tsx"
]
"@typescript-eslint/parser": [".ts", ".tsx"],
},
"import/resolver": {
"typescript": {
"alwaysTryTypes": true,
"project": "./tsconfig.json"
}
}
"project": "./tsconfig.json",
},
},
},
"rules": {
"prefer-const": [
"off",
{
"destructuring": "any",
"ignoreReadBeforeAssign": false
}
"ignoreReadBeforeAssign": false,
},
],
"no-plusplus": "off",
"no-inner-declarations": "off",
@@ -83,20 +73,16 @@
"no-undef": "off",
"no-return-await": "off",
"@typescript-eslint/return-await": "off",
"@typescript-eslint/no-shadow": [
"off"
],
"@typescript-eslint/no-use-before-define": [
"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
}
"props": false,
},
],
"no-console": "off",
"consistent-return": "off",
@@ -110,8 +96,8 @@
"error",
{
"before": true,
"after": true
}
"after": true,
},
],
"no-continue": "off",
"space-before-blocks": [
@@ -119,24 +105,22 @@
{
"functions": "always",
"keywords": "always",
"classes": "always"
}
"classes": "always",
},
],
"react/jsx-filename-extension": [
1,
{
"extensions": [
".tsx"
]
}
"extensions": [".tsx"],
},
],
"react/no-deprecated": "warn",
"react/prop-types": "off",
"react-prefer-function-component/react-prefer-function-component": [
"warn",
{
"allowComponentDidCatch": false
}
"allowComponentDidCatch": false,
},
],
"react/function-component-definition": "off",
"react/button-has-type": "off",
@@ -154,7 +138,7 @@
"ArrowFunctionExpression": true,
"ClassDeclaration": true,
"ClassExpression": true,
"FunctionExpression": true
"FunctionExpression": true,
},
"contexts": [
"MethodDefinition:not([key.name=\"componentDidMount\"]):not([key.name=\"render\"])",
@@ -169,9 +153,9 @@
"TSInterfaceDeclaration",
"TSMethodSignature",
"TSModuleDeclaration",
"TSTypeAliasDeclaration"
]
}
"TSTypeAliasDeclaration",
],
},
],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "warn",
@@ -186,31 +170,36 @@
{
"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!"
"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!"
"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."
}
]
}
"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"
]
}
"WithStatement",
{
"selector": "TSEnumDeclaration",
"message": "Don't declare enums",
},
],
"@typescript-eslint/consistent-type-exports": "error",
"@typescript-eslint/consistent-type-imports": "error",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
},
}

43
.github/workflows/best-practices.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: Best Practices
on: [push, pull_request]
jobs:
lint:
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: Run ESLint
run: pnpm run lint
format:
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: Run Prettier
run: pnpm run prettier

View File

@@ -1,26 +1,26 @@
name: "Chromatic"
name: 'Chromatic'
on: [push, pull_request_target]
on: [push, pull_request]
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
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: Install dependencies
run: pnpm install
- name: Publish to Chromatic
uses: chromaui/action@latest
with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
exitZeroOnChanges: true
autoAcceptChanges: "main"
- name: Publish to Chromatic
uses: chromaui/action@latest
with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
exitZeroOnChanges: true
autoAcceptChanges: 'main'

24
.github/workflows/tests.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: Tests
on: [push, pull_request]
jobs:
test:
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: Run tests
run: pnpm test

1
.husky/commit-msg Normal file
View File

@@ -0,0 +1 @@
npx --no -- commitlint --edit $1

View File

@@ -34,4 +34,4 @@
}
]
]
}
}

View File

@@ -0,0 +1,30 @@
// .storybook/vite-storybook.config.ts
import react from "file:///C:/Users/somgu/OneDrive/Desktop/UT-Registration-Plus/node_modules/.pnpm/@vitejs+plugin-react-swc@3.6.0_vite@5.1.2/node_modules/@vitejs/plugin-react-swc/index.mjs";
import { resolve } from "path";
import UnoCSS from "file:///C:/Users/somgu/OneDrive/Desktop/UT-Registration-Plus/node_modules/.pnpm/unocss@0.58.5_postcss@8.4.35_vite@5.1.2/node_modules/unocss/dist/vite.mjs";
import Icons from "file:///C:/Users/somgu/OneDrive/Desktop/UT-Registration-Plus/node_modules/.pnpm/unplugin-icons@0.18.5_@svgr+core@8.1.0/node_modules/unplugin-icons/dist/vite.js";
import { defineConfig } from "file:///C:/Users/somgu/OneDrive/Desktop/UT-Registration-Plus/node_modules/.pnpm/vite@5.1.2_@types+node@20.11.17_sass@1.70.0/node_modules/vite/dist/node/index.js";
var __vite_injected_original_dirname = "C:\\Users\\somgu\\OneDrive\\Desktop\\UT-Registration-Plus\\.storybook";
var root = resolve(__vite_injected_original_dirname, "../src");
var pagesDir = resolve(root, "pages");
var assetsDir = resolve(root, "assets");
var publicDir = resolve(__vite_injected_original_dirname, "../public");
console.log(root);
var vite_storybook_config_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")
}
}
});
export {
vite_storybook_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLnN0b3J5Ym9vay92aXRlLXN0b3J5Ym9vay5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJDOlxcXFxVc2Vyc1xcXFxzb21ndVxcXFxPbmVEcml2ZVxcXFxEZXNrdG9wXFxcXFVULVJlZ2lzdHJhdGlvbi1QbHVzXFxcXC5zdG9yeWJvb2tcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkM6XFxcXFVzZXJzXFxcXHNvbWd1XFxcXE9uZURyaXZlXFxcXERlc2t0b3BcXFxcVVQtUmVnaXN0cmF0aW9uLVBsdXNcXFxcLnN0b3J5Ym9va1xcXFx2aXRlLXN0b3J5Ym9vay5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL0M6L1VzZXJzL3NvbWd1L09uZURyaXZlL0Rlc2t0b3AvVVQtUmVnaXN0cmF0aW9uLVBsdXMvLnN0b3J5Ym9vay92aXRlLXN0b3J5Ym9vay5jb25maWcudHNcIjtpbXBvcnQgcmVhY3QgZnJvbSAnQHZpdGVqcy9wbHVnaW4tcmVhY3Qtc3djJztcbmltcG9ydCB7IHJlc29sdmUgfSBmcm9tICdwYXRoJztcbmltcG9ydCBVbm9DU1MgZnJvbSAndW5vY3NzL3ZpdGUnO1xuaW1wb3J0IEljb25zIGZyb20gJ3VucGx1Z2luLWljb25zL3ZpdGUnO1xuaW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSAndml0ZSc7XG5cbmNvbnN0IHJvb3QgPSByZXNvbHZlKF9fZGlybmFtZSwgJy4uL3NyYycpO1xuY29uc3QgcGFnZXNEaXIgPSByZXNvbHZlKHJvb3QsICdwYWdlcycpO1xuY29uc3QgYXNzZXRzRGlyID0gcmVzb2x2ZShyb290LCAnYXNzZXRzJyk7XG5jb25zdCBwdWJsaWNEaXIgPSByZXNvbHZlKF9fZGlybmFtZSwgJy4uL3B1YmxpYycpO1xuXG5jb25zb2xlLmxvZyhyb290KTtcblxuLy8gaHR0cHM6Ly92aXRlanMuZGV2L2NvbmZpZy9cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG4gICAgcGx1Z2luczogW3JlYWN0KCksIFVub0NTUygpLCBJY29ucyh7IGNvbXBpbGVyOiAnanN4JywganN4OiAncmVhY3QnIH0pXSxcbiAgICByZXNvbHZlOiB7XG4gICAgICAgIGFsaWFzOiB7XG4gICAgICAgICAgICBzcmM6IHJvb3QsXG4gICAgICAgICAgICAnQGFzc2V0cyc6IGFzc2V0c0RpcixcbiAgICAgICAgICAgICdAcGFnZXMnOiBwYWdlc0RpcixcbiAgICAgICAgICAgICdAcHVibGljJzogcHVibGljRGlyLFxuICAgICAgICAgICAgJ0BzaGFyZWQnOiByZXNvbHZlKHJvb3QsICdzaGFyZWQnKSxcbiAgICAgICAgICAgICdAYmFja2dyb3VuZCc6IHJlc29sdmUocGFnZXNEaXIsICdiYWNrZ3JvdW5kJyksXG4gICAgICAgICAgICAnQHZpZXdzJzogcmVzb2x2ZShyb290LCAndmlld3MnKSxcbiAgICAgICAgfSxcbiAgICB9LFxufSk7XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQWlaLE9BQU8sV0FBVztBQUNuYSxTQUFTLGVBQWU7QUFDeEIsT0FBTyxZQUFZO0FBQ25CLE9BQU8sV0FBVztBQUNsQixTQUFTLG9CQUFvQjtBQUo3QixJQUFNLG1DQUFtQztBQU16QyxJQUFNLE9BQU8sUUFBUSxrQ0FBVyxRQUFRO0FBQ3hDLElBQU0sV0FBVyxRQUFRLE1BQU0sT0FBTztBQUN0QyxJQUFNLFlBQVksUUFBUSxNQUFNLFFBQVE7QUFDeEMsSUFBTSxZQUFZLFFBQVEsa0NBQVcsV0FBVztBQUVoRCxRQUFRLElBQUksSUFBSTtBQUdoQixJQUFPLGdDQUFRLGFBQWE7QUFBQSxFQUN4QixTQUFTLENBQUMsTUFBTSxHQUFHLE9BQU8sR0FBRyxNQUFNLEVBQUUsVUFBVSxPQUFPLEtBQUssUUFBUSxDQUFDLENBQUM7QUFBQSxFQUNyRSxTQUFTO0FBQUEsSUFDTCxPQUFPO0FBQUEsTUFDSCxLQUFLO0FBQUEsTUFDTCxXQUFXO0FBQUEsTUFDWCxVQUFVO0FBQUEsTUFDVixXQUFXO0FBQUEsTUFDWCxXQUFXLFFBQVEsTUFBTSxRQUFRO0FBQUEsTUFDakMsZUFBZSxRQUFRLFVBQVUsWUFBWTtBQUFBLE1BQzdDLFVBQVUsUUFBUSxNQUFNLE9BQU87QUFBQSxJQUNuQztBQUFBLEVBQ0o7QUFDSixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=

View File

@@ -37,4 +37,7 @@
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"typescript.tsdk": "node_modules/typescript/lib",
"tailwindCSS.includeLanguages": {
"plaintext": "javascript"
}
}

123
commitlint.config.ts Normal file
View File

@@ -0,0 +1,123 @@
import { RuleConfigCondition, RuleConfigSeverity, TargetCaseType } from '@commitlint/types';
export default {
parserPreset: 'conventional-changelog-conventionalcommits',
rules: {
'body-leading-blank': [RuleConfigSeverity.Warning, 'always'] as const,
'body-max-line-length': [RuleConfigSeverity.Error, 'always', 100] as const,
'footer-leading-blank': [RuleConfigSeverity.Warning, 'always'] as const,
'footer-max-line-length': [RuleConfigSeverity.Error, 'always', 100] as const,
'header-max-length': [RuleConfigSeverity.Error, 'always', 100] as const,
'header-trim': [RuleConfigSeverity.Error, 'always'] as const,
'subject-case': [
RuleConfigSeverity.Error,
'never',
['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
] as [RuleConfigSeverity, RuleConfigCondition, TargetCaseType[]],
'subject-empty': [RuleConfigSeverity.Error, 'never'] as const,
'subject-full-stop': [RuleConfigSeverity.Error, 'never', '.'] as const,
'type-case': [RuleConfigSeverity.Error, 'always', 'lower-case'] as const,
'type-empty': [RuleConfigSeverity.Error, 'never'] as const,
'type-enum': [
RuleConfigSeverity.Error,
'always',
['build', 'chore', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test'],
] as [RuleConfigSeverity, RuleConfigCondition, string[]],
},
prompt: {
questions: {
type: {
description: "Select the type of change that you're committing",
enum: {
feat: {
description: 'A new feature',
title: 'Features',
emoji: '✨',
},
fix: {
description: 'A bug fix',
title: 'Bug Fixes',
emoji: '🐛',
},
docs: {
description: 'Documentation only changes',
title: 'Documentation',
emoji: '📚',
},
style: {
description:
'Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)',
title: 'Styles',
emoji: '💎',
},
refactor: {
description: 'A code change that neither fixes a bug nor adds a feature',
title: 'Code Refactoring',
emoji: '📦',
},
perf: {
description: 'A code change that improves performance',
title: 'Performance Improvements',
emoji: '🚀',
},
test: {
description: 'Adding missing tests or correcting existing tests',
title: 'Tests',
emoji: '🚨',
},
build: {
description:
'Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)',
title: 'Builds',
emoji: '🛠',
},
ci: {
description:
'Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)',
title: 'Continuous Integrations',
emoji: '⚙️',
},
chore: {
description: "Other changes that don't modify src or test files",
title: 'Chores',
emoji: '♻️',
},
revert: {
description: 'Reverts a previous commit',
title: 'Reverts',
emoji: '🗑',
},
},
},
scope: {
description: 'What is the scope of this change (e.g. component or file name)',
},
subject: {
description: 'Write a short, imperative tense description of the change',
},
body: {
description: 'Provide a longer description of the change',
},
isBreaking: {
description: 'Are there any breaking changes?',
},
breakingBody: {
description:
'A BREAKING CHANGE commit requires a body. Please enter a longer description of the commit itself',
},
breaking: {
description: 'Describe the breaking changes',
},
isIssueAffected: {
description: 'Does this change affect any open issues?',
},
issuesBody: {
description:
'If issues are closed, the commit requires a body. Please enter a longer description of the commit itself',
},
issues: {
description: 'Add issue references (e.g. "fix #123", "re #123".)',
},
},
},
};

9
package-lock.json generated
View File

@@ -19,6 +19,7 @@
"react": "^18.2.0",
"react-devtools-core": "^5.0.0",
"react-dom": "^18.2.0",
"react-icons": "^5.0.1",
"react-window": "^1.8.10",
"sass": "^1.70.0",
"sql.js": "1.10.2",
@@ -20112,6 +20113,14 @@
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==",
"dev": true
},
"node_modules/react-icons": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.0.1.tgz",
"integrity": "sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw==",
"peerDependencies": {
"react": "*"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",

View File

@@ -9,14 +9,23 @@
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"prettier": "prettier src --check",
"prettier:fix": "prettier src --write",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives",
"lint:fix": "eslint src --ext ts,tsx --report-unused-disable-directives --fix",
"test": "vitest",
"test:ui": "vitest --ui",
"coverage": "vitest run --coverage",
"preview": "vite preview",
"devtools": "react-devtools",
"preinstall": "npx only-allow pnpm",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
"build-storybook": "storybook build",
"prepare": "husky"
},
"dependencies": {
"@headlessui/react": "^1.7.18",
"@headlessui/tailwindcss": "^0.2.0",
"@hello-pangea/dnd": "^16.5.0",
"@types/sql.js": "^1.4.9",
"@vitejs/plugin-react": "^4.2.1",
@@ -28,30 +37,31 @@
"react": "^18.2.0",
"react-devtools-core": "^5.0.0",
"react-dom": "^18.2.0",
"react-window": "^1.8.10",
"sass": "^1.70.0",
"sass": "^1.71.1",
"sql.js": "1.10.2",
"styled-components": "^6.1.8",
"uuid": "^9.0.1"
},
"devDependencies": {
"@commitlint/cli": "^18.6.1",
"@commitlint/config-conventional": "^18.6.2",
"@crxjs/vite-plugin": "2.0.0-beta.21",
"@iconify-json/material-symbols": "^1.1.72",
"@iconify-json/material-symbols": "^1.1.73",
"@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",
"@storybook/addon-essentials": "^7.6.17",
"@storybook/addon-links": "^7.6.17",
"@storybook/blocks": "^7.6.17",
"@storybook/react": "^7.6.17",
"@storybook/react-vite": "^7.6.17",
"@storybook/test": "^7.6.17",
"@svgr/core": "^8.1.0",
"@svgr/plugin-jsx": "^8.1.0",
"@types/chrome": "^0.0.260",
"@types/node": "^20.11.17",
"@types/node": "^20.11.19",
"@types/prompts": "^2.4.9",
"@types/react": "^18.2.55",
"@types/react": "^18.2.57",
"@types/react-dom": "^18.2.19",
"@types/semver": "^7.5.6",
"@types/semver": "^7.5.7",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
@@ -63,10 +73,12 @@
"@unocss/transformer-directives": "^0.58.5",
"@unocss/transformer-variant-group": "^0.58.5",
"@vitejs/plugin-react-swc": "^3.6.0",
"chromatic": "^10.9.1",
"@vitest/coverage-v8": "^1.3.1",
"@vitest/ui": "^1.3.1",
"chromatic": "^10.9.6",
"cssnano": "^6.0.3",
"cssnano-preset-advanced": "^6.0.3",
"dotenv": "^16.4.1",
"dotenv": "^16.4.5",
"es-module-lexer": "^1.4.1",
"eslint": "^8.56.0",
"eslint-config-airbnb": "^19.0.4",
@@ -75,24 +87,27 @@
"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-jsdoc": "^48.1.0",
"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-simple-import-sort": "^12.0.0",
"eslint-plugin-storybook": "^0.6.15",
"husky": "^9.0.11",
"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",
"storybook": "^7.6.17",
"typescript": "^5.3.3",
"unocss": "^0.58.5",
"unplugin-icons": "^0.18.5",
"vite": "^5.1.1",
"vite-plugin-inspect": "^0.8.3"
"vite": "^5.1.4",
"vite-plugin-inspect": "^0.8.3",
"vitest": "^1.3.1"
},
"pnpm": {
"patchedDependencies": {

4186
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,4 +1,5 @@
import { defineManifest } from '@crxjs/vite-plugin';
import packageJson from '../package.json';
// Convert from Semver (example: 0.1.0-beta6)

View File

@@ -1,5 +1,6 @@
import { BACKGROUND_MESSAGES } from '@shared/messages';
import type { 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';

View File

@@ -1,5 +1,5 @@
import BrowserActionMessages from '@shared/messages/BrowserActionMessages';
import { MessageHandler } from 'chrome-extension-toolkit';
import type BrowserActionMessages from '@shared/messages/BrowserActionMessages';
import type { MessageHandler } from 'chrome-extension-toolkit';
const browserActionHandler: MessageHandler<BrowserActionMessages> = {
disableBrowserAction({ sender, sendResponse }) {

View File

@@ -1,6 +1,6 @@
import HotReloadingMessages from '@shared/messages/HotReloadingMessages';
import type HotReloadingMessages from '@shared/messages/HotReloadingMessages';
import { DevStore } from '@shared/storage/DevStore';
import { MessageHandler } from 'chrome-extension-toolkit';
import type { MessageHandler } from 'chrome-extension-toolkit';
const hotReloadingHandler: MessageHandler<HotReloadingMessages> = {
async reloadExtension({ sendResponse }) {

View File

@@ -1,5 +1,6 @@
import TabManagementMessages from '@shared/messages/TabManagementMessages';
import { MessageHandler } from 'chrome-extension-toolkit';
import type TabManagementMessages from '@shared/messages/TabManagementMessages';
import type { MessageHandler } from 'chrome-extension-toolkit';
import openNewTab from '../util/openNewTab';
const tabManagementHandler: MessageHandler<TabManagementMessages> = {

View File

@@ -1,6 +1,7 @@
import { UserScheduleMessages } from '@shared/messages/UserScheduleMessages';
import type { UserScheduleMessages } from '@shared/messages/UserScheduleMessages';
import { Course } from '@shared/types/Course';
import { MessageHandler } from 'chrome-extension-toolkit';
import type { MessageHandler } from 'chrome-extension-toolkit';
import addCourse from '../lib/addCourse';
import clearCourses from '../lib/clearCourses';
import createSchedule from '../lib/createSchedule';

View File

@@ -1,5 +1,5 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
import { Course } from '@shared/types/Course';
import type { Course } from '@shared/types/Course';
/**
*

View File

@@ -1,5 +1,10 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
/**
* Clears the courses for a given schedule.
* @param scheduleName - The name of the schedule.
* @throws Error if the schedule does not exist.
*/
export default async function clearCourses(scheduleName: string): Promise<void> {
const schedules = await UserScheduleStore.get('schedules');
const schedule = schedules.find(schedule => schedule.name === scheduleName);

View File

@@ -14,6 +14,7 @@ export default async function createSchedule(scheduleName: string): Promise<stri
schedules.push({
name: scheduleName,
courses: [],
hours: 0,
});
await UserScheduleStore.set('schedules', schedules);

View File

@@ -1,5 +1,11 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
/**
* Deletes a schedule with the specified name.
*
* @param scheduleName - The name of the schedule to delete.
* @returns A promise that resolves to a string if there is an error, or undefined if the schedule is deleted successfully.
*/
export default async function deleteSchedule(scheduleName: string): Promise<string | undefined> {
const [schedules, activeIndex] = await Promise.all([
UserScheduleStore.get('schedules'),

View File

@@ -1,5 +1,5 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
import { Course } from '@shared/types/Course';
import type { Course } from '@shared/types/Course';
/**
*

View File

@@ -1,5 +1,11 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
/**
* Renames a schedule with the specified name to a new name.
* @param scheduleName - The name of the schedule to be renamed.
* @param newName - The new name for the schedule.
* @returns A promise that resolves to a string if there is an error, or undefined if the schedule is renamed successfully.
*/
export default async function renameSchedule(scheduleName: string, newName: string): Promise<string | undefined> {
const schedules = await UserScheduleStore.get('schedules');
const scheduleIndex = schedules.findIndex(schedule => schedule.name === scheduleName);

View File

@@ -1,5 +1,11 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
/**
* Switches the active schedule to the specified schedule name.
* Throws an error if the schedule does not exist.
* @param scheduleName - The name of the schedule to switch to.
* @returns A Promise that resolves when the active schedule is successfully switched.
*/
export default async function switchSchedule(scheduleName: string): Promise<void> {
const schedules = await UserScheduleStore.get('schedules');

View File

@@ -1,5 +1,6 @@
import React from 'react';
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
import React from 'react';
import { Calendar } from 'src/views/components/calendar/Calendar/Calendar';
/**
* Calendar page
@@ -8,7 +9,7 @@ import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot'
export default function CalendarMain() {
return (
<ExtensionRoot>
<div>Calendar Placeholder</div>
<Calendar />
</ExtensionRoot>
);
}

View File

@@ -1,18 +1,16 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Calendar</title>
</head>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Calendar</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./index.tsx" type="module"></script>
</body>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./index.tsx" type="module"></script>
</body>
</html>

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import CalendarMain from './CalendarMain';
createRoot(document.getElementById('root')).render(<CalendarMain />);

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import CourseCatalogMain from '@views/components/CourseCatalogMain';
import getSiteSupport, { SiteSupport } from '@views/lib/getSiteSupport';
import React from 'react';
import { createRoot } from 'react-dom/client';
const support = getSiteSupport(window.location.href);

View File

@@ -1,5 +1,5 @@
import React from 'react';
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
import React from 'react';
/**
*

View File

@@ -1,18 +1,16 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Debug</title>
</head>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Debug</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./index.tsx" type="module"></script>
</body>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./index.tsx" type="module"></script>
</body>
</html>

View File

@@ -1,5 +1,5 @@
import React from 'react';
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
import React from 'react';
/**
*

View File

@@ -1,18 +1,16 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Popup</title>
</head>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Popup</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./index.tsx" type="module"></script>
</body>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./index.tsx" type="module"></script>
</body>
</html>

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')).render(<App />);

View File

@@ -1,18 +1,16 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Popup</title>
</head>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Popup</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./index.tsx" type="module"></script>
</body>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./index.tsx" type="module"></script>
</body>
</html>

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import PopupMain from '../../views/components/PopupMain';
createRoot(document.getElementById('root')).render(<PopupMain />);

View File

@@ -1,3 +1,4 @@
/* eslint-disable jsdoc/require-jsdoc */
export default interface BrowserActionMessages {
/** make it so that clicking the browser action will open the popup.html */
enableBrowserAction: () => void;

View File

@@ -1,3 +1,4 @@
/* eslint-disable jsdoc/require-jsdoc */
export default interface HotReloadingMessages {
reloadExtension: () => void;
}

View File

@@ -1,5 +1,8 @@
import { Course } from '../types/Course';
import type { Course } from '@shared/types/Course';
/**
* Represents a collection of user schedule messages.
*/
export interface UserScheduleMessages {
/**
* Add a course to a schedule

View File

@@ -1,8 +1,9 @@
import { createMessenger } from 'chrome-extension-toolkit';
import BrowserActionMessages from './BrowserActionMessages';
import TabManagementMessages from './TabManagementMessages';
import TAB_MESSAGES from './TabMessages';
import { UserScheduleMessages } from './UserScheduleMessages';
import type BrowserActionMessages from './BrowserActionMessages';
import type TabManagementMessages from './TabManagementMessages';
import type TAB_MESSAGES from './TabMessages';
import type { UserScheduleMessages } from './UserScheduleMessages';
/**
* This is a type with all the message definitions that can be sent TO the background script

View File

@@ -15,6 +15,4 @@ export const ExtensionStore = createLocalStore<IExtensionStore>({
lastUpdate: Date.now(),
});
debugStore({ ExtensionStore });

View File

@@ -14,6 +14,7 @@ export const UserScheduleStore = createLocalStore<IUserScheduleStore>({
new UserSchedule({
courses: [],
name: 'Schedule 1',
hours: 0,
}),
],
activeIndex: 0,

View File

@@ -1,6 +1,6 @@
/* eslint-disable max-classes-per-file */
import { Serialized } from 'chrome-extension-toolkit';
import { CourseMeeting } from './CourseMeeting';
import type { Serialized } from 'chrome-extension-toolkit';
import type { CourseMeeting } from './CourseMeeting';
import { CourseSchedule } from './CourseSchedule';
import Instructor from './Instructor';
@@ -12,12 +12,17 @@ export type InstructionMode = 'Online' | 'In Person' | 'Hybrid';
/**
* The status of a course (e.g. open, closed, waitlisted, cancelled)
*/
export enum Status {
OPEN = 'OPEN',
CLOSED = 'CLOSED',
WAITLISTED = 'WAITLISTED',
CANCELLED = 'CANCELLED',
}
export const Status = {
OPEN: 'OPEN',
CLOSED: 'CLOSED',
WAITLISTED: 'WAITLISTED',
CANCELLED: 'CANCELLED',
} as const;
/**
* Represents the type of status for a course.
*/
export type StatusType = (typeof Status)[keyof typeof Status];
/**
* Represents a semester, with the year and the season for when a course is offered
@@ -49,7 +54,7 @@ export class Course {
/** The number of credits that a course is worth */
creditHours: number;
/** Is the course open, closed, waitlisted, or cancelled? */
status: Status;
status: StatusType;
/** all the people that are teaching this course, and some metadata about their names */
instructors: Instructor[];
/** Some courses at UT are reserved for certain groups of people or people within a certain major, which makes it difficult for people outside of that group to register for the course. */

View File

@@ -1,4 +1,4 @@
import { Serialized } from 'chrome-extension-toolkit';
import type { Serialized } from 'chrome-extension-toolkit';
/**
* a map of the days of the week that a class is taught, and the corresponding abbreviation

View File

@@ -1,5 +1,7 @@
import { Serialized } from 'chrome-extension-toolkit';
import { CourseMeeting, Day, DAY_MAP } from './CourseMeeting';
import type { Serialized } from 'chrome-extension-toolkit';
import type { Day } from './CourseMeeting';
import { CourseMeeting, DAY_MAP } from './CourseMeeting';
/**
* This represents the schedule for a course, which includes all the meeting times for the course, as well as helper functions for parsing, serializing, and deserializing the schedule

View File

@@ -1,5 +1,5 @@
/* eslint-disable max-classes-per-file */
import { PointOptionsObject } from 'highcharts';
import { Semester } from './Course';
/**
* Each of the possible letter grades that can be given in a course

View File

@@ -1,4 +1,5 @@
import { Serialized } from 'chrome-extension-toolkit';
import type { Serialized } from 'chrome-extension-toolkit';
import { capitalize } from '../util/string';
/**

View File

@@ -1,4 +1,5 @@
import { Serialized } from 'chrome-extension-toolkit';
import type { Serialized } from 'chrome-extension-toolkit';
import { Course } from './Course';
/**
@@ -7,10 +8,15 @@ import { Course } from './Course';
export class UserSchedule {
courses: Course[];
name: string;
hours: number;
constructor(schedule: Serialized<UserSchedule>) {
this.courses = schedule.courses.map(c => new Course(c));
this.name = schedule.name;
this.hours = 0;
for (const course of this.courses) {
this.hours += course.creditHours;
}
}
containsCourse(course: Course): boolean {

View File

@@ -1,12 +1,20 @@
import { theme } from 'unocss/preset-mini';
/**
* Represents the colors for a course.
*/
export interface CourseColors {
primaryColor: string;
secondaryColor: string;
}
// calculates luminance of a hex string
function getLuminance(hex: string): number {
/**
* Calculates the luminance of a given hexadecimal color.
*
* @param hex - The hexadecimal color value.
* @returns The luminance value between 0 and 1.
*/
export function getLuminance(hex: string): number {
let r = parseInt(hex.substring(1, 3), 16);
let g = parseInt(hex.substring(3, 5), 16);
let b = parseInt(hex.substring(5, 7), 16);

View File

@@ -1,15 +1,18 @@
import React, { SVGProps } from 'react';
import type { StatusType } from '@shared/types/Course';
import { Status } from '@shared/types/Course';
import type { SVGProps } from 'react';
import React from 'react';
import ClosedIcon from '~icons/material-symbols/lock';
import WaitlistIcon from '~icons/material-symbols/timelapse';
import CancelledIcon from '~icons/material-symbols/warning';
import { Status } from '../types/Course';
/**
* Get Icon component based on status
* @param props.status status
* @returns React.ReactElement - the icon component
*/
export function StatusIcon(props: SVGProps<SVGSVGElement> & { status: Status }): React.ReactElement {
export function StatusIcon(props: SVGProps<SVGSVGElement> & { status: StatusType }): React.ReactElement {
const { status, ...rest } = props;
switch (props.status) {

View File

@@ -18,6 +18,8 @@ export function capitalize(input: string): string {
}
capitalized += ' ';
}
capitalized = capitalized.trim(); // Remove extra space
return capitalized;
}
@@ -31,7 +33,7 @@ export function capitalizeFirstLetter(input: string): string {
}
/**
* Cuts the
* Cuts the input string to the specified length and adds an ellipsis if the string is longer than the specified length.
* @param input The string to ellipsify.
* @param length The length of the string to return.
* @returns The ellipsified string.

View File

@@ -0,0 +1,22 @@
import { describe, expect, it } from 'vitest';
import { getLuminance } from '../colors';
describe('getLuminance', () => {
it('should return the correct luminance value for a given hex color', () => {
// Test case 1: Hex color #FFFFFF (white)
expect(getLuminance('#FFFFFF')).toBeCloseTo(1);
// Test case 2: Hex color #000000 (black)
expect(getLuminance('#000000')).toBeCloseTo(0);
// Test case 3: Hex color #FF0000 (red)
expect(getLuminance('#FF0000')).toBeCloseTo(0.2126);
// Test case 4: Hex color #00FF00 (green)
expect(getLuminance('#00FF00')).toBeCloseTo(0.7152);
// Test case 5: Hex color #0000FF (blue)
expect(getLuminance('#0000FF')).toBeCloseTo(0.0722);
});
});

View File

@@ -0,0 +1,26 @@
import { describe, expect, it } from 'vitest';
import { generateRandomId } from '../random';
describe('generateRandomId', () => {
it('should generate a random ID with the specified length', () => {
// Test case 1: Length 5
expect(generateRandomId(5)).toHaveLength(5);
// Test case 2: Length 10
expect(generateRandomId(10)).toHaveLength(10);
// Test case 3: Length 15
expect(generateRandomId(15)).toHaveLength(15);
});
it('should generate a unique ID each time', () => {
// Generate 100 IDs and check if they are all unique
const ids = new Set<string>();
for (let i = 0; i < 100; i += 1) {
const id = generateRandomId();
expect(ids.has(id)).toBe(false);
ids.add(id);
}
});
});

View File

@@ -0,0 +1,64 @@
import { describe, expect, it } from 'vitest';
import { capitalize, capitalizeFirstLetter, ellipsify } from '../string';
// TODO: Fix `string.ts` and `string.test.ts` to make the tests pass
// `capitalize` is adding an extra space at the end of the word.
describe('capitalize', () => {
it('should capitalize the first letter of each word', () => {
// Debug
// const word = 'hello world';
// const capitalized = capitalize(word);
// console.log(capitalize(word));
// console.log(capitalized.length);
// console.log(capitalized.split(''));
// Test case 1: Single word
expect(capitalize('hello')).toBe('Hello');
// Test case 2: Multiple words
expect(capitalize('hello world')).toBe('Hello World');
// Test case 3: Words with hyphens
expect(capitalize('hello-world')).toBe('Hello-World');
// Test case 4: Words with hyphens and spaces
expect(capitalize('hello-world test')).toBe('Hello-World Test');
});
});
describe('capitalizeFirstLetter', () => {
it('should return a string with the first letter capitalized', () => {
// Test case 1: Single word
expect(capitalizeFirstLetter('hello')).toBe('Hello');
// Test case 2: Word with all lowercase letters
expect(capitalizeFirstLetter('world')).toBe('World');
// Test case 3: Word with all uppercase letters
expect(capitalizeFirstLetter('EXAMPLE')).toBe('Example');
// Test case 4: Word with mixed case letters
expect(capitalizeFirstLetter('tEsT')).toBe('Test');
});
it('should handle empty string input', () => {
expect(capitalizeFirstLetter('')).toBe('');
});
});
describe('ellipsify', () => {
it('should add ellipsis if the input string exceeds the specified character limit', () => {
// Test case 1: Input string is shorter than the character limit
expect(ellipsify('Hello', 10)).toBe('Hello');
// Test case 2: Input string is equal to the character limit
expect(ellipsify('Hello World', 11)).toBe('Hello World');
// Test case 3: Input string is longer than the character limit
expect(ellipsify('Hello World', 5)).toBe('Hello...');
// Test case 4: Input string is empty
expect(ellipsify('', 5)).toBe('');
});
});

View File

@@ -0,0 +1,51 @@
import { describe, expect, it } from 'vitest';
import { getThemeColorHexByName, getThemeColorRgbByName, hexToRgb } from '../themeColors';
describe('hexToRgb', () => {
it('should convert hex color to RGB', () => {
expect(hexToRgb('#BF5700')).toEqual([191, 87, 0]);
expect(hexToRgb('#333F48')).toEqual([51, 63, 72]);
expect(hexToRgb('#f8971f')).toEqual([248, 151, 31]);
expect(hexToRgb('#ffd600')).toEqual([255, 214, 0]);
expect(hexToRgb('#a6cd57')).toEqual([166, 205, 87]);
expect(hexToRgb('#579d42')).toEqual([87, 157, 66]);
expect(hexToRgb('#00a9b7')).toEqual([0, 169, 183]);
expect(hexToRgb('#005f86')).toEqual([0, 95, 134]);
expect(hexToRgb('#9cadb7')).toEqual([156, 173, 183]);
expect(hexToRgb('#d6d2c4')).toEqual([214, 210, 196]);
expect(hexToRgb('#95a5a6')).toEqual([149, 165, 166]);
expect(hexToRgb('#B91C1C')).toEqual([185, 28, 28]);
expect(hexToRgb('#af2e2d')).toEqual([175, 46, 45]);
expect(hexToRgb('#1a2024')).toEqual([26, 32, 36]);
expect(hexToRgb('#22c55e')).toEqual([34, 197, 94]);
expect(hexToRgb('#a3e635')).toEqual([163, 230, 53]);
expect(hexToRgb('#84CC16')).toEqual([132, 204, 22]);
expect(hexToRgb('#FDE047')).toEqual([253, 224, 71]);
expect(hexToRgb('#FACC15')).toEqual([250, 204, 21]);
expect(hexToRgb('#F59E0B')).toEqual([245, 158, 11]);
expect(hexToRgb('#FB923C')).toEqual([251, 146, 60]);
expect(hexToRgb('#F97316')).toEqual([249, 115, 22]);
expect(hexToRgb('#EA580C')).toEqual([234, 88, 12]);
expect(hexToRgb('#DC2626')).toEqual([220, 38, 38]);
expect(hexToRgb('#B91C1C')).toEqual([185, 28, 28]);
});
});
describe('getThemeColorHexByName', () => {
it('should return the hex color value by name', () => {
expect(getThemeColorHexByName('ut-burntorange')).toEqual('#BF5700');
expect(getThemeColorHexByName('ut-offwhite')).toEqual('#D6D2C4');
expect(getThemeColorHexByName('ut-black')).toEqual('#333F48');
// Add more test cases for other theme color names
});
});
describe('getThemeColorRgbByName', () => {
it('should return the RGB color value by name', () => {
expect(getThemeColorRgbByName('ut-burntorange')).toEqual([191, 87, 0]);
expect(getThemeColorRgbByName('ut-offwhite')).toEqual([214, 210, 196]);
expect(getThemeColorRgbByName('ut-black')).toEqual([51, 63, 72]);
// Add more test cases for other theme color names
});
});

View File

@@ -0,0 +1,14 @@
import { describe, expect, it } from 'vitest';
import { sleep } from '../time';
describe('sleep', () => {
it('should resolve after the specified number of milliseconds', async () => {
const start = Date.now();
const milliseconds = 1000;
await sleep(milliseconds);
const end = Date.now();
const elapsed = end - start;
expect(elapsed).toBeGreaterThanOrEqual(milliseconds);
});
});

View File

@@ -2,21 +2,36 @@ export const colors = {
ut: {
burntorange: '#BF5700',
black: '#333F48',
orange: '#f8971f',
yellow: '#ffd600',
lightgreen: '#a6cd57',
green: '#579d42',
teal: '#00a9b7',
blue: '#005f86',
gray: '#9cadb7',
offwhite: '#d6d2c4',
concrete: '#95a5a6',
orange: '#F8971F',
yellow: '#FFD600',
lightgreen: '#A6CD57',
green: '#579D42',
teal: '#00A9B7',
blue: '#005F86',
gray: '#9CADB7',
offwhite: '#D6D2C4',
concrete: '#95A5A6',
red: '#B91C1C', // Not sure if this should be here, but it's used for remove course, and add course is ut-green
},
theme: {
red: '#af2e2d',
black: '#1a2024',
red: '#AF2E2D',
black: '#1A2024',
},
} as const;
gradeDistribution: {
a: '#22C55E',
aminus: '#A3E635',
bplus: '#84CC16',
b: '#FDE047',
bminus: '#FACC15',
cplus: '#F59E0B',
c: '#FB923C',
cminus: '#F97316',
dplus: '#EA580C', // TODO (achadaga): copilot generated, get actual color from Isaiah
d: '#DC2626',
dminus: '#B91C1C',
f: '#B91C1C',
},
} as const satisfies Record<string, Record<string, string>>;
type NestedKeys<T> = {
[K in keyof T]: T[K] extends Record<string, any> ? `${string & K}-${string & keyof T[K]}` : never;
@@ -27,6 +42,10 @@ type NestedKeys<T> = {
*/
export type ThemeColor = NestedKeys<typeof colors>;
/**
* Flattened colors object.
* @type {Record<ThemeColor, string>}
*/
export const colorsFlattened = Object.entries(colors).reduce(
(acc, [prefix, group]) => {
for (const [name, hex] of Object.entries(group)) {
@@ -37,9 +56,18 @@ export const colorsFlattened = Object.entries(colors).reduce(
{} as Record<ThemeColor, string>
);
const hexToRgb = (hex: string) =>
/**
* Converts a hexadecimal color code to an RGB color array.
* @param hex The hexadecimal color code to convert.
* @returns An array representing the RGB color values.
*/
export const hexToRgb = (hex: string) =>
hex.match(/[0-9a-f]{2}/gi).map(partialHex => parseInt(partialHex, 16)) as [number, number, number];
/**
* Represents the flattened RGB values of the colors.
* @type {Record<ThemeColor, ReturnType<typeof hexToRgb>>}
*/
const colorsFlattenedRgb = Object.fromEntries(
Object.entries(colorsFlattened).map(([name, hex]) => [name, hexToRgb(hex)])
) as Record<ThemeColor, ReturnType<typeof hexToRgb>>;

View File

@@ -5,7 +5,9 @@ export const HOUR = 60 * MINUTE;
export const DAY = 24 * HOUR;
/**
*
* Pauses the execution for the specified number of milliseconds.
* @param milliseconds - The number of milliseconds to sleep.
* @returns A promise that resolves after the specified number of milliseconds.
*/
export const sleep = (milliseconds: number): Promise<void> => new Promise(resolve => setTimeout(resolve, milliseconds));

View File

@@ -1,7 +1,8 @@
import { colorsFlattened } from '@shared/util/themeColors';
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from '@views/components/common/Button/Button';
import React from 'react';
import { colorsFlattened } from 'src/shared/util/themeColors';
import { Button } from 'src/views/components/common/Button/Button';
import AddIcon from '~icons/material-symbols/add';
import CalendarMonthIcon from '~icons/material-symbols/calendar-month';
import DescriptionIcon from '~icons/material-symbols/description';
@@ -135,7 +136,7 @@ export const CourseCatalogActionButtons: Story = {
<Button {...props} variant='outline' color='ut-teal' icon={HappyFaceIcon}>
CES
</Button>
<Button {...props} variant='outline' color='ut-yellow' icon={DescriptionIcon}>
<Button {...props} variant='outline' color='ut-orange' icon={DescriptionIcon}>
Past Syllabi
</Button>
<Button {...props} variant='filled' color='ut-green' icon={AddIcon}>

View File

@@ -1,69 +0,0 @@
import { Course, Status } from '@shared/types/Course';
import { getCourseColors } from '@shared/util/colors';
import { Meta, StoryObj } from '@storybook/react';
import CalendarCourseCell from '@views/components/common/CalendarCourseCell/CalendarCourseCell';
import React from 'react';
import { exampleCourse } from './PopupCourseBlock.stories';
const meta = {
title: 'Components/Common/CalendarCourseCell',
component: CalendarCourseCell,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
department: { control: { type: 'text' } },
courseNumber: { control: { type: 'text' } },
instructorLastName: { control: { type: 'text' } },
status: { control: { type: 'select', options: Object.values(Status) } },
meetingTime: { control: { type: 'text' } },
colors: { control: { type: 'object' } },
},
render: (args: any) => (
<div className='w-45'>
<CalendarCourseCell {...args} />
</div>
),
args: {
department: exampleCourse.department,
courseNumber: exampleCourse.number,
instructorLastName: exampleCourse.instructors[0].lastName,
status: exampleCourse.status,
meetingTime: exampleCourse.schedule.meetings[0].getTimeString({ separator: '-' }),
colors: getCourseColors('emerald', 500),
},
} satisfies Meta<typeof CalendarCourseCell>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};
export const Variants: Story = {
render: props => (
<div className='grid grid-cols-2 h-40 max-w-60 w-90vw gap-x-4 gap-y-2'>
<CalendarCourseCell
{...props}
course={new Course({ ...exampleCourse, status: Status.OPEN })}
colors={getCourseColors('green', 500)}
/>
<CalendarCourseCell
{...props}
course={new Course({ ...exampleCourse, status: Status.CLOSED })}
colors={getCourseColors('teal', 400)}
/>
<CalendarCourseCell
{...props}
course={new Course({ ...exampleCourse, status: Status.WAITLISTED })}
colors={getCourseColors('indigo', 400)}
/>
<CalendarCourseCell
{...props}
course={new Course({ ...exampleCourse, status: Status.CANCELLED })}
colors={getCourseColors('red', 500)}
/>
</div>
),
};

View File

@@ -1,57 +0,0 @@
import { Meta, StoryObj } from '@storybook/react';
import CalendarGrid from 'src/views/components/common/CalendarGrid/CalendarGrid';
import { getCourseColors } from 'src/shared/util/colors';
import { CalendarGridCourse } from 'src/views/hooks/useFlattenedCourseSchedule';
import { Status } from 'src/shared/types/Course';
const meta = {
title: 'Components/Common/CalendarGrid',
component: CalendarGrid,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
saturdayClass: { control: 'boolean' },
},
} satisfies Meta<typeof CalendarGrid>;
export default meta;
const testData: CalendarGridCourse[] = [
{
calendarGridPoint: {
dayIndex: 0,
startIndex: 1,
endIndex: 2,
},
componentProps: {
courseDeptAndInstr: 'Course 1',
timeAndLocation: '9:00 AM - 10:00 AM, Room 101',
status: Status.OPEN,
colors: getCourseColors('emerald', 500),
},
},
{
calendarGridPoint: {
dayIndex: 1,
startIndex: 2,
endIndex: 3,
},
componentProps: {
courseDeptAndInstr: 'Course 2',
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
status: Status.CLOSED,
colors: getCourseColors('emerald', 500),
},
},
// add more data as needed
];
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
saturdayClass: true,
courseCells: testData,
},
};

View File

@@ -1,19 +0,0 @@
// Calendar.stories.tsx
import React from 'react';
import CalendarCell from '@views/components/common/CalendarGridCell/CalendarGridCell';
import type { Meta, StoryObj } from '@storybook/react';
const meta = {
title: 'Components/Common/CalendarGridCell',
component: CalendarCell,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
tags: ['autodocs'],
}
} satisfies Meta<typeof CalendarCell>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View File

@@ -1,20 +1,20 @@
import Card from 'src/views/components/common/Card/Card';
import type { Meta, StoryObj } from '@storybook/react';
import Card from '@views/components/common/Card/Card';
import React from 'react';
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = {
title: 'Components/Common/Card',
component: Card,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
},
args: {
children: <div>Hello</div>,
},
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
title: 'Components/Common/Card',
component: Card,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
},
args: {
children: <div>Hello</div>,
},
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
} satisfies Meta<typeof Card>;
export default meta;

View File

@@ -1,5 +1,5 @@
import { Meta, StoryObj } from '@storybook/react';
import { Chip } from 'src/views/components/common/Chip/Chip';
import type { Meta, StoryObj } from '@storybook/react';
import { Chip } from '@views/components/common/Chip/Chip';
const meta = {
title: 'Components/Common/Chip',
@@ -20,4 +20,4 @@ export const Default: Story = {
args: {
label: 'QR',
},
};
};

View File

@@ -1,8 +1,8 @@
import { Meta, StoryObj } from '@storybook/react';
import { Course, Status } from '@shared/types/Course';
import { CourseMeeting } from '@shared/types/CourseMeeting';
import Instructor from '@shared/types/Instructor';
import type { Meta, StoryObj } from '@storybook/react';
import ConflictsWithWarning from '@views/components/common/ConflictsWithWarning/ConflictsWithWarning';
import { Course, Status } from 'src/shared/types/Course';
import { CourseMeeting } from 'src/shared/types/CourseMeeting';
import Instructor from 'src/shared/types/Instructor';
export const ExampleCourse: Course = new Course({
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB',

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { Status } from '@shared/types/Course';
import { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react';
import CourseStatus from '@views/components/common/CourseStatus/CourseStatus';
import React from 'react';
const meta = {
title: 'Components/Common/CourseStatus',
@@ -40,7 +39,7 @@ export const Default: Story = {};
export const Variants: Story = {
render: args => (
<div className='flex flex-col gap-4 items-center'>
<div className='flex flex-col items-center gap-4'>
<CourseStatus {...args} size='small' />
<CourseStatus {...args} size='mini' />
</div>

View File

@@ -1,18 +0,0 @@
import Divider from 'src/views/components/common/Divider/Divider';
import type { Meta, StoryObj } from '@storybook/react';
const meta = {
title: 'Components/Common/Divider',
component: Divider,
tags: ['autodocs'],
argTypes: {
color: {
control: 'color',
},
},
} satisfies Meta<typeof Divider>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View File

@@ -0,0 +1,78 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from '@views/components/common/Button/Button';
import Divider from '@views/components/common/Divider/Divider';
import React from 'react';
import AddIcon from '~icons/material-symbols/add';
import CalendarMonthIcon from '~icons/material-symbols/calendar-month';
import DescriptionIcon from '~icons/material-symbols/description';
import HappyFaceIcon from '~icons/material-symbols/mood';
import ReviewsIcon from '~icons/material-symbols/reviews';
const meta = {
title: 'Components/Common/Divider',
component: Divider,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof Divider>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Vertical: Story = {
args: {
size: '2.5rem',
orientation: 'vertical',
},
render: props => <Divider {...props} />,
};
export const Horizontal: Story = {
args: {
size: '2.5rem',
orientation: 'horizontal',
},
render: props => <Divider {...props} />,
};
export const IGotHorizontalIGotVerticalWhatYouWant: Story = {
args: {
size: '2.5rem',
orientation: 'vertical',
},
render: props => (
<div className='grid grid-cols-7 grid-rows-3 items-center justify-items-center gap-3.75'>
{Array.from({ length: 21 }).map((_, i) => (
<Divider {...props} orientation={i % 2 === 0 ? 'horizontal' : 'vertical'} />
))}
</div>
),
};
export const CourseCatalogActionButtons: Story = {
args: {
size: '1.75rem',
orientation: 'vertical',
},
render: props => (
<div className='flex items-center gap-3.75'>
<Button variant='filled' color='ut-burntorange' icon={CalendarMonthIcon} />
<Divider {...props} />
<Button variant='outline' color='ut-blue' icon={ReviewsIcon}>
RateMyProf
</Button>
<Button variant='outline' color='ut-teal' icon={HappyFaceIcon}>
CES
</Button>
<Button variant='outline' color='ut-orange' icon={DescriptionIcon}>
Past Syllabi
</Button>
<Button variant='filled' color='ut-green' icon={AddIcon}>
Add Course
</Button>
</div>
),
};

View File

@@ -0,0 +1,157 @@
import { Course, Status } from '@shared/types/Course';
import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting';
import { CourseSchedule } from '@shared/types/CourseSchedule';
import Instructor from '@shared/types/Instructor';
import { UserSchedule } from '@shared/types/UserSchedule';
import type { Meta, StoryObj } from '@storybook/react';
import Dropdown from '@views/components/common/Dropdown/Dropdown';
import ScheduleListItem from '@views/components/common/ScheduleListItem/ScheduleListItem';
import type { Serialized } from 'chrome-extension-toolkit';
import React from 'react';
const meta: Meta<typeof Dropdown> = {
title: 'Components/Common/Dropdown',
component: Dropdown,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
dummySchedules: { control: 'object' },
dummyActiveIndex: { control: 'number' },
scheduleComponents: { control: 'object' },
},
render: (args: any) => (
<div className='w-80'>
<Dropdown {...args} />
</div>
),
} satisfies Meta<typeof Dropdown>;
export default meta;
type Story = StoryObj<typeof meta>;
const schedules = [
new UserSchedule({
courses: [
new Course({
uniqueId: 123,
number: '311C',
fullName: "311C - Bevo's Default Course",
courseName: "Bevo's Default Course",
department: 'BVO',
creditHours: 3,
status: Status.WAITLISTED,
instructors: [new Instructor({ firstName: '', lastName: 'Bevo', fullName: 'Bevo' })],
isReserved: false,
url: '',
flags: [],
schedule: new CourseSchedule({
meetings: [
new CourseMeeting({
days: [DAY_MAP.M, DAY_MAP.W, DAY_MAP.F],
startTime: 480,
endTime: 570,
location: {
building: 'UTC',
room: '123',
},
}),
],
}),
instructionMode: 'In Person',
semester: {
year: 2024,
season: 'Fall',
},
}),
],
name: 'Main Schedule',
hours: 0,
} as Serialized<UserSchedule>),
new UserSchedule({
courses: [
new Course({
uniqueId: 123,
number: '311C',
fullName: "311C - Bevo's Default Course",
courseName: "Bevo's Default Course",
department: 'BVO',
creditHours: 3,
status: Status.WAITLISTED,
instructors: [new Instructor({ firstName: '', lastName: 'Bevo', fullName: 'Bevo' })],
isReserved: false,
url: '',
flags: [],
schedule: new CourseSchedule({
meetings: [
new CourseMeeting({
days: [DAY_MAP.M, DAY_MAP.W, DAY_MAP.F],
startTime: 480,
endTime: 570,
location: {
building: 'UTC',
room: '123',
},
}),
],
}),
instructionMode: 'In Person',
semester: {
year: 2024,
season: 'Fall',
},
}),
new Course({
uniqueId: 123,
number: '311C',
fullName: "311C - Bevo's Default Course",
courseName: "Bevo's Default Course",
department: 'BVO',
creditHours: 3,
status: Status.WAITLISTED,
instructors: [new Instructor({ firstName: '', lastName: 'Bevo', fullName: 'Bevo' })],
isReserved: false,
url: '',
flags: [],
schedule: new CourseSchedule({
meetings: [
new CourseMeeting({
days: [DAY_MAP.M, DAY_MAP.W, DAY_MAP.F],
startTime: 480,
endTime: 570,
location: {
building: 'UTC',
room: '123',
},
}),
],
}),
instructionMode: 'In Person',
semester: {
year: 2024,
season: 'Fall',
},
}),
],
name: 'Backup #3',
hours: 0,
} as Serialized<UserSchedule>),
];
export const Hidden: Story = {
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/8tsCay2FRqctrdcZ3r9Ahw/UTRP?type=design&node-id=1579-5083&mode=dev',
},
},
args: {
dummySchedules: schedules,
dummyActiveIndex: 0,
scheduleComponents: schedules.map((schedule, index) => (
<ScheduleListItem active={index === 0} name={schedule.name} />
)),
},
};

View File

@@ -1,5 +1,5 @@
import { Meta, StoryObj } from '@storybook/react';
import ImportantLinks from 'src/views/components/ImportantLinks';
import type { Meta, StoryObj } from '@storybook/react';
import ImportantLinks from '@views/components/calendar/ImportantLinks';
const meta = {
title: 'Components/Common/ImportantLinks',

View File

@@ -1,5 +1,5 @@
import { Meta, StoryObj } from '@storybook/react';
import { InfoCard } from 'src/views/components/common/InfoCard/InfoCard';
import type { Meta, StoryObj } from '@storybook/react';
import { InfoCard } from '@views/components/common/InfoCard/InfoCard';
const meta = {
title: 'Components/Common/InfoCard',
@@ -22,4 +22,4 @@ export const Default: Story = {
titleText: 'WAITLIST SIZE',
bodyText: '14 Students',
},
};
};

View File

@@ -1,21 +1,21 @@
import Link from 'src/views/components/common/Link/Link';
import type { Meta, StoryObj } from '@storybook/react';
import Link from '@views/components/common/Link/Link';
const meta = {
title: 'Components/Common/Link',
component: Link,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
color: {
control: 'color',
title: 'Components/Common/Link',
component: Link,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
color: {
control: 'color',
},
},
args: {
children: 'Link',
},
},
args: {
children: 'Link',
},
} satisfies Meta<typeof Link>;
export default meta;

View File

@@ -1,15 +1,22 @@
import { Course, Status } from '@shared/types/Course';
import { CourseMeeting } from '@shared/types/CourseMeeting';
import Instructor from '@shared/types/Instructor';
import { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react';
import List from '@views/components/common/List/List';
import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock';
import React from 'react';
import { test_colors } from './PopupCourseBlock.stories';
import { TestColors } from './PopupCourseBlock.stories';
const numberOfCourses = 5;
const generateCourses = count => {
/**
* Generates an array of courses.
*
* @param count - The number of courses to generate.
* @returns An array of generated courses.
*/
export const GenerateCourses = count => {
const courses = [];
for (let i = 0; i < count; i++) {
@@ -61,10 +68,10 @@ const generateCourses = count => {
return courses;
};
const exampleCourses = generateCourses(numberOfCourses);
const exampleCourses = GenerateCourses(numberOfCourses);
const generateCourseBlocks = (exampleCourses, colors) =>
exampleCourses.map((course, i) => <PopupCourseBlock key={course.uniqueId} course={course} colors={colors[i]} />);
const exampleCourseBlocks = generateCourseBlocks(exampleCourses, test_colors);
export const ExampleCourseBlocks = generateCourseBlocks(exampleCourses, TestColors);
const meta = {
title: 'Components/Common/List',
@@ -87,7 +94,7 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
draggableElements: exampleCourseBlocks,
draggableElements: ExampleCourseBlocks,
itemHeight: 55,
listHeight: 300,
listWidth: 300,

View File

@@ -1,13 +1,19 @@
import { Course, Status } from '@shared/types/Course';
import { CourseMeeting } from '@shared/types/CourseMeeting';
import Instructor from '@shared/types/Instructor';
import { getCourseColors } from '@shared/util/colors';
import type { Meta, StoryObj } from '@storybook/react';
import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock';
import React from 'react';
import { Course, Status } from 'src/shared/types/Course';
import { CourseMeeting } from 'src/shared/types/CourseMeeting';
import Instructor from 'src/shared/types/Instructor';
import { getCourseColors } from 'src/shared/util/colors';
import { theme } from 'unocss/preset-mini';
export const exampleCourse: Course = new Course({
/**
* Represents an example course.
*
* @remarks
* This is a sample course object that provides information about a specific course.
*/
export const ExampleCourse: Course = new Course({
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB',
creditHours: 3,
department: 'C S',
@@ -62,7 +68,7 @@ const meta = {
// More on argTypes: https://storybook.js.org/docs/api/argtypes
args: {
colors: getCourseColors('emerald'),
course: exampleCourse,
course: ExampleCourse,
},
argTypes: {
colors: {
@@ -87,15 +93,15 @@ export const Default: Story = {
export const Variants: Story = {
render: props => (
<div className='grid grid-cols-2 max-w-2xl w-90vw gap-x-4 gap-y-2'>
<PopupCourseBlock {...props} course={new Course({ ...exampleCourse, status: Status.OPEN })} />
<PopupCourseBlock {...props} course={new Course({ ...exampleCourse, status: Status.CLOSED })} />
<PopupCourseBlock {...props} course={new Course({ ...exampleCourse, status: Status.WAITLISTED })} />
<PopupCourseBlock {...props} course={new Course({ ...exampleCourse, status: Status.CANCELLED })} />
<PopupCourseBlock {...props} course={new Course({ ...ExampleCourse, status: Status.OPEN })} />
<PopupCourseBlock {...props} course={new Course({ ...ExampleCourse, status: Status.CLOSED })} />
<PopupCourseBlock {...props} course={new Course({ ...ExampleCourse, status: Status.WAITLISTED })} />
<PopupCourseBlock {...props} course={new Course({ ...ExampleCourse, status: Status.CANCELLED })} />
</div>
),
};
export const test_colors = Object.keys(theme.colors)
export const TestColors = Object.keys(theme.colors)
// check that the color is a colorway (is an object)
.filter(color => typeof theme.colors[color] === 'object')
.slice(0, 17)
@@ -104,8 +110,8 @@ export const test_colors = Object.keys(theme.colors)
export const AllColors: Story = {
render: props => (
<div className='grid grid-flow-col grid-cols-2 grid-rows-9 max-w-2xl w-90vw gap-x-4 gap-y-2'>
{test_colors.map((color, i) => (
<PopupCourseBlock key={color.primaryColor} course={exampleCourse} colors={color} />
{TestColors.map((color, i) => (
<PopupCourseBlock key={color.primaryColor} course={ExampleCourse} colors={color} />
))}
</div>
),

View File

@@ -0,0 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react';
import PopupMain from '@views/components/PopupMain';
const meta = {
title: 'Components/Common/PopupMain',
component: PopupMain,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {},
} satisfies Meta<typeof PopupMain>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {},
};

View File

@@ -1,4 +1,4 @@
import { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react';
import ScheduleTotalHoursAndCourses from '@views/components/common/ScheduleTotalHoursAndCourses/ScheduleTotalHoursAndCourses';
const meta = {
@@ -11,7 +11,7 @@ const meta = {
argTypes: {
scheduleName: { control: 'text' },
totalHours: { control: 'number' },
totalCourses: { control: 'number' }
totalCourses: { control: 'number' },
},
} satisfies Meta<typeof ScheduleTotalHoursAndCourses>;
export default meta;
@@ -22,6 +22,6 @@ export const Default: Story = {
args: {
scheduleName: 'SCHEDULE',
totalHours: 22,
totalCourses: 8
totalCourses: 8,
},
};
};

View File

@@ -1,5 +1,6 @@
/* eslint-disable jsdoc/require-jsdoc */
import ScheduleListItem from '@views/components/common/ScheduleListItem/ScheduleListItem';
import React from 'react';
import ScheduleListItem from 'src/views/components/common/ScheduleListItem/ScheduleListItem';
export default {
title: 'Components/Common/ScheduleListItem',
@@ -14,21 +15,21 @@ export default {
},
};
export const Default = (args) => <ScheduleListItem {...args} />;
export const Default = args => <ScheduleListItem {...args} />;
Default.args = {
name: 'My Schedule',
active: true,
};
export const Active = (args) => <ScheduleListItem {...args} />;
export const Active = args => <ScheduleListItem {...args} />;
Active.args = {
name: 'My Schedule',
active: true,
};
export const Inactive = (args) => <ScheduleListItem {...args} />;
export const Inactive = args => <ScheduleListItem {...args} />;
Inactive.args = {
name: 'My Schedule',

View File

@@ -1,5 +1,5 @@
import { Meta, StoryObj } from '@storybook/react';
import Settings from 'src/views/components/Settings';
import type { Meta, StoryObj } from '@storybook/react';
import Settings from '@views/components/Settings';
const meta = {
title: 'Components/Common/Settings',

View File

@@ -1,11 +1,11 @@
import Spinner from 'src/views/components/common/Spinner/Spinner';
import type { Meta, StoryObj } from '@storybook/react';
import Spinner from '@views/components/common/Spinner/Spinner';
const meta = {
title: 'Components/Common/Spinner',
component: Spinner,
tags: ['autodocs'],
argTypes: {},
title: 'Components/Common/Spinner',
component: Spinner,
tags: ['autodocs'],
argTypes: {},
} satisfies Meta<typeof Spinner>;
export default meta;

View File

@@ -1,7 +1,6 @@
import { Button } from 'src/views/components/common/Button/Button';
import type { Meta, StoryObj } from '@storybook/react';
import Text from '@views/components/common/Text/Text';
import React from 'react';
import Text from '../../views/components/common/Text/Text';
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = {

View File

@@ -0,0 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Calendar } from '@views/components/calendar/Calendar/Calendar';
const meta = {
title: 'Components/Calendar/Calendar',
component: Calendar,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {},
} satisfies Meta<typeof Calendar>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {},
};

View File

@@ -1,9 +1,9 @@
import React from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { Course, Status } from '@shared/types/Course';
import Instructor from '@shared/types/Instructor';
import { CalendarBottomBar } from '@views/components/common/CalendarBottomBar/CalendarBottomBar';
import { getCourseColors } from '../../shared/util/colors';
import { getCourseColors } from '@shared/util/colors';
import type { Meta, StoryObj } from '@storybook/react';
import { CalendarBottomBar } from '@views/components/calendar/CalendarBottomBar/CalendarBottomBar';
import React from 'react';
const exampleGovCourse: Course = new Course({
courseName: 'Nope',
@@ -66,7 +66,7 @@ const examplePsyCourse: Course = new Course({
});
const meta = {
title: 'Components/Common/CalendarBottomBar',
title: 'Components/Calendar/CalendarBottomBar',
component: CalendarBottomBar,
parameters: {
layout: 'centered',

View File

@@ -1,12 +1,12 @@
import { Meta, StoryObj } from '@storybook/react';
import { Course, Status } from '@shared/types/Course';
import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting';
import { CourseSchedule } from '@shared/types/CourseSchedule';
import Instructor from '@shared/types/Instructor';
import CalendarCourse from '@views/components/common/CalendarCourseBlock/CalendarCourseMeeting';
import type { Meta, StoryObj } from '@storybook/react';
import CalendarCourse from '@views/components/calendar/CalendarCourseBlock/CalendarCourseMeeting';
const meta = {
title: 'Components/Common/CalendarCourseMeeting',
title: 'Components/Calendar/CalendarCourseMeeting',
component: CalendarCourse,
parameters: {
layout: 'centered',

View File

@@ -0,0 +1,115 @@
import { Course, Status } from '@shared/types/Course';
import { getCourseColors } from '@shared/util/colors';
import type { Meta, StoryObj } from '@storybook/react';
import CalendarCourseCell from '@views/components/calendar/CalendarCourseCell/CalendarCourseCell';
import React from 'react';
import { ExampleCourse } from '../PopupCourseBlock.stories';
const meta = {
title: 'Components/Calendar/CalendarCourseCell',
component: CalendarCourseCell,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
courseDeptAndInstr: { control: { type: 'text' } },
className: { control: { type: 'text' } },
status: { control: { type: 'select', options: Object.values(Status) } },
timeAndLocation: { control: { type: 'text' } },
colors: { control: { type: 'object' } },
},
render: (args: any) => (
<div className='w-45'>
<CalendarCourseCell {...args} />
</div>
),
args: {
courseDeptAndInstr: ExampleCourse.department,
className: ExampleCourse.number,
status: ExampleCourse.status,
timeAndLocation: ExampleCourse.schedule.meetings[0].getTimeString({ separator: '-' }),
colors: getCourseColors('emerald', 500),
},
} satisfies Meta<typeof CalendarCourseCell>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};
export const Variants: Story = {
render: props => (
<div className='grid grid-cols-2 h-40 max-w-60 w-90vw gap-x-4 gap-y-2'>
<CalendarCourseCell
{...props}
// course={new Course({ ...exampleCourse, status: Status.OPEN })}
// Course = new Course({
// courseName: 'PRINCIPLES OF COMPUTER SYSTEMS',
// creditHours: 3,
// department: 'C S',
// description: [
// 'Restricted to computer science majors.',
// 'An introduction to computer systems software abstractions with an emphasis on the connection of these abstractions to underlying computer hardware. Key abstractions include threads, virtual memory, protection, and I/O. Requires writing of synchronized multithreaded programs and pieces of an operating system.',
// 'Computer Science 439 and 439H may not both be counted.',
// 'Prerequisite: Computer Science 429, or 429H with a grade of at least C-.',
// 'May be counted toward the Independent Inquiry flag requirement.',
// ],
// flags: ['Independent Inquiry'],
// fullName: 'C S 439 PRINCIPLES OF COMPUTER SYSTEMS',
// instructionMode: 'In Person',
// instructors: [
// new Instructor({
// firstName: 'Allison',
// lastName: 'Norman',
// fullName: 'Allison Norman',
// }),
// ],
// isReserved: false,
// number: '439',
// schedule: {
// meetings: [
// new CourseMeeting({
// days: ['Tuesday', 'Thursday'],
// startTime: 930,
// endTime: 1050,
// }),
// new CourseMeeting({
// days: ['Friday'],
// startTime: 600,
// endTime: 720,
// }),
// ],
// },
// semester: {
// code: '12345',
// season: 'Spring',
// year: 2024,
// },
// status: Status.WAITLISTED,
// uniqueId: 67890,
// url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
// });
colors={getCourseColors('green', 500)}
/>
<CalendarCourseCell
{...props}
// course={new Course({ ...exampleCourse, status: Status.CLOSED })}
colors={getCourseColors('teal', 400)}
/>
<CalendarCourseCell
{...props}
// course={new Course({ ...exampleCourse, status: Status.WAITLISTED })}
colors={getCourseColors('indigo', 400)}
/>
<CalendarCourseCell
{...props}
// course={new Course({ ...exampleCourse, status: Status.CANCELLED })}
colors={getCourseColors('red', 500)}
/>
</div>
),
};

View File

@@ -0,0 +1,121 @@
import { Status } from '@shared/types/Course';
import { getCourseColors } from '@shared/util/colors';
import type { Meta, StoryObj } from '@storybook/react';
import CalendarGrid from '@views/components/calendar/CalendarGrid/CalendarGrid';
import type { CalendarGridCourse } from '@views/hooks/useFlattenedCourseSchedule';
const meta = {
title: 'Components/Calendar/CalendarGrid',
component: CalendarGrid,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
saturdayClass: { control: 'boolean' },
},
} satisfies Meta<typeof CalendarGrid>;
export default meta;
const testData: CalendarGridCourse[] = [
{
calendarGridPoint: {
dayIndex: 4,
startIndex: 10,
endIndex: 11,
},
componentProps: {
courseDeptAndInstr: 'Course 1',
timeAndLocation: '9:00 AM - 10:00 AM, Room 101',
status: Status.OPEN,
colors: getCourseColors('emerald', 500),
},
},
{
calendarGridPoint: {
dayIndex: 2,
startIndex: 5,
endIndex: 6,
},
componentProps: {
courseDeptAndInstr: 'Course 1',
timeAndLocation: '9:00 AM - 10:00 AM, Room 101',
status: Status.OPEN,
colors: getCourseColors('emerald', 500),
},
},
{
calendarGridPoint: {
dayIndex: 1,
startIndex: 10,
endIndex: 12,
},
componentProps: {
courseDeptAndInstr: 'Course 2',
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
status: Status.CLOSED,
colors: getCourseColors('emerald', 500),
},
},
{
calendarGridPoint: {
dayIndex: 4,
startIndex: 10,
endIndex: 11,
},
componentProps: {
courseDeptAndInstr: 'Course 1',
timeAndLocation: '9:00 AM - 10:00 AM, Room 101',
status: Status.OPEN,
colors: getCourseColors('emerald', 500),
},
},
{
calendarGridPoint: {
dayIndex: 1,
startIndex: 10,
endIndex: 12,
},
componentProps: {
courseDeptAndInstr: 'Course 2',
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
status: Status.CLOSED,
colors: getCourseColors('emerald', 500),
},
},
{
calendarGridPoint: {
dayIndex: 1,
startIndex: 10,
endIndex: 12,
},
componentProps: {
courseDeptAndInstr: 'Course 3',
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
status: Status.CLOSED,
colors: getCourseColors('emerald', 500),
},
},
{
calendarGridPoint: {
dayIndex: 1,
startIndex: 10,
endIndex: 12,
},
componentProps: {
courseDeptAndInstr: 'Course 4',
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
status: Status.CLOSED,
colors: getCourseColors('emerald', 500),
},
},
];
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
saturdayClass: true,
courseCells: testData,
},
};

View File

@@ -0,0 +1,18 @@
// Calendar.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import CalendarCell from '@views/components/calendar/CalendarGridCell/CalendarGridCell';
const meta = {
title: 'Components/Calendar/CalendarGridCell',
component: CalendarCell,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
tags: ['autodocs'],
},
} satisfies Meta<typeof CalendarCell>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View File

@@ -1,9 +1,8 @@
import React from 'react';
import { Meta, StoryObj } from '@storybook/react';
import CalendarHeader from '@views/components/common/CalendarHeader/CalenderHeader';
import type { Meta, StoryObj } from '@storybook/react';
import CalendarHeader from '@views/components/calendar/CalendarHeader/CalenderHeader';
const meta = {
title: 'Components/Common/CalendarHeader',
title: 'Components/Calendar/CalendarHeader',
component: CalendarHeader,
parameters: {
layout: 'centered',

View File

@@ -1,14 +1,14 @@
import { Course, Status } from '@shared/types/Course';
import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting';
import { CourseSchedule } from '@shared/types/CourseSchedule';
import Instructor from '@shared/types/Instructor';
import { UserSchedule } from '@shared/types/UserSchedule';
import type { Meta, StoryObj } from '@storybook/react';
import { CalendarSchedules } from '@views/components/calendar/CalendarSchedules/CalendarSchedules';
import React from 'react';
import { CourseMeeting, DAY_MAP } from 'src/shared/types/CourseMeeting';
import { CourseSchedule } from 'src/shared/types/CourseSchedule';
import Instructor from 'src/shared/types/Instructor';
import { CalendarSchedules } from 'src/views/components/common/CalendarSchedules/CalendarSchedules';
const meta = {
title: 'Components/Common/CalendarSchedules',
title: 'Components/Calendar/CalendarSchedules',
component: CalendarSchedules,
parameters: {
layout: 'centered',
@@ -64,6 +64,7 @@ const schedules = [
}),
],
name: 'Main Schedule',
hours: 0, // Add the missing 'hours' property
}),
new UserSchedule({
courses: [
@@ -131,6 +132,7 @@ const schedules = [
}),
],
name: 'Backup #3',
hours: 0, // Add the missing 'hours' property
}),
];

View File

@@ -0,0 +1,68 @@
import { Course, Status } from '@shared/types/Course';
import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting';
import { CourseSchedule } from '@shared/types/CourseSchedule';
import Instructor from '@shared/types/Instructor';
import type { Meta, StoryObj } from '@storybook/react';
import CourseCatalogInjectedPopup from '@views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup';
const exampleCourse: Course = new Course({
uniqueId: 50805,
number: '314',
fullName: 'C S 314 DATA STRUCTURES',
courseName: 'DATA STRUCTURES',
department: 'C S',
creditHours: 3,
status: Status.OPEN,
instructors: [
new Instructor({ fullName: 'SCOTT, MICHAEL', firstName: 'MICHAEL', lastName: 'SCOTT', middleInitial: 'D' }),
],
isReserved: true,
description: [
'Second part of a two-part sequence in programming. Introduction to specifications, simple unit testing, and debugging; building and using canonical data structures; algorithm analysis and reasoning techniques such as assertions and invariants.',
'Computer Science 314 and 314H may not both be counted.',
'BVO 311C and 312H may not both be counted.',
'Prerequisite: Computer Science 312 or 312H with a grade of at least C-.',
'May be counted toward the Quantitative Reasoning flag requirement.',
],
schedule: new CourseSchedule({
meetings: [
new CourseMeeting({
days: [DAY_MAP.T, DAY_MAP.TH],
startTime: 480,
endTime: 570,
location: { building: 'UTC', room: '123' },
}),
new CourseMeeting({
days: [DAY_MAP.TH],
startTime: 570,
endTime: 630,
location: { building: 'JES', room: '123' },
}),
],
}),
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
flags: ['Writing', 'Independent Inquiry'],
instructionMode: 'In Person',
semester: {
code: '12345',
year: 2024,
season: 'Spring',
},
});
const meta: Meta<typeof CourseCatalogInjectedPopup> = {
title: 'Components/Injected/CourseCatalogInjectedPopup',
component: CourseCatalogInjectedPopup,
argTypes: {
onClose: { action: 'onClose' },
},
};
export default meta;
type Story = StoryObj<typeof CourseCatalogInjectedPopup>;
export const Default: Story = {
args: {
course: exampleCourse,
},
};

View File

@@ -1,9 +1,9 @@
import { Course, Status } from 'src/shared/types/Course';
import { CourseMeeting } from 'src/shared/types/CourseMeeting';
import { UserSchedule } from 'src/shared/types/UserSchedule';
import CoursePopup from 'src/views/components/injected/CoursePopup/CoursePopup';
import { Course, Status } from '@shared/types/Course';
import { CourseMeeting } from '@shared/types/CourseMeeting';
import Instructor from '@shared/types/Instructor';
import { UserSchedule } from '@shared/types/UserSchedule';
import type { Meta, StoryObj } from '@storybook/react';
import Instructor from 'src/shared/types/Instructor';
import CoursePopup from '@views/components/injected/CoursePopupOld/CoursePopup';
const exampleCourse: Course = new Course({
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB',
@@ -57,6 +57,7 @@ const exampleCourse: Course = new Course({
const exampleSchedule: UserSchedule = new UserSchedule({
courses: [exampleCourse],
name: 'Example Schedule',
hours: 0,
});
const meta = {
@@ -96,6 +97,7 @@ export const Open: Story = {
activeSchedule: new UserSchedule({
courses: [],
name: 'Example Schedule',
hours: 0,
}),
},
};

View File

@@ -2,13 +2,7 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"composite": true,
"lib": [
"DOM",
"es2021"
],
"types": [
"chrome",
"node",
],
},
"lib": ["DOM", "es2021"],
"types": ["chrome", "node"]
}
}

View File

@@ -1,21 +1,23 @@
import { Course, ScrapedRow } from '@shared/types/Course';
import type { Course, ScrapedRow } from '@shared/types/Course';
import React, { useEffect, useState } from 'react';
import { useKeyPress } from '../hooks/useKeyPress';
import useSchedules from '../hooks/useSchedules';
import { CourseCatalogScraper } from '../lib/CourseCatalogScraper';
import getCourseTableRows from '../lib/getCourseTableRows';
import { SiteSupport } from '../lib/getSiteSupport';
import type { SiteSupport } from '../lib/getSiteSupport';
import { populateSearchInputs } from '../lib/populateSearchInputs';
import ExtensionRoot from './common/ExtensionRoot/ExtensionRoot';
import AutoLoad from './injected/AutoLoad/AutoLoad';
import CoursePopup from './injected/CoursePopup/CoursePopup';
import CourseCatalogInjectedPopup from './injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup';
import CoursePopup from './injected/CoursePopupOld/CoursePopup';
import RecruitmentBanner from './injected/RecruitmentBanner/RecruitmentBanner';
import TableHead from './injected/TableHead';
import TableRow from './injected/TableRow/TableRow';
import TableSubheading from './injected/TableSubheading/TableSubheading';
interface Props {
support: SiteSupport.COURSE_CATALOG_DETAILS | SiteSupport.COURSE_CATALOG_LIST;
support: any; // SiteSupport.COURSE_CATALOG_DETAILS | SiteSupport.COURSE_CATALOG_LIST;
}
/**
@@ -79,7 +81,7 @@ export default function CourseCatalogMain({ support }: Props) {
);
})}
{selectedCourse && (
<CoursePopup
<CourseCatalogInjectedPopup
course={selectedCourse}
activeSchedule={activeSchedule}
onClose={handleClearSelectedCourse}

View File

@@ -1,24 +1,164 @@
import { background } from '@shared/messages';
import logoImage from '@assets/logo.png'; // Adjust the path as necessary
import { Status } from '@shared/types/Course';
import { StatusIcon } from '@shared/util/icons';
import Divider from '@views/components/common/Divider/Divider';
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
import List from '@views/components/common/List/List'; // Ensure this path is correctly pointing to your List component
import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock';
import Text from '@views/components/common/Text/Text';
import { handleOpenCalendar } from '@views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions';
import useSchedules from '@views/hooks/useSchedules';
import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript';
import React from 'react';
import useSchedules from '../hooks/useSchedules';
import { Button } from './common/Button/Button';
import ExtensionRoot from './common/ExtensionRoot/ExtensionRoot';
import { TestColors } from 'src/stories/components/PopupCourseBlock.stories';
import CalendarIcon from '~icons/material-symbols/calendar-month';
import RefreshIcon from '~icons/material-symbols/refresh';
import SettingsIcon from '~icons/material-symbols/settings';
/**
* Renders the main popup component.
* This component displays the main schedule, courses, and options buttons.
*/
export default function PopupMain() {
const [activeSchedule, schedules] = useSchedules();
const [activeSchedule] = useSchedules();
// TODO: Add a button to to switch the active schedule
const draggableElements = activeSchedule?.courses.map((course, i) => (
<PopupCourseBlock key={course.uniqueId} course={course} colors={TestColors[i]} />
));
const handleOpenOptions = async () => {
// Not sure if it's bad practice to export this
const url = chrome.runtime.getURL('/src/pages/options/index.html');
await openTabFromContentScript(url);
};
return (
<ExtensionRoot>
<Button
onClick={() => {
if (!activeSchedule) return;
background.clearCourses({ scheduleName: activeSchedule?.name });
}}
>
Clear Courses
</Button>
<div className='mx-auto max-w-sm rounded-lg bg-white p-4 shadow-md'>
<div className='mb-2 flex items-center justify-between bg-white'>
<div className='flex items-center'>
<img src={logoImage} alt='Logo' style={{ width: '40px', height: '40px', marginRight: '8px' }} />
<div>
<Text as='div' variant='h1-course' style={{ color: '#bf5700', fontSize: '1.3rem' }}>
UT Registration
</Text>
<Text as='div' variant='h1-course' style={{ color: '#f8971f', fontSize: '1.3rem' }}>
Plus
</Text>
</div>
</div>
<div className='flex items-center'>
<button
style={{ backgroundColor: '#bf5700', borderRadius: '8px', padding: '8px' }}
onClick={handleOpenCalendar}
>
<CalendarIcon color='white' />
</button>
<button
style={{
backgroundColor: 'white',
marginLeft: '10px',
borderRadius: '8px',
padding: '8px',
boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)',
}}
onClick={handleOpenOptions}
>
<SettingsIcon color='#C05621' />
</button>
</div>
</div>
{/* <Divider color='#E2E8F0' type='solid' style={{ margin: '1rem 0' }} /> */}
<div
className='mb-4 rounded-lg bg-white p-2 text-left shadow-inner'
style={{ backgroundColor: 'white', border: '1px solid #FBD38D', borderRadius: '0.5rem' }}
>
<Text as='div' variant='h2-course' style={{ color: '#DD6B20', fontSize: '1.2rem' }}>
MAIN SCHEDULE:
</Text>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'start', color: '#333f48' }}>
<Text
as='div'
variant='h1'
style={{ fontSize: '1.2rem', fontWeight: 'bold', marginRight: '0.5rem' }}
>
22 HOURS
</Text>
<Text as='div' variant='h2-course' style={{ fontSize: '1.2rem' }}>
8 Courses
</Text>
</div>
</div>
{/* Integrate the List component here */}
{activeSchedule ? (
<List
draggableElements={draggableElements}
itemHeight={100} // Adjust based on your content size
listHeight={500} // Adjust based on total height you want for the list
listWidth={350} // Adjust based on your layout/design
gap={12} // Spacing between items
/>
) : null}
<div className='mt-4 flex justify-between border-t border-gray-200 p-4 text-xs'>
<div className='flex items-center'>
<div
style={{
backgroundColor: '#6B7280',
padding: '1px',
borderRadius: '4px',
marginRight: '3px',
marginLeft: '8px',
}}
>
<StatusIcon status={Status.WAITLISTED} className='h-5 w-5 text-white' />
</div>
<Text as='span' variant='mini'>
WAITLISTED
</Text>
</div>
<div className='flex items-center'>
<div
style={{
backgroundColor: '#6B7280',
padding: '1px',
borderRadius: '4px',
marginRight: '3px',
marginLeft: '8px',
}}
>
<StatusIcon status={Status.CLOSED} className='h-5 w-5 text-white' />
</div>
<Text as='span' variant='mini'>
CLOSED
</Text>
</div>
<div className='flex items-center'>
<div
style={{
backgroundColor: '#6B7280',
padding: '1px',
borderRadius: '4px',
marginRight: '3px',
marginLeft: '8px',
}}
>
<StatusIcon status={Status.CANCELLED} className='h-5 w-5 text-white' />
</div>
<Text as='span' variant='mini'>
CANCELLED
</Text>
</div>
</div>
<div className='mt-2 text-center text-xs'>
<div className='inline-flex items-center justify-center'>
<Text as='div' variant='mini'>
DATA UPDATED ON: 12:00 AM 02/01/2024
</Text>
<RefreshIcon className='ml-2 h-4 w-4 text-gray-600' />
</div>
</div>
</div>
</ExtensionRoot>
);
}

View File

@@ -0,0 +1,41 @@
import React from 'react';
import CalendarHeader from 'src/views/components/calendar/CalendarHeader/CalenderHeader';
import { CalendarBottomBar } from '../CalendarBottomBar/CalendarBottomBar';
import CalendarGrid from '../CalendarGrid/CalendarGrid';
import { CalendarSchedules } from '../CalendarSchedules/CalendarSchedules';
import ImportantLinks from '../ImportantLinks';
export const flags = ['WR', 'QR', 'GC', 'CD', 'E', 'II'];
interface Props {
label: string;
}
/**
* A reusable chip component that follows the design system of the extension.
* @returns
*/
export function Calendar(): JSX.Element {
return (
<>
<CalendarHeader />
<div className='h-screen w-full flex flex-col md:flex-row'>
<div className='min-h-[30%] flex flex-col items-start gap-2.5 p-5 pl-7'>
<div className='min-h-[30%]'>
<CalendarSchedules />
</div>
<ImportantLinks />
</div>
<div className='flex flex-grow flex-col gap-4 overflow-hidden'>
<div className='flex-grow overflow-auto'>
<CalendarGrid />
</div>
<div>
<CalendarBottomBar />
</div>
</div>
</div>
</>
);
}

View File

@@ -1,26 +1,29 @@
import React from 'react';
import clsx from 'clsx';
import Text from '../Text/Text';
import CalendarCourseBlock, { CalendarCourseCellProps } from '../CalendarCourseCell/CalendarCourseCell';
import { Button } from '../Button/Button';
import ImageIcon from '~icons/material-symbols/image';
import React from 'react';
import CalendarMonthIcon from '~icons/material-symbols/calendar-month';
import ImageIcon from '~icons/material-symbols/image';
import { Button } from '../../common/Button/Button';
import Text from '../../common/Text/Text';
import type { CalendarCourseCellProps } from '../CalendarCourseCell/CalendarCourseCell';
import CalendarCourseBlock from '../CalendarCourseCell/CalendarCourseCell';
type CalendarBottomBarProps = {
courses: CalendarCourseCellProps[];
courses?: CalendarCourseCellProps[];
};
/**
*
*/
export const CalendarBottomBar = ({ courses }: CalendarBottomBarProps): JSX.Element => {
if (courses.length === -1) console.log('foo'); // dumb line to make eslint happy
if (courses?.length === -1) console.log('foo'); // dumb line to make eslint happy
return (
<div className='w-full flex py-1.25'>
<div className='flex flex-grow items-center gap-3.75 pl-7.5 pr-2.5'>
<Text variant='h4'>Async. and Other:</Text>
<div className='h-14 inline-flex gap-2.5'>
{courses.map(course => (
{courses?.map(course => (
<CalendarCourseBlock
courseDeptAndInstr={course.courseDeptAndInstr}
status={course.status}

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { Course } from 'src/shared/types/Course';
import { CourseMeeting } from 'src/shared/types/CourseMeeting';
import type { Course } from 'src/shared/types/Course';
import type { CourseMeeting } from 'src/shared/types/CourseMeeting';
import styles from './CalendarCourseMeeting.module.scss';
/**
@@ -26,6 +27,8 @@ export interface CalendarCourseMeetingProps {
const CalendarCourseMeeting: React.FC<CalendarCourseMeetingProps> = ({
course,
meetingIdx,
color,
rightIcon,
}: CalendarCourseMeetingProps) => {
let meeting: CourseMeeting | null = meetingIdx !== undefined ? course.schedule.meetings[meetingIdx] : null;
return (

View File

@@ -1,20 +1,38 @@
import type { StatusType } from '@shared/types/Course';
import { Status } from '@shared/types/Course';
import Text from '@views/components/common/Text/Text';
import clsx from 'clsx';
import React from 'react';
import { CourseColors, pickFontColor } from 'src/shared/util/colors';
import type { CourseColors } from 'src/shared/util/colors';
import { pickFontColor } from 'src/shared/util/colors';
import ClosedIcon from '~icons/material-symbols/lock';
import WaitlistIcon from '~icons/material-symbols/timelapse';
import CancelledIcon from '~icons/material-symbols/warning';
import Text from '../Text/Text';
/**
* Props for the CalendarCourseCell component.
*/
export interface CalendarCourseCellProps {
courseDeptAndInstr: string;
timeAndLocation?: string;
status: Status;
status: StatusType;
colors: CourseColors;
className?: string;
}
/**
* Renders a cell for a calendar course.
*
* @component
* @param {CalendarCourseCellProps} props - The component props.
* @param {string} props.courseDeptAndInstr - The course department and instructor.
* @param {string} props.timeAndLocation - The time and location of the course.
* @param {Status} props.status - The status of the course.
* @param {Colors} props.colors - The colors for styling the cell.
* @param {string} props.className - Additional CSS class name for the cell.
* @returns {JSX.Element} The rendered component.
*/
const CalendarCourseCell: React.FC<CalendarCourseCellProps> = ({
courseDeptAndInstr,
timeAndLocation,
@@ -36,12 +54,12 @@ const CalendarCourseCell: React.FC<CalendarCourseCellProps> = ({
return (
<div
className={clsx('w-full flex justify-center rounded p-2', fontColor, className)}
className={clsx('h-full w-full flex justify-center rounded p-2 overflow-x-hidden', fontColor, className)}
style={{
backgroundColor: colors.primaryColor,
}}
>
<div className='flex flex-1 flex-col gap-1 overflow-x-hidden'>
<div className='flex flex-1 flex-col gap-1'>
<Text
variant='h1-course'
className={clsx('-my-0.8 leading-tight', {

View File

@@ -13,8 +13,10 @@
.calendarGrid {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-template-rows: repeat(13, 1fr);
grid-template-columns: auto repeat(5, 6fr);
grid-template-rows: repeat(26, 1fr);
width: 100%;
height: 100%;
}
.calendarRow {
@@ -25,7 +27,9 @@
display: flex;
flex-direction: column;
gap: 10px;
position: relative; // Ensuring that child elements can be positioned in relation to this.
position: relative;
width: 100%;
height: 100%;
}
.day {
@@ -53,7 +57,7 @@
justify-content: space-between;
align-items: flex-start;
flex: 1 0 0;
border-radius: var(--border-radius-none, 0px);
border-radius: 0px;
}
.timeBlock {
@@ -109,7 +113,7 @@
background-color: transparent;
color: #333;
cursor: pointer;
&:hover {
background-color: rgba(0, 0, 0, 0.1);
}
@@ -124,4 +128,13 @@
height: 30px;
width: 1px;
background-color: grey;
}
}
.dot {
height: 75%; /* 75% of the container's height */
width: 75%; /* 75% of the container's width */
background-color: #000000; /* Color of the dot */
border-radius: 50%; /* Rounds the corners into a circle */
top: 12.5%; /* Centers the dot vertically */
left: 12.5%; /* Centers the dot horizontally */
}

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