Compare commits
193 Commits
v2.2.0
...
derek/popu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4a368fbb6 | ||
|
|
29247d5dfa | ||
|
|
bb2efc0b46 | ||
|
|
d5a04c745f | ||
|
|
f01cb070b3 | ||
| b2eac59ae7 | |||
| e99664fdae | |||
| 49c0d63f0b | |||
|
|
8c82282467 | ||
|
|
0a3c31ec09 | ||
|
|
ce7917b474 | ||
|
|
a5fe6ec06b | ||
|
|
5a2ee0d19a | ||
|
|
9ec0d106f5 | ||
|
|
70a3f14e0a | ||
|
|
d69707b8e8 | ||
|
|
41e6d77d02 | ||
|
|
11fece0595 | ||
|
|
c676be4765 | ||
|
|
b4c96a9a10 | ||
|
|
adba5e1bbc | ||
|
|
58dc706ece | ||
|
|
70c75ff481 | ||
|
|
7f76af7ab3 | ||
|
|
a538f20aad | ||
|
|
0acd0b722c | ||
| ae32d0b645 | |||
|
|
f214ed7c01 | ||
|
|
e44b0c0e45 | ||
|
|
206c97c5b5 | ||
| ac71b838db | |||
|
|
4f5753917b | ||
|
|
478ab31706 | ||
|
|
79c5c97d98 | ||
|
|
42420f5502 | ||
|
|
0ced198f01 | ||
|
|
ff4ee494b6 | ||
|
|
a03bcf17b8 | ||
|
|
314ea09b41 | ||
|
|
1891bd941e | ||
|
|
af991e6609 | ||
|
|
fe3521dec7 | ||
|
|
a363efb2d2 | ||
|
|
e7ba9c54a6 | ||
|
|
d69e3435cf | ||
|
|
b554b4344d | ||
|
|
14f241d603 | ||
|
|
419e3066f1 | ||
|
|
ed3ff846d8 | ||
|
|
7132bcf572 | ||
|
|
0bc30d3d1e | ||
|
|
3878c7104e | ||
|
|
851947db0b | ||
|
|
724e1a1d19 | ||
|
|
66fea21abd | ||
|
|
7550b55b9b | ||
|
|
65f0cb27af | ||
|
|
9ce69c2f2e | ||
|
|
a46526fa40 | ||
|
|
c5968f3f11 | ||
|
|
bba067f591 | ||
|
|
71d8ac7486 | ||
|
|
2997cb87d4 | ||
|
|
8b2d07033c | ||
|
|
b8fe5109a9 | ||
|
|
2cffb794db | ||
|
|
73fe14e17a | ||
|
|
33ca937d12 | ||
|
|
c0968ef7a0 | ||
|
|
3c7b35d5f3 | ||
|
|
8edd062588 | ||
|
|
42d24f6367 | ||
|
|
2f19c57553 | ||
|
|
e3b8e4c270 | ||
|
|
5aef43496f | ||
|
|
e1224e11af | ||
|
|
7f826c2a78 | ||
|
|
89a8e42059 | ||
|
|
ed8915bcd1 | ||
|
|
1168584b8a | ||
|
|
a0496fea18 | ||
|
|
fd747e9e8e | ||
|
|
27fdd9c559 | ||
|
|
ba22ba427c | ||
|
|
3e4cec43d1 | ||
|
|
633c9ba593 | ||
|
|
0f14b8ce1b | ||
|
|
bee506b79b | ||
|
|
d8a4c91a80 | ||
|
|
14ab537db1 | ||
|
|
939208532b | ||
|
|
8e3502593e | ||
|
|
7b7b858cf5 | ||
|
|
82b467a223 | ||
|
|
f049a25aac | ||
|
|
39b4396f88 | ||
|
|
854d24bf0d | ||
|
|
c47320e9a3 | ||
|
|
982b7de50e | ||
|
|
17efb94e68 | ||
|
|
40631e2421 | ||
|
|
81e02c8187 | ||
|
|
d907a43552 | ||
|
|
3e1a20a9f2 | ||
|
|
d21843468b | ||
|
|
6b7e45b5f4 | ||
|
|
36b9189a6c | ||
|
|
80148ba0ff | ||
|
|
776cab9297 | ||
|
|
f95736339a | ||
|
|
3c83a3c064 | ||
|
|
0928e8d033 | ||
|
|
3b9ea8ebd5 | ||
|
|
b2e7af64b3 | ||
|
|
11ce6be578 | ||
|
|
524e3b46d3 | ||
|
|
000682b4db | ||
| 147d38d5c5 | |||
|
|
14a90f3dc0 | ||
| dcc7c731c6 | |||
| ad77d73363 | |||
| 80ee5bfdda | |||
| e93c5d3167 | |||
| 5ec0f19c53 | |||
| 24da9baffe | |||
| de16ece2ea | |||
| d42ba72170 | |||
| 64563b2f40 | |||
| 18dcf8a139 | |||
| 72ecb314e8 | |||
| 0ce6de8b13 | |||
| fa30c526b9 | |||
| c44fd014e9 | |||
| d44f5216f8 | |||
|
|
f1e8485eb5 | ||
|
|
b57415c846 | ||
|
|
ff06d05857 | ||
|
|
1a4d22ccf0 | ||
|
|
279ac076b0 | ||
|
|
e3301cc200 | ||
|
|
ccf2c68340 | ||
|
|
dc19d3975a | ||
|
|
2eaf1b3c36 | ||
|
|
16cc2f071e | ||
|
|
ae08ee02f4 | ||
| 4f3ccd9c90 | |||
| aaccd9b562 | |||
| 349e1e5332 | |||
| 08e66c95e3 | |||
| 873be55b8b | |||
| c20fb0827f | |||
| 1c0ad51914 | |||
| d81ec5c2fc | |||
| 4bc65ec171 | |||
| e630bc82ee | |||
| 36314e0e3b | |||
| cdc1fc9224 | |||
| be76f4bcc7 | |||
| a71bc0f9c3 | |||
| 3d95d03813 | |||
| d1720f3356 | |||
| 8a317c590b | |||
| 9ab86cfab1 | |||
| 01a3918502 | |||
| 7a014a7aab | |||
| 618089b17e | |||
| 96dbe40637 | |||
|
|
13bc06cc6d | ||
|
|
429b49111a | ||
|
|
6e595d21aa | ||
|
|
80043dc652 | ||
|
|
b470bf6996 | ||
|
|
e0bf48805a | ||
| bb2caa2dda | |||
|
|
dad74ddf4e | ||
| cbf31aecd5 | |||
| 6aab174618 | |||
| 8f2b61d28a | |||
| fb6781e17f | |||
| 1683d8c48b | |||
| e9f95ad3d8 | |||
| 745b83e945 | |||
| b0dec80364 | |||
|
|
03e4ab9d60 | ||
| 9854c9aafa | |||
| b161e319bd | |||
|
|
ae0d1a3c67 | ||
|
|
3da4a395dd | ||
|
|
77a1d67af3 | ||
|
|
856d6cda24 | ||
|
|
cbbea7f810 | ||
|
|
cedaa27a2c | ||
| 72b7a9d7b1 |
106
.eslintrc
106
.eslintrc
@@ -4,12 +4,9 @@
|
|||||||
"browser": true,
|
"browser": true,
|
||||||
"es6": true,
|
"es6": true,
|
||||||
"node": true,
|
"node": true,
|
||||||
"webextensions": true
|
"webextensions": true,
|
||||||
},
|
},
|
||||||
"ignorePatterns": [
|
"ignorePatterns": ["*.html", "tsconfig.json"],
|
||||||
"*.html",
|
|
||||||
"tsconfig.json"
|
|
||||||
],
|
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:react/recommended",
|
"plugin:react/recommended",
|
||||||
@@ -21,18 +18,14 @@
|
|||||||
"@unocss",
|
"@unocss",
|
||||||
"prettier",
|
"prettier",
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": ["import", "jsdoc", "react-prefer-function-component", "@typescript-eslint", "simple-import-sort"],
|
||||||
"import",
|
|
||||||
"jsdoc",
|
|
||||||
"react-prefer-function-component"
|
|
||||||
],
|
|
||||||
"globals": {
|
"globals": {
|
||||||
"Atomics": "readonly",
|
"Atomics": "readonly",
|
||||||
"SharedArrayBuffer": "readonly",
|
"SharedArrayBuffer": "readonly",
|
||||||
"debugger": true,
|
"debugger": true,
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"context": true,
|
"context": true,
|
||||||
"JSX": true
|
"JSX": true,
|
||||||
},
|
},
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
@@ -42,37 +35,35 @@
|
|||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
"jsx": true,
|
"jsx": true,
|
||||||
"modules": true,
|
"modules": true,
|
||||||
"experimentalObjectRestSpread": true
|
"experimentalObjectRestSpread": true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"react": {
|
"react": {
|
||||||
"version": "detect"
|
"version": "detect",
|
||||||
},
|
},
|
||||||
"jsdoc": {
|
"jsdoc": {
|
||||||
"mode": "typescript"
|
"mode": "typescript",
|
||||||
},
|
},
|
||||||
"import/parsers": {
|
"import/parsers": {
|
||||||
"@typescript-eslint/parser": [
|
"@typescript-eslint/parser": [".ts", ".tsx"],
|
||||||
".ts",
|
|
||||||
".tsx"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"import/resolver": {
|
"import/resolver": {
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"alwaysTryTypes": true,
|
"alwaysTryTypes": true,
|
||||||
"project": "./tsconfig.json"
|
"project": "./tsconfig.json",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"prefer-const": [
|
"prefer-const": [
|
||||||
"off",
|
"off",
|
||||||
{
|
{
|
||||||
"destructuring": "any",
|
"destructuring": "any",
|
||||||
"ignoreReadBeforeAssign": false
|
"ignoreReadBeforeAssign": false,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
|
"no-plusplus": "off",
|
||||||
"no-inner-declarations": "off",
|
"no-inner-declarations": "off",
|
||||||
"sort-imports": "off",
|
"sort-imports": "off",
|
||||||
"no-case-declarations": "off",
|
"no-case-declarations": "off",
|
||||||
@@ -82,20 +73,16 @@
|
|||||||
"no-undef": "off",
|
"no-undef": "off",
|
||||||
"no-return-await": "off",
|
"no-return-await": "off",
|
||||||
"@typescript-eslint/return-await": "off",
|
"@typescript-eslint/return-await": "off",
|
||||||
"@typescript-eslint/no-shadow": [
|
"@typescript-eslint/no-shadow": ["off"],
|
||||||
"off"
|
"@typescript-eslint/no-use-before-define": ["off"],
|
||||||
],
|
|
||||||
"@typescript-eslint/no-use-before-define": [
|
|
||||||
"off"
|
|
||||||
],
|
|
||||||
"class-methods-use-this": "off",
|
"class-methods-use-this": "off",
|
||||||
"react-hooks/exhaustive-deps": "warn",
|
"react-hooks/exhaustive-deps": "warn",
|
||||||
"@typescript-eslint/lines-between-class-members": "off",
|
"@typescript-eslint/lines-between-class-members": "off",
|
||||||
"no-param-reassign": [
|
"no-param-reassign": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
"props": false
|
"props": false,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"no-console": "off",
|
"no-console": "off",
|
||||||
"consistent-return": "off",
|
"consistent-return": "off",
|
||||||
@@ -109,8 +96,8 @@
|
|||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
"before": true,
|
"before": true,
|
||||||
"after": true
|
"after": true,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"no-continue": "off",
|
"no-continue": "off",
|
||||||
"space-before-blocks": [
|
"space-before-blocks": [
|
||||||
@@ -118,24 +105,22 @@
|
|||||||
{
|
{
|
||||||
"functions": "always",
|
"functions": "always",
|
||||||
"keywords": "always",
|
"keywords": "always",
|
||||||
"classes": "always"
|
"classes": "always",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"react/jsx-filename-extension": [
|
"react/jsx-filename-extension": [
|
||||||
1,
|
1,
|
||||||
{
|
{
|
||||||
"extensions": [
|
"extensions": [".tsx"],
|
||||||
".tsx"
|
},
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"react/no-deprecated": "warn",
|
"react/no-deprecated": "warn",
|
||||||
"react/prop-types": "off",
|
"react/prop-types": "off",
|
||||||
"react-prefer-function-component/react-prefer-function-component": [
|
"react-prefer-function-component/react-prefer-function-component": [
|
||||||
"warn",
|
"warn",
|
||||||
{
|
{
|
||||||
"allowComponentDidCatch": false
|
"allowComponentDidCatch": false,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"react/function-component-definition": "off",
|
"react/function-component-definition": "off",
|
||||||
"react/button-has-type": "off",
|
"react/button-has-type": "off",
|
||||||
@@ -153,7 +138,7 @@
|
|||||||
"ArrowFunctionExpression": true,
|
"ArrowFunctionExpression": true,
|
||||||
"ClassDeclaration": true,
|
"ClassDeclaration": true,
|
||||||
"ClassExpression": true,
|
"ClassExpression": true,
|
||||||
"FunctionExpression": true
|
"FunctionExpression": true,
|
||||||
},
|
},
|
||||||
"contexts": [
|
"contexts": [
|
||||||
"MethodDefinition:not([key.name=\"componentDidMount\"]):not([key.name=\"render\"])",
|
"MethodDefinition:not([key.name=\"componentDidMount\"]):not([key.name=\"render\"])",
|
||||||
@@ -168,9 +153,9 @@
|
|||||||
"TSInterfaceDeclaration",
|
"TSInterfaceDeclaration",
|
||||||
"TSMethodSignature",
|
"TSMethodSignature",
|
||||||
"TSModuleDeclaration",
|
"TSModuleDeclaration",
|
||||||
"TSTypeAliasDeclaration"
|
"TSTypeAliasDeclaration",
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"@typescript-eslint/no-unused-vars": "warn",
|
"@typescript-eslint/no-unused-vars": "warn",
|
||||||
@@ -185,31 +170,36 @@
|
|||||||
{
|
{
|
||||||
"target": "./src/background",
|
"target": "./src/background",
|
||||||
"from": "./src/views",
|
"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",
|
"target": "./src/views",
|
||||||
"from": "./src/background",
|
"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",
|
"target": "./src/shared",
|
||||||
"from": "./",
|
"from": "./",
|
||||||
"except": [
|
"except": ["./src/shared", "./node_modules"],
|
||||||
"./src/shared",
|
"message": "You cannot import into `shared` from an external directory.",
|
||||||
"./node_modules"
|
},
|
||||||
],
|
],
|
||||||
"message": "You cannot import into `shared` from an external directory."
|
},
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"import/extensions": "off",
|
"import/extensions": "off",
|
||||||
"no-restricted-syntax": [
|
"no-restricted-syntax": [
|
||||||
"error",
|
"error",
|
||||||
"ForInStatement",
|
"ForInStatement",
|
||||||
"LabeledStatement",
|
"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
43
.github/workflows/best-practices.yml
vendored
Normal 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
|
||||||
6
.github/workflows/chromatic.yml
vendored
6
.github/workflows/chromatic.yml
vendored
@@ -1,6 +1,6 @@
|
|||||||
name: "Chromatic"
|
name: 'Chromatic'
|
||||||
|
|
||||||
on: [push, pull_request_target]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
chromatic:
|
chromatic:
|
||||||
@@ -23,4 +23,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||||
exitZeroOnChanges: true
|
exitZeroOnChanges: true
|
||||||
autoAcceptChanges: "main"
|
autoAcceptChanges: 'main'
|
||||||
|
|||||||
24
.github/workflows/tests.yml
vendored
Normal file
24
.github/workflows/tests.yml
vendored
Normal 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
1
.husky/commit-msg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
npx --no -- commitlint --edit $1
|
||||||
@@ -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=
|
||||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "explicit"
|
||||||
|
},
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
@@ -34,4 +37,7 @@
|
|||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
"tailwindCSS.includeLanguages": {
|
||||||
|
"plaintext": "javascript"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
16
README.md
16
README.md
@@ -14,5 +14,17 @@
|
|||||||
|
|
||||||
1. Clone this repo
|
1. Clone this repo
|
||||||
2. Run `pnpm install` to install and patch all the required dependencies
|
2. Run `pnpm install` to install and patch all the required dependencies
|
||||||
3. Run `pnpm run dev` to start the development server
|
|
||||||
4. Run `pnpm build` to build the extension for production
|
- If you want to run the development build:
|
||||||
|
|
||||||
|
- Run `pnpm run dev`
|
||||||
|
|
||||||
|
- If you want to build the extension for production:
|
||||||
|
|
||||||
|
- Run `pnpm build`
|
||||||
|
|
||||||
|
You may have to rename the `__uno.css.js` to `uno.css.js` in dist
|
||||||
|
|
||||||
|
Go to chrome://extensions, ensure you have "Developer Mode" enabled, and click 'Load unpacked'
|
||||||
|
|
||||||
|
Navigate to the 'dist' folder and click 'select' to import the extension
|
||||||
|
|||||||
123
commitlint.config.ts
Normal file
123
commitlint.config.ts
Normal 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".)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
3267
package-lock.json
generated
3267
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
83
package.json
83
package.json
@@ -9,60 +9,76 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"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",
|
"preview": "vite preview",
|
||||||
"devtools": "react-devtools",
|
"devtools": "react-devtools",
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
"storybook": "storybook dev -p 6006",
|
"storybook": "storybook dev -p 6006",
|
||||||
"build-storybook": "storybook build"
|
"build-storybook": "storybook build",
|
||||||
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@headlessui/react": "^1.7.18",
|
||||||
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
|
"@hello-pangea/dnd": "^16.5.0",
|
||||||
"@types/sql.js": "^1.4.9",
|
"@types/sql.js": "^1.4.9",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"chrome-extension-toolkit": "^0.0.51",
|
"chrome-extension-toolkit": "^0.0.51",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"highcharts": "^11.3.0",
|
"highcharts": "^11.3.0",
|
||||||
"highcharts-react-official": "^3.2.1",
|
"highcharts-react-official": "^3.2.1",
|
||||||
|
"html-to-image": "^1.11.11",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-devtools-core": "^5.0.0",
|
"react-devtools-core": "^5.0.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"sass": "^1.70.0",
|
"sass": "^1.71.1",
|
||||||
"sql.js": "1.10.2",
|
"sql.js": "1.10.2",
|
||||||
|
"styled-components": "^6.1.8",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@commitlint/cli": "^18.6.1",
|
||||||
|
"@commitlint/config-conventional": "^18.6.2",
|
||||||
"@crxjs/vite-plugin": "2.0.0-beta.21",
|
"@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-designs": "^7.0.9",
|
||||||
"@storybook/addon-essentials": "^7.6.12",
|
"@storybook/addon-essentials": "^7.6.17",
|
||||||
"@storybook/addon-links": "^7.6.12",
|
"@storybook/addon-links": "^7.6.17",
|
||||||
"@storybook/blocks": "^7.6.12",
|
"@storybook/blocks": "^7.6.17",
|
||||||
"@storybook/react": "^7.6.12",
|
"@storybook/react": "^7.6.17",
|
||||||
"@storybook/react-vite": "^7.6.12",
|
"@storybook/react-vite": "^7.6.17",
|
||||||
"@storybook/test": "^7.6.12",
|
"@storybook/test": "^7.6.17",
|
||||||
"@svgr/core": "^8.1.0",
|
"@svgr/core": "^8.1.0",
|
||||||
"@svgr/plugin-jsx": "^8.1.0",
|
"@svgr/plugin-jsx": "^8.1.0",
|
||||||
"@types/chrome": "^0.0.260",
|
"@types/chrome": "^0.0.260",
|
||||||
"@types/node": "^20.11.16",
|
"@types/node": "^20.11.19",
|
||||||
"@types/prompts": "^2.4.9",
|
"@types/prompts": "^2.4.9",
|
||||||
"@types/react": "^18.2.51",
|
"@types/react": "^18.2.57",
|
||||||
"@types/react-dom": "^18.2.18",
|
"@types/react-dom": "^18.2.19",
|
||||||
"@types/semver": "^7.5.6",
|
"@types/semver": "^7.5.7",
|
||||||
"@types/uuid": "^9.0.8",
|
"@types/uuid": "^9.0.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||||
"@typescript-eslint/parser": "^6.20.0",
|
"@typescript-eslint/parser": "^6.21.0",
|
||||||
"@unocss/eslint-config": "^0.58.4",
|
"@unocss/eslint-config": "^0.58.5",
|
||||||
"@unocss/postcss": "^0.58.4",
|
"@unocss/postcss": "^0.58.5",
|
||||||
"@unocss/preset-uno": "^0.58.4",
|
"@unocss/preset-uno": "^0.58.5",
|
||||||
"@unocss/preset-web-fonts": "^0.58.4",
|
"@unocss/preset-web-fonts": "^0.58.5",
|
||||||
"@unocss/reset": "^0.58.5",
|
"@unocss/reset": "^0.58.5",
|
||||||
"@unocss/transformer-directives": "^0.58.4",
|
"@unocss/transformer-directives": "^0.58.5",
|
||||||
"@unocss/transformer-variant-group": "^0.58.4",
|
"@unocss/transformer-variant-group": "^0.58.5",
|
||||||
"@vitejs/plugin-react-swc": "^3.6.0",
|
"@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": "^6.0.3",
|
||||||
"cssnano-preset-advanced": "^6.0.3",
|
"cssnano-preset-advanced": "^6.0.3",
|
||||||
"dotenv": "^16.4.1",
|
"dotenv": "^16.4.5",
|
||||||
"es-module-lexer": "^1.4.1",
|
"es-module-lexer": "^1.4.1",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
@@ -71,24 +87,27 @@
|
|||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-jsdoc": "^48.0.4",
|
"eslint-plugin-jsdoc": "^48.1.0",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-react-prefer-function-component": "^3.3.0",
|
"eslint-plugin-react-prefer-function-component": "^3.3.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.5",
|
"eslint-plugin-react-refresh": "^0.4.5",
|
||||||
|
"eslint-plugin-simple-import-sort": "^12.0.0",
|
||||||
"eslint-plugin-storybook": "^0.6.15",
|
"eslint-plugin-storybook": "^0.6.15",
|
||||||
|
"husky": "^9.0.11",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"postcss": "^8.4.33",
|
"postcss": "^8.4.35",
|
||||||
"prettier": "^3.2.4",
|
"prettier": "^3.2.5",
|
||||||
"react-dev-utils": "^12.0.1",
|
"react-dev-utils": "^12.0.1",
|
||||||
"react-devtools": "^5.0.0",
|
"react-devtools": "^5.0.0",
|
||||||
"storybook": "^7.6.12",
|
"storybook": "^7.6.17",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"unocss": "^0.58.4",
|
"unocss": "^0.58.5",
|
||||||
"unplugin-icons": "^0.18.3",
|
"unplugin-icons": "^0.18.5",
|
||||||
"vite": "^5.0.12",
|
"vite": "^5.1.4",
|
||||||
"vite-plugin-inspect": "^0.8.3"
|
"vite-plugin-inspect": "^0.8.3",
|
||||||
|
"vitest": "^1.3.1"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
|
|||||||
4996
pnpm-lock.yaml
generated
4996
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
8
src/assets/icons/cal.svg
Normal file
8
src/assets/icons/cal.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<mask id="mask0_3175_7842" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="25">
|
||||||
|
<rect y="0.5" width="24" height="24" fill="#D9D9D9"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_3175_7842)">
|
||||||
|
<path d="M12 14.5C11.7167 14.5 11.4792 14.4042 11.2875 14.2125C11.0958 14.0208 11 13.7833 11 13.5C11 13.2167 11.0958 12.9792 11.2875 12.7875C11.4792 12.5958 11.7167 12.5 12 12.5C12.2833 12.5 12.5208 12.5958 12.7125 12.7875C12.9042 12.9792 13 13.2167 13 13.5C13 13.7833 12.9042 14.0208 12.7125 14.2125C12.5208 14.4042 12.2833 14.5 12 14.5ZM8 14.5C7.71667 14.5 7.47917 14.4042 7.2875 14.2125C7.09583 14.0208 7 13.7833 7 13.5C7 13.2167 7.09583 12.9792 7.2875 12.7875C7.47917 12.5958 7.71667 12.5 8 12.5C8.28333 12.5 8.52083 12.5958 8.7125 12.7875C8.90417 12.9792 9 13.2167 9 13.5C9 13.7833 8.90417 14.0208 8.7125 14.2125C8.52083 14.4042 8.28333 14.5 8 14.5ZM16 14.5C15.7167 14.5 15.4792 14.4042 15.2875 14.2125C15.0958 14.0208 15 13.7833 15 13.5C15 13.2167 15.0958 12.9792 15.2875 12.7875C15.4792 12.5958 15.7167 12.5 16 12.5C16.2833 12.5 16.5208 12.5958 16.7125 12.7875C16.9042 12.9792 17 13.2167 17 13.5C17 13.7833 16.9042 14.0208 16.7125 14.2125C16.5208 14.4042 16.2833 14.5 16 14.5ZM12 18.5C11.7167 18.5 11.4792 18.4042 11.2875 18.2125C11.0958 18.0208 11 17.7833 11 17.5C11 17.2167 11.0958 16.9792 11.2875 16.7875C11.4792 16.5958 11.7167 16.5 12 16.5C12.2833 16.5 12.5208 16.5958 12.7125 16.7875C12.9042 16.9792 13 17.2167 13 17.5C13 17.7833 12.9042 18.0208 12.7125 18.2125C12.5208 18.4042 12.2833 18.5 12 18.5ZM8 18.5C7.71667 18.5 7.47917 18.4042 7.2875 18.2125C7.09583 18.0208 7 17.7833 7 17.5C7 17.2167 7.09583 16.9792 7.2875 16.7875C7.47917 16.5958 7.71667 16.5 8 16.5C8.28333 16.5 8.52083 16.5958 8.7125 16.7875C8.90417 16.9792 9 17.2167 9 17.5C9 17.7833 8.90417 18.0208 8.7125 18.2125C8.52083 18.4042 8.28333 18.5 8 18.5ZM16 18.5C15.7167 18.5 15.4792 18.4042 15.2875 18.2125C15.0958 18.0208 15 17.7833 15 17.5C15 17.2167 15.0958 16.9792 15.2875 16.7875C15.4792 16.5958 15.7167 16.5 16 16.5C16.2833 16.5 16.5208 16.5958 16.7125 16.7875C16.9042 16.9792 17 17.2167 17 17.5C17 17.7833 16.9042 18.0208 16.7125 18.2125C16.5208 18.4042 16.2833 18.5 16 18.5ZM5 22.5C4.45 22.5 3.97917 22.3042 3.5875 21.9125C3.19583 21.5208 3 21.05 3 20.5V6.5C3 5.95 3.19583 5.47917 3.5875 5.0875C3.97917 4.69583 4.45 4.5 5 4.5H6V2.5H8V4.5H16V2.5H18V4.5H19C19.55 4.5 20.0208 4.69583 20.4125 5.0875C20.8042 5.47917 21 5.95 21 6.5V20.5C21 21.05 20.8042 21.5208 20.4125 21.9125C20.0208 22.3042 19.55 22.5 19 22.5H5ZM5 20.5H19V10.5H5V20.5Z" fill="#333F48"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
8
src/assets/icons/png.svg
Normal file
8
src/assets/icons/png.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<mask id="mask0_3211_5369" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="25">
|
||||||
|
<rect y="0.5" width="24" height="24" fill="#D9D9D9"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_3211_5369)">
|
||||||
|
<path d="M5 21.5C4.45 21.5 3.97917 21.3042 3.5875 20.9125C3.19583 20.5208 3 20.05 3 19.5V5.5C3 4.95 3.19583 4.47917 3.5875 4.0875C3.97917 3.69583 4.45 3.5 5 3.5H19C19.55 3.5 20.0208 3.69583 20.4125 4.0875C20.8042 4.47917 21 4.95 21 5.5V19.5C21 20.05 20.8042 20.5208 20.4125 20.9125C20.0208 21.3042 19.55 21.5 19 21.5H5ZM6 17.5H18L14.25 12.5L11.25 16.5L9 13.5L6 17.5Z" fill="#333F48"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 702 B |
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -1,4 +1,5 @@
|
|||||||
import { defineManifest } from '@crxjs/vite-plugin';
|
import { defineManifest } from '@crxjs/vite-plugin';
|
||||||
|
|
||||||
import packageJson from '../package.json';
|
import packageJson from '../package.json';
|
||||||
|
|
||||||
// Convert from Semver (example: 0.1.0-beta6)
|
// Convert from Semver (example: 0.1.0-beta6)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { BACKGROUND_MESSAGES } from '@shared/messages';
|
import type { BACKGROUND_MESSAGES } from '@shared/messages';
|
||||||
import { MessageListener } from 'chrome-extension-toolkit';
|
import { MessageListener } from 'chrome-extension-toolkit';
|
||||||
|
|
||||||
import onInstall from './events/onInstall';
|
import onInstall from './events/onInstall';
|
||||||
import onServiceWorkerAlive from './events/onServiceWorkerAlive';
|
import onServiceWorkerAlive from './events/onServiceWorkerAlive';
|
||||||
import onUpdate from './events/onUpdate';
|
import onUpdate from './events/onUpdate';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import BrowserActionMessages from '@shared/messages/BrowserActionMessages';
|
import type BrowserActionMessages from '@shared/messages/BrowserActionMessages';
|
||||||
import { MessageHandler } from 'chrome-extension-toolkit';
|
import type { MessageHandler } from 'chrome-extension-toolkit';
|
||||||
|
|
||||||
const browserActionHandler: MessageHandler<BrowserActionMessages> = {
|
const browserActionHandler: MessageHandler<BrowserActionMessages> = {
|
||||||
disableBrowserAction({ sender, sendResponse }) {
|
disableBrowserAction({ sender, sendResponse }) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import HotReloadingMessages from '@shared/messages/HotReloadingMessages';
|
import type HotReloadingMessages from '@shared/messages/HotReloadingMessages';
|
||||||
import { DevStore } from '@shared/storage/DevStore';
|
import { DevStore } from '@shared/storage/DevStore';
|
||||||
import { MessageHandler } from 'chrome-extension-toolkit';
|
import type { MessageHandler } from 'chrome-extension-toolkit';
|
||||||
|
|
||||||
const hotReloadingHandler: MessageHandler<HotReloadingMessages> = {
|
const hotReloadingHandler: MessageHandler<HotReloadingMessages> = {
|
||||||
async reloadExtension({ sendResponse }) {
|
async reloadExtension({ sendResponse }) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import TabManagementMessages from '@shared/messages/TabManagementMessages';
|
import type TabManagementMessages from '@shared/messages/TabManagementMessages';
|
||||||
import { MessageHandler } from 'chrome-extension-toolkit';
|
import type { MessageHandler } from 'chrome-extension-toolkit';
|
||||||
|
|
||||||
import openNewTab from '../util/openNewTab';
|
import openNewTab from '../util/openNewTab';
|
||||||
|
|
||||||
const tabManagementHandler: MessageHandler<TabManagementMessages> = {
|
const tabManagementHandler: MessageHandler<TabManagementMessages> = {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { UserScheduleMessages } from '@shared/messages/UserScheduleMessages';
|
import type { UserScheduleMessages } from '@shared/messages/UserScheduleMessages';
|
||||||
import { Course } from '@shared/types/Course';
|
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 addCourse from '../lib/addCourse';
|
||||||
import clearCourses from '../lib/clearCourses';
|
import clearCourses from '../lib/clearCourses';
|
||||||
import createSchedule from '../lib/createSchedule';
|
import createSchedule from '../lib/createSchedule';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
||||||
import { Course } from '@shared/types/Course';
|
import type { Course } from '@shared/types/Course';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
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> {
|
export default async function clearCourses(scheduleName: string): Promise<void> {
|
||||||
const schedules = await UserScheduleStore.get('schedules');
|
const schedules = await UserScheduleStore.get('schedules');
|
||||||
const schedule = schedules.find(schedule => schedule.name === scheduleName);
|
const schedule = schedules.find(schedule => schedule.name === scheduleName);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export default async function createSchedule(scheduleName: string): Promise<stri
|
|||||||
schedules.push({
|
schedules.push({
|
||||||
name: scheduleName,
|
name: scheduleName,
|
||||||
courses: [],
|
courses: [],
|
||||||
|
hours: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
await UserScheduleStore.set('schedules', schedules);
|
await UserScheduleStore.set('schedules', schedules);
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
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> {
|
export default async function deleteSchedule(scheduleName: string): Promise<string | undefined> {
|
||||||
const [schedules, activeIndex] = await Promise.all([
|
const [schedules, activeIndex] = await Promise.all([
|
||||||
UserScheduleStore.get('schedules'),
|
UserScheduleStore.get('schedules'),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
||||||
import { Course } from '@shared/types/Course';
|
import type { Course } from '@shared/types/Course';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
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> {
|
export default async function renameSchedule(scheduleName: string, newName: string): Promise<string | undefined> {
|
||||||
const schedules = await UserScheduleStore.get('schedules');
|
const schedules = await UserScheduleStore.get('schedules');
|
||||||
const scheduleIndex = schedules.findIndex(schedule => schedule.name === scheduleName);
|
const scheduleIndex = schedules.findIndex(schedule => schedule.name === scheduleName);
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
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> {
|
export default async function switchSchedule(scheduleName: string): Promise<void> {
|
||||||
const schedules = await UserScheduleStore.get('schedules');
|
const schedules = await UserScheduleStore.get('schedules');
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import React from 'react';
|
|
||||||
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
||||||
|
import React from 'react';
|
||||||
|
import { Calendar } from 'src/views/components/calendar/Calendar/Calendar';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calendar page
|
||||||
|
* @returns entire page
|
||||||
|
*/
|
||||||
export default function CalendarMain() {
|
export default function CalendarMain() {
|
||||||
return (
|
return (
|
||||||
<ExtensionRoot>
|
<ExtensionRoot>
|
||||||
<div>Calendar Placeholder</div>
|
<Calendar />
|
||||||
</ExtensionRoot>
|
</ExtensionRoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<head>
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<title>Calendar</title>
|
<title>Calendar</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
||||||
<script src="./index.tsx" type="module"></script>
|
<script src="./index.tsx" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import CalendarMain from './CalendarMain';
|
import CalendarMain from './CalendarMain';
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(<CalendarMain />);
|
createRoot(document.getElementById('root')).render(<CalendarMain />);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
|
||||||
import { createRoot } from 'react-dom/client';
|
|
||||||
import CourseCatalogMain from '@views/components/CourseCatalogMain';
|
import CourseCatalogMain from '@views/components/CourseCatalogMain';
|
||||||
import getSiteSupport, { SiteSupport } from '@views/lib/getSiteSupport';
|
import getSiteSupport, { SiteSupport } from '@views/lib/getSiteSupport';
|
||||||
|
import React from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
const support = getSiteSupport(window.location.href);
|
const support = getSiteSupport(window.location.href);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
|
||||||
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<head>
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<title>Debug</title>
|
<title>Debug</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
||||||
<script src="./index.tsx" type="module"></script>
|
<script src="./index.tsx" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
|
||||||
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<head>
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<title>Popup</title>
|
<title>Popup</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
||||||
<script src="./index.tsx" type="module"></script>
|
<script src="./index.tsx" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(<App />);
|
createRoot(document.getElementById('root')).render(<App />);
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<head>
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<title>Popup</title>
|
<title>Popup</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
||||||
<script src="./index.tsx" type="module"></script>
|
<script src="./index.tsx" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import PopupMain from '../../views/components/PopupMain';
|
import PopupMain from '../../views/components/PopupMain';
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(<PopupMain />);
|
createRoot(document.getElementById('root')).render(<PopupMain />);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable jsdoc/require-jsdoc */
|
||||||
export default interface BrowserActionMessages {
|
export default interface BrowserActionMessages {
|
||||||
/** make it so that clicking the browser action will open the popup.html */
|
/** make it so that clicking the browser action will open the popup.html */
|
||||||
enableBrowserAction: () => void;
|
enableBrowserAction: () => void;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable jsdoc/require-jsdoc */
|
||||||
export default interface HotReloadingMessages {
|
export default interface HotReloadingMessages {
|
||||||
reloadExtension: () => void;
|
reloadExtension: () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
export interface UserScheduleMessages {
|
||||||
/**
|
/**
|
||||||
* Add a course to a schedule
|
* Add a course to a schedule
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createMessenger } from 'chrome-extension-toolkit';
|
import { createMessenger } from 'chrome-extension-toolkit';
|
||||||
import BrowserActionMessages from './BrowserActionMessages';
|
|
||||||
import TabManagementMessages from './TabManagementMessages';
|
import type BrowserActionMessages from './BrowserActionMessages';
|
||||||
import TAB_MESSAGES from './TabMessages';
|
import type TabManagementMessages from './TabManagementMessages';
|
||||||
import { UserScheduleMessages } from './UserScheduleMessages';
|
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
|
* This is a type with all the message definitions that can be sent TO the background script
|
||||||
|
|||||||
@@ -15,6 +15,4 @@ export const ExtensionStore = createLocalStore<IExtensionStore>({
|
|||||||
lastUpdate: Date.now(),
|
lastUpdate: Date.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
debugStore({ ExtensionStore });
|
debugStore({ ExtensionStore });
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const UserScheduleStore = createLocalStore<IUserScheduleStore>({
|
|||||||
new UserSchedule({
|
new UserSchedule({
|
||||||
courses: [],
|
courses: [],
|
||||||
name: 'Schedule 1',
|
name: 'Schedule 1',
|
||||||
|
hours: 0,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
activeIndex: 0,
|
activeIndex: 0,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable max-classes-per-file */
|
import type { Serialized } from 'chrome-extension-toolkit';
|
||||||
import { Serialized } from 'chrome-extension-toolkit';
|
|
||||||
import { CourseMeeting } from './CourseMeeting';
|
import type { CourseMeeting } from './CourseMeeting';
|
||||||
import { CourseSchedule } from './CourseSchedule';
|
import { CourseSchedule } from './CourseSchedule';
|
||||||
import Instructor from './Instructor';
|
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)
|
* The status of a course (e.g. open, closed, waitlisted, cancelled)
|
||||||
*/
|
*/
|
||||||
export enum Status {
|
export const Status = {
|
||||||
OPEN = 'OPEN',
|
OPEN: 'OPEN',
|
||||||
CLOSED = 'CLOSED',
|
CLOSED: 'CLOSED',
|
||||||
WAITLISTED = 'WAITLISTED',
|
WAITLISTED: 'WAITLISTED',
|
||||||
CANCELLED = 'CANCELLED',
|
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
|
* 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 */
|
/** The number of credits that a course is worth */
|
||||||
creditHours: number;
|
creditHours: number;
|
||||||
/** Is the course open, closed, waitlisted, or cancelled? */
|
/** 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 */
|
/** all the people that are teaching this course, and some metadata about their names */
|
||||||
instructors: Instructor[];
|
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. */
|
/** 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. */
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
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
|
* a map of the days of the week that a class is taught, and the corresponding abbreviation
|
||||||
|
* Don't modify the keys
|
||||||
*/
|
*/
|
||||||
export const DAY_MAP = {
|
export const DAY_MAP = {
|
||||||
M: 'Monday',
|
M: 'Monday',
|
||||||
@@ -14,7 +15,7 @@ export const DAY_MAP = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/** A day of the week that a class is taught */
|
/** A day of the week that a class is taught */
|
||||||
export type Day = typeof DAY_MAP[keyof typeof DAY_MAP];
|
export type Day = (typeof DAY_MAP)[keyof typeof DAY_MAP];
|
||||||
|
|
||||||
/** A physical room that a class is taught in */
|
/** A physical room that a class is taught in */
|
||||||
export type Location = {
|
export type Location = {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Serialized } from 'chrome-extension-toolkit';
|
import type { Serialized } from 'chrome-extension-toolkit';
|
||||||
import { CourseMeeting, Day, DAY_MAP } from './CourseMeeting';
|
|
||||||
|
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
|
* 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
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable max-classes-per-file */
|
|
||||||
import { PointOptionsObject } from 'highcharts';
|
import { PointOptionsObject } from 'highcharts';
|
||||||
|
|
||||||
import { Semester } from './Course';
|
import { Semester } from './Course';
|
||||||
/**
|
/**
|
||||||
* Each of the possible letter grades that can be given in a course
|
* Each of the possible letter grades that can be given in a course
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Serialized } from 'chrome-extension-toolkit';
|
import type { Serialized } from 'chrome-extension-toolkit';
|
||||||
|
|
||||||
import { capitalize } from '../util/string';
|
import { capitalize } from '../util/string';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Serialized } from 'chrome-extension-toolkit';
|
import type { Serialized } from 'chrome-extension-toolkit';
|
||||||
|
|
||||||
import { Course } from './Course';
|
import { Course } from './Course';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -7,10 +8,15 @@ import { Course } from './Course';
|
|||||||
export class UserSchedule {
|
export class UserSchedule {
|
||||||
courses: Course[];
|
courses: Course[];
|
||||||
name: string;
|
name: string;
|
||||||
|
hours: number;
|
||||||
|
|
||||||
constructor(schedule: Serialized<UserSchedule>) {
|
constructor(schedule: Serialized<UserSchedule>) {
|
||||||
this.courses = schedule.courses.map(c => new Course(c));
|
this.courses = schedule.courses.map(c => new Course(c));
|
||||||
this.name = schedule.name;
|
this.name = schedule.name;
|
||||||
|
this.hours = 0;
|
||||||
|
for (const course of this.courses) {
|
||||||
|
this.hours += course.creditHours;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
containsCourse(course: Course): boolean {
|
containsCourse(course: Course): boolean {
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import { theme } from 'unocss/preset-mini';
|
import { theme } from 'unocss/preset-mini';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the colors for a course.
|
||||||
|
*/
|
||||||
export interface CourseColors {
|
export interface CourseColors {
|
||||||
primaryColor: string;
|
primaryColor: string;
|
||||||
secondaryColor: 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 r = parseInt(hex.substring(1, 3), 16);
|
||||||
let g = parseInt(hex.substring(3, 5), 16);
|
let g = parseInt(hex.substring(3, 5), 16);
|
||||||
let b = parseInt(hex.substring(5, 7), 16);
|
let b = parseInt(hex.substring(5, 7), 16);
|
||||||
@@ -42,9 +50,9 @@ export function pickFontColor(bgColor: string): 'text-white' | 'text-black' {
|
|||||||
* Get primary and secondary colors from a tailwind colorway
|
* Get primary and secondary colors from a tailwind colorway
|
||||||
* @param colorway the tailwind colorway ex. "emerald"
|
* @param colorway the tailwind colorway ex. "emerald"
|
||||||
*/
|
*/
|
||||||
export function getCourseColors(colorway: keyof typeof theme.colors): CourseColors {
|
export function getCourseColors(colorway: keyof typeof theme.colors, index = 600, offset = 200): CourseColors {
|
||||||
return {
|
return {
|
||||||
primaryColor: theme.colors[colorway][600] as string,
|
primaryColor: theme.colors[colorway][index] as string,
|
||||||
secondaryColor: theme.colors[colorway][800] as string,
|
secondaryColor: theme.colors[colorway][index + offset] as string,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ClosedIcon from '~icons/material-symbols/lock';
|
||||||
import WaitlistIcon from '~icons/material-symbols/timelapse';
|
import WaitlistIcon from '~icons/material-symbols/timelapse';
|
||||||
import CancelledIcon from '~icons/material-symbols/warning';
|
import CancelledIcon from '~icons/material-symbols/warning';
|
||||||
import { Status } from '../types/Course';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Icon component based on status
|
* Get Icon component based on status
|
||||||
* @param props.status status
|
* @param props.status status
|
||||||
* @returns React.ReactElement - the icon component
|
* @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;
|
const { status, ...rest } = props;
|
||||||
|
|
||||||
switch (props.status) {
|
switch (props.status) {
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export function capitalize(input: string): string {
|
|||||||
}
|
}
|
||||||
capitalized += ' ';
|
capitalized += ' ';
|
||||||
}
|
}
|
||||||
|
capitalized = capitalized.trim(); // Remove extra space
|
||||||
|
|
||||||
return capitalized;
|
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 input The string to ellipsify.
|
||||||
* @param length The length of the string to return.
|
* @param length The length of the string to return.
|
||||||
* @returns The ellipsified string.
|
* @returns The ellipsified string.
|
||||||
|
|||||||
22
src/shared/util/tests/colors.test.ts
Normal file
22
src/shared/util/tests/colors.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
26
src/shared/util/tests/random.test.ts
Normal file
26
src/shared/util/tests/random.test.ts
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
64
src/shared/util/tests/string.test.ts
Normal file
64
src/shared/util/tests/string.test.ts
Normal 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('');
|
||||||
|
});
|
||||||
|
});
|
||||||
51
src/shared/util/tests/themeColors.test.ts
Normal file
51
src/shared/util/tests/themeColors.test.ts
Normal 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
|
||||||
|
});
|
||||||
|
});
|
||||||
14
src/shared/util/tests/time.test.ts
Normal file
14
src/shared/util/tests/time.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
88
src/shared/util/themeColors.ts
Normal file
88
src/shared/util/themeColors.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
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',
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
}[keyof T];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A union of all colors in the theme
|
||||||
|
*/
|
||||||
|
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)) {
|
||||||
|
acc[`${prefix}-${name}`] = hex;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<ThemeColor, 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>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the hexadecimal color value by name from the theme.
|
||||||
|
*
|
||||||
|
* @param name - The name of the theme color.
|
||||||
|
* @returns The hexadecimal color value.
|
||||||
|
*/
|
||||||
|
export const getThemeColorHexByName = (name: ThemeColor): string => colorsFlattened[name];
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param name - The name of the theme color.
|
||||||
|
* @returns An array of the red, green, and blue values, respectively
|
||||||
|
*/
|
||||||
|
export const getThemeColorRgbByName = (name: ThemeColor) => colorsFlattenedRgb[name];
|
||||||
@@ -5,7 +5,9 @@ export const HOUR = 60 * MINUTE;
|
|||||||
export const DAY = 24 * HOUR;
|
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));
|
export const sleep = (milliseconds: number): Promise<void> => new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import { Button } from 'src/views/components/common/Button/Button';
|
import { colorsFlattened } from '@shared/util/themeColors';
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { Button } from '@views/components/common/Button/Button';
|
||||||
import React from 'react';
|
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 ImagePlaceholderIcon from '~icons/material-symbols/image';
|
||||||
|
import HappyFaceIcon from '~icons/material-symbols/mood';
|
||||||
|
import RemoveIcon from '~icons/material-symbols/remove';
|
||||||
|
import ReviewsIcon from '~icons/material-symbols/reviews';
|
||||||
|
|
||||||
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
||||||
const meta = {
|
const meta = {
|
||||||
title: 'Components/Common/Button',
|
title: 'Components/Common/Button',
|
||||||
@@ -15,6 +24,7 @@ const meta = {
|
|||||||
// More on argTypes: https://storybook.js.org/docs/api/argtypes
|
// More on argTypes: https://storybook.js.org/docs/api/argtypes
|
||||||
args: {
|
args: {
|
||||||
children: 'Button',
|
children: 'Button',
|
||||||
|
icon: ImagePlaceholderIcon,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
children: { control: 'text' },
|
children: { control: 'text' },
|
||||||
@@ -26,66 +36,82 @@ type Story = StoryObj<typeof meta>;
|
|||||||
|
|
||||||
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
|
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {},
|
args: {
|
||||||
|
variant: 'filled',
|
||||||
|
color: 'ut-black',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Disabled: Story = {
|
export const Disabled: Story = {
|
||||||
args: {
|
args: {
|
||||||
|
variant: 'filled',
|
||||||
|
color: 'ut-black',
|
||||||
disabled: true,
|
disabled: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
export const Grid: Story = {
|
export const Grid: Story = {
|
||||||
render: props => (
|
render: props => (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
|
||||||
<div style={{ display: 'flex' }}>
|
<div style={{ display: 'flex', gap: '15px' }}>
|
||||||
<Button {...props} type='primary' />
|
<Button {...props} variant='filled' color='ut-black' />
|
||||||
<Button {...props} type='secondary' />
|
<Button {...props} variant='outline' color='ut-black' />
|
||||||
<Button {...props} type='tertiary' />
|
<Button {...props} variant='single' color='ut-black' />
|
||||||
<Button {...props} type='danger' />
|
|
||||||
<Button {...props} type='warning' />
|
|
||||||
<Button {...props} type='success' />
|
|
||||||
<Button {...props} type='info' />
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex' }}>
|
|
||||||
<Button {...props} type='primary' disabled />
|
<hr />
|
||||||
<Button {...props} type='secondary' disabled />
|
|
||||||
<Button {...props} type='tertiary' disabled />
|
<div style={{ display: 'flex', gap: '15px' }}>
|
||||||
<Button {...props} type='danger' disabled />
|
<Button {...props} variant='filled' color='ut-black' disabled />
|
||||||
<Button {...props} type='warning' disabled />
|
<Button {...props} variant='outline' color='ut-black' disabled />
|
||||||
<Button {...props} type='success' disabled />
|
<Button {...props} variant='single' color='ut-black' disabled />
|
||||||
<Button {...props} type='info' disabled />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PrettyColors: Story = {
|
||||||
// TODO: Actually show the buttons as they appear in the design
|
// @ts-ignore
|
||||||
export const CourseButtons: Story = {
|
|
||||||
args: {
|
args: {
|
||||||
children: 'Add Course',
|
children: '',
|
||||||
},
|
},
|
||||||
|
render: props => {
|
||||||
|
const colorsNames = Object.keys(colorsFlattened) as (keyof typeof colorsFlattened)[];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
|
||||||
|
{colorsNames.map(color => (
|
||||||
|
<div style={{ display: 'flex', gap: '15px' }} key={color}>
|
||||||
|
<Button {...props} variant='filled' color={color}>
|
||||||
|
Button
|
||||||
|
</Button>
|
||||||
|
<Button {...props} variant='outline' color={color}>
|
||||||
|
Button
|
||||||
|
</Button>
|
||||||
|
<Button {...props} variant='single' color={color}>
|
||||||
|
Button
|
||||||
|
</Button>
|
||||||
|
<Button {...props} variant='filled' color={color} />
|
||||||
|
<Button {...props} variant='outline' color={color} />
|
||||||
|
<Button {...props} variant='single' color={color} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
export const CourseButtons: Story = {
|
||||||
render: props => (
|
render: props => (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px', alignItems: 'center' }}>
|
||||||
<div style={{ display: 'flex' }}>
|
<Button {...props} variant='filled' color='ut-green' icon={AddIcon}>
|
||||||
<Button {...props} type='primary' />
|
Add Course
|
||||||
<Button {...props} type='secondary' />
|
</Button>
|
||||||
<Button {...props} type='tertiary' />
|
<Button {...props} variant='filled' color='theme-red' icon={RemoveIcon}>
|
||||||
<Button {...props} type='danger' />
|
Remove Course
|
||||||
<Button {...props} type='warning' />
|
</Button>
|
||||||
<Button {...props} type='success' />
|
|
||||||
<Button {...props} type='info' />
|
|
||||||
</div>
|
|
||||||
<div style={{ display: 'flex' }}>
|
|
||||||
<Button {...props} type='primary' disabled />
|
|
||||||
<Button {...props} type='secondary' disabled />
|
|
||||||
<Button {...props} type='tertiary' disabled />
|
|
||||||
<Button {...props} type='danger' disabled />
|
|
||||||
<Button {...props} type='warning' disabled />
|
|
||||||
<Button {...props} type='success' disabled />
|
|
||||||
<Button {...props} type='info' disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
parameters: {
|
parameters: {
|
||||||
@@ -95,3 +121,27 @@ export const CourseButtons: Story = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CourseCatalogActionButtons: Story = {
|
||||||
|
// @ts-ignore
|
||||||
|
args: {
|
||||||
|
children: '',
|
||||||
|
},
|
||||||
|
render: props => (
|
||||||
|
<div style={{ display: 'flex', gap: '15px' }}>
|
||||||
|
<Button {...props} variant='filled' color='ut-burntorange' icon={CalendarMonthIcon} />
|
||||||
|
<Button {...props} variant='outline' color='ut-blue' icon={ReviewsIcon}>
|
||||||
|
RateMyProf
|
||||||
|
</Button>
|
||||||
|
<Button {...props} variant='outline' color='ut-teal' icon={HappyFaceIcon}>
|
||||||
|
CES
|
||||||
|
</Button>
|
||||||
|
<Button {...props} variant='outline' color='ut-orange' icon={DescriptionIcon}>
|
||||||
|
Past Syllabi
|
||||||
|
</Button>
|
||||||
|
<Button {...props} variant='filled' color='ut-green' icon={AddIcon}>
|
||||||
|
Add Course
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
// Calendar.stories.tsx
|
|
||||||
import React from 'react';
|
|
||||||
import Calendar from '@views/components/common/CalendarGrid/CalendarGrid';
|
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
title: 'Components/Common/Calendar',
|
|
||||||
component: Calendar,
|
|
||||||
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 Calendar>;
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof meta>;
|
|
||||||
|
|
||||||
export const Default: Story = {};
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import Card from 'src/views/components/common/Card/Card';
|
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import Card from '@views/components/common/Card/Card';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
||||||
|
|||||||
23
src/stories/components/Chip.stories.tsx
Normal file
23
src/stories/components/Chip.stories.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { Chip } from '@views/components/common/Chip/Chip';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Common/Chip',
|
||||||
|
component: Chip,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
label: { control: 'text' },
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof Chip>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
label: 'QR',
|
||||||
|
},
|
||||||
|
};
|
||||||
117
src/stories/components/ConflictsWithWarning.stories.tsx
Normal file
117
src/stories/components/ConflictsWithWarning.stories.tsx
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
export const ExampleCourse: Course = new Course({
|
||||||
|
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
||||||
|
creditHours: 3,
|
||||||
|
department: 'C S',
|
||||||
|
description: [
|
||||||
|
'Problem solving and fundamental algorithms for various applications in science, business, and on the World Wide Web, and introductory programming in a modern object-oriented programming language.',
|
||||||
|
'Only one of the following may be counted: Computer Science 303E, 312, 312H. Credit for Computer Science 303E may not be earned after a student has received credit for Computer Science 314, or 314H. May not be counted toward a degree in computer science.',
|
||||||
|
'May be counted toward the Quantitative Reasoning flag requirement.',
|
||||||
|
'Designed to accommodate 100 or more students.',
|
||||||
|
'Taught as a Web-based course.',
|
||||||
|
],
|
||||||
|
flags: ['Quantitative Reasoning'],
|
||||||
|
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
||||||
|
instructionMode: 'Online',
|
||||||
|
instructors: [
|
||||||
|
new Instructor({
|
||||||
|
firstName: 'Bevo',
|
||||||
|
lastName: 'Bevo',
|
||||||
|
fullName: 'Bevo Bevo',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
isReserved: false,
|
||||||
|
number: '303E',
|
||||||
|
schedule: {
|
||||||
|
meetings: [
|
||||||
|
new CourseMeeting({
|
||||||
|
days: ['Tuesday', 'Thursday'],
|
||||||
|
endTime: 660,
|
||||||
|
startTime: 570,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
semester: {
|
||||||
|
code: '12345',
|
||||||
|
season: 'Spring',
|
||||||
|
year: 2024,
|
||||||
|
},
|
||||||
|
status: Status.WAITLISTED,
|
||||||
|
uniqueId: 12345,
|
||||||
|
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
|
||||||
|
});
|
||||||
|
export const ExampleCourse2: 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/',
|
||||||
|
});
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Common/ConflictsWithWarning',
|
||||||
|
component: ConflictsWithWarning,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
conflicts: { control: 'object' },
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
conflicts: [ExampleCourse, ExampleCourse2],
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof ConflictsWithWarning>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
conflicts: [ExampleCourse, ExampleCourse2],
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { Status } from '@shared/types/Course';
|
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 CourseStatus from '@views/components/common/CourseStatus/CourseStatus';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: 'Components/Common/CourseStatus',
|
title: 'Components/Common/CourseStatus',
|
||||||
@@ -40,7 +39,7 @@ export const Default: Story = {};
|
|||||||
|
|
||||||
export const Variants: Story = {
|
export const Variants: Story = {
|
||||||
render: args => (
|
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='small' />
|
||||||
<CourseStatus {...args} size='mini' />
|
<CourseStatus {...args} size='mini' />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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 = {};
|
|
||||||
78
src/stories/components/Divider.stories.tsx
Normal file
78
src/stories/components/Divider.stories.tsx
Normal 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>
|
||||||
|
),
|
||||||
|
};
|
||||||
157
src/stories/components/Dropdown.stories.tsx
Normal file
157
src/stories/components/Dropdown.stories.tsx
Normal 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} />
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
};
|
||||||
19
src/stories/components/ImportantLinks.stories.tsx
Normal file
19
src/stories/components/ImportantLinks.stories.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import ImportantLinks from '@views/components/calendar/ImportantLinks';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Common/ImportantLinks',
|
||||||
|
component: ImportantLinks,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {},
|
||||||
|
} satisfies Meta<typeof ImportantLinks>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {},
|
||||||
|
};
|
||||||
25
src/stories/components/InfoCard.stories.tsx
Normal file
25
src/stories/components/InfoCard.stories.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { InfoCard } from '@views/components/common/InfoCard/InfoCard';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Common/InfoCard',
|
||||||
|
component: InfoCard,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
titleText: { control: 'text' },
|
||||||
|
bodyText: { control: 'text' },
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof InfoCard>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
titleText: 'WAITLIST SIZE',
|
||||||
|
bodyText: '14 Students',
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import Link from 'src/views/components/common/Link/Link';
|
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import Link from '@views/components/common/Link/Link';
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: 'Components/Common/Link',
|
title: 'Components/Common/Link',
|
||||||
|
|||||||
103
src/stories/components/List.stories.tsx
Normal file
103
src/stories/components/List.stories.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
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 List from '@views/components/common/List/List';
|
||||||
|
import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { TestColors } from './PopupCourseBlock.stories';
|
||||||
|
|
||||||
|
const numberOfCourses = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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++) {
|
||||||
|
const course = new Course({
|
||||||
|
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
||||||
|
creditHours: 3,
|
||||||
|
department: 'C S',
|
||||||
|
description: [
|
||||||
|
'Problem solving and fundamental algorithms for various applications in science, business, and on the World Wide Web, and introductory programming in a modern object-oriented programming language.',
|
||||||
|
'Only one of the following may be counted: Computer Science 303E, 312, 312H. Credit for Computer Science 303E may not be earned after a student has received credit for Computer Science 314, or 314H. May not be counted toward a degree in computer science.',
|
||||||
|
'May be counted toward the Quantitative Reasoning flag requirement.',
|
||||||
|
'Designed to accommodate 100 or more students.',
|
||||||
|
'Taught as a Web-based course.',
|
||||||
|
],
|
||||||
|
flags: ['Quantitative Reasoning'],
|
||||||
|
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
||||||
|
instructionMode: 'Online',
|
||||||
|
instructors: [
|
||||||
|
new Instructor({
|
||||||
|
firstName: 'Bevo',
|
||||||
|
lastName: 'Bevo',
|
||||||
|
fullName: 'Bevo Bevo',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
isReserved: false,
|
||||||
|
number: '303E',
|
||||||
|
schedule: {
|
||||||
|
meetings: [
|
||||||
|
new CourseMeeting({
|
||||||
|
days: ['Tuesday', 'Thursday'],
|
||||||
|
endTime: 660,
|
||||||
|
startTime: 570,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
semester: {
|
||||||
|
code: '12345',
|
||||||
|
season: 'Spring',
|
||||||
|
year: 2024,
|
||||||
|
},
|
||||||
|
status: Status.WAITLISTED,
|
||||||
|
uniqueId: 12345 + i, // Make uniqueId different for each course
|
||||||
|
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
|
||||||
|
});
|
||||||
|
|
||||||
|
courses.push(course);
|
||||||
|
}
|
||||||
|
|
||||||
|
return courses;
|
||||||
|
};
|
||||||
|
|
||||||
|
const exampleCourses = GenerateCourses(numberOfCourses);
|
||||||
|
const generateCourseBlocks = (exampleCourses, colors) =>
|
||||||
|
exampleCourses.map((course, i) => <PopupCourseBlock key={course.uniqueId} course={course} colors={colors[i]} />);
|
||||||
|
export const ExampleCourseBlocks = generateCourseBlocks(exampleCourses, TestColors);
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Common/List',
|
||||||
|
component: List,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
draggableElements: { control: 'object' },
|
||||||
|
itemHeight: { control: 'number' },
|
||||||
|
listHeight: { control: 'number' },
|
||||||
|
listWidth: { control: 'number' },
|
||||||
|
gap: { control: 'number' },
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof List>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
draggableElements: ExampleCourseBlocks,
|
||||||
|
itemHeight: 55,
|
||||||
|
listHeight: 300,
|
||||||
|
listWidth: 300,
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -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 type { Meta, StoryObj } from '@storybook/react';
|
||||||
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 PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock';
|
import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock';
|
||||||
import { getCourseColors } from 'src/shared/util/colors';
|
import React from 'react';
|
||||||
import { theme } from 'unocss/preset-mini';
|
import { theme } from 'unocss/preset-mini';
|
||||||
|
|
||||||
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',
|
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
||||||
creditHours: 3,
|
creditHours: 3,
|
||||||
department: 'C S',
|
department: 'C S',
|
||||||
@@ -62,7 +68,7 @@ const meta = {
|
|||||||
// More on argTypes: https://storybook.js.org/docs/api/argtypes
|
// More on argTypes: https://storybook.js.org/docs/api/argtypes
|
||||||
args: {
|
args: {
|
||||||
colors: getCourseColors('emerald'),
|
colors: getCourseColors('emerald'),
|
||||||
course: exampleCourse,
|
course: ExampleCourse,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
colors: {
|
colors: {
|
||||||
@@ -87,15 +93,15 @@ export const Default: Story = {
|
|||||||
export const Variants: Story = {
|
export const Variants: Story = {
|
||||||
render: props => (
|
render: props => (
|
||||||
<div className='grid grid-cols-2 max-w-2xl w-90vw gap-x-4 gap-y-2'>
|
<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.OPEN })} />
|
||||||
<PopupCourseBlock {...props} course={new Course({ ...exampleCourse, status: Status.CLOSED })} />
|
<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.WAITLISTED })} />
|
||||||
<PopupCourseBlock {...props} course={new Course({ ...exampleCourse, status: Status.CANCELLED })} />
|
<PopupCourseBlock {...props} course={new Course({ ...ExampleCourse, status: Status.CANCELLED })} />
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const colors = Object.keys(theme.colors)
|
export const TestColors = Object.keys(theme.colors)
|
||||||
// check that the color is a colorway (is an object)
|
// check that the color is a colorway (is an object)
|
||||||
.filter(color => typeof theme.colors[color] === 'object')
|
.filter(color => typeof theme.colors[color] === 'object')
|
||||||
.slice(0, 17)
|
.slice(0, 17)
|
||||||
@@ -103,9 +109,9 @@ const colors = Object.keys(theme.colors)
|
|||||||
|
|
||||||
export const AllColors: Story = {
|
export const AllColors: Story = {
|
||||||
render: props => (
|
render: props => (
|
||||||
<div className='grid grid-rows-9 grid-cols-2 grid-flow-col max-w-2xl w-90vw gap-x-4 gap-y-2'>
|
<div className='grid grid-flow-col grid-cols-2 grid-rows-9 max-w-2xl w-90vw gap-x-4 gap-y-2'>
|
||||||
{colors.map((color, i) => (
|
{TestColors.map((color, i) => (
|
||||||
<PopupCourseBlock key={color.primaryColor} course={exampleCourse} colors={color} />
|
<PopupCourseBlock key={color.primaryColor} course={ExampleCourse} colors={color} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
|||||||
19
src/stories/components/PopupMain.stories.tsx
Normal file
19
src/stories/components/PopupMain.stories.tsx
Normal 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: {},
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import ScheduleTotalHoursAndCourses from '@views/components/common/ScheduleTotalHoursAndCourses/ScheduleTotalHoursAndCourses';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Common/ScheduleTotalHoursAndCourses',
|
||||||
|
component: ScheduleTotalHoursAndCourses,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
scheduleName: { control: 'text' },
|
||||||
|
totalHours: { control: 'number' },
|
||||||
|
totalCourses: { control: 'number' },
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof ScheduleTotalHoursAndCourses>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
scheduleName: 'SCHEDULE',
|
||||||
|
totalHours: 22,
|
||||||
|
totalCourses: 8,
|
||||||
|
},
|
||||||
|
};
|
||||||
37
src/stories/components/ScheduleListItem.stories.tsx
Normal file
37
src/stories/components/ScheduleListItem.stories.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/* eslint-disable jsdoc/require-jsdoc */
|
||||||
|
import ScheduleListItem from '@views/components/common/ScheduleListItem/ScheduleListItem';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/Common/ScheduleListItem',
|
||||||
|
component: ScheduleListItem,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
tags: ['autodocs'],
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
active: { control: 'boolean' },
|
||||||
|
name: { control: 'text' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Default = args => <ScheduleListItem {...args} />;
|
||||||
|
|
||||||
|
Default.args = {
|
||||||
|
name: 'My Schedule',
|
||||||
|
active: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Active = args => <ScheduleListItem {...args} />;
|
||||||
|
|
||||||
|
Active.args = {
|
||||||
|
name: 'My Schedule',
|
||||||
|
active: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Inactive = args => <ScheduleListItem {...args} />;
|
||||||
|
|
||||||
|
Inactive.args = {
|
||||||
|
name: 'My Schedule',
|
||||||
|
active: false,
|
||||||
|
};
|
||||||
19
src/stories/components/Settings.stories.tsx
Normal file
19
src/stories/components/Settings.stories.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import Settings from '@views/components/Settings';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Common/Settings',
|
||||||
|
component: Settings,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {},
|
||||||
|
} satisfies Meta<typeof Settings>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {},
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import Spinner from 'src/views/components/common/Spinner/Spinner';
|
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import Spinner from '@views/components/common/Spinner/Spinner';
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: 'Components/Common/Spinner',
|
title: 'Components/Common/Spinner',
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Button } from 'src/views/components/common/Button/Button';
|
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import Text from '@views/components/common/Text/Text';
|
||||||
import React from 'react';
|
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
|
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
19
src/stories/components/calendar/Calendar.stories.tsx
Normal file
19
src/stories/components/calendar/Calendar.stories.tsx
Normal 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: {},
|
||||||
|
};
|
||||||
101
src/stories/components/calendar/CalendarBottomBar.stories.tsx
Normal file
101
src/stories/components/calendar/CalendarBottomBar.stories.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import { Course, Status } from '@shared/types/Course';
|
||||||
|
import Instructor from '@shared/types/Instructor';
|
||||||
|
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',
|
||||||
|
creditHours: 3,
|
||||||
|
department: 'GOV',
|
||||||
|
description: ['nah', 'aint typing this', 'corndog'],
|
||||||
|
flags: ['no flag for you >:)'],
|
||||||
|
fullName: 'GOV 312L Something something',
|
||||||
|
instructionMode: 'Online',
|
||||||
|
instructors: [
|
||||||
|
new Instructor({
|
||||||
|
firstName: 'Bevo',
|
||||||
|
lastName: 'Barrymore',
|
||||||
|
fullName: 'Bevo Barrymore',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
isReserved: false,
|
||||||
|
number: '312L',
|
||||||
|
schedule: {
|
||||||
|
meetings: [],
|
||||||
|
},
|
||||||
|
semester: {
|
||||||
|
code: '12345',
|
||||||
|
season: 'Spring',
|
||||||
|
year: 2024,
|
||||||
|
},
|
||||||
|
status: Status.OPEN,
|
||||||
|
uniqueId: 12345,
|
||||||
|
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
|
||||||
|
});
|
||||||
|
|
||||||
|
const examplePsyCourse: Course = new Course({
|
||||||
|
courseName: 'Nope Again',
|
||||||
|
creditHours: 3,
|
||||||
|
department: 'PSY',
|
||||||
|
description: ['nah', 'aint typing this', 'corndog'],
|
||||||
|
flags: ['no flag for you >:)'],
|
||||||
|
fullName: 'PSY 317L Yada yada',
|
||||||
|
instructionMode: 'Online',
|
||||||
|
instructors: [
|
||||||
|
new Instructor({
|
||||||
|
firstName: 'Bevo',
|
||||||
|
lastName: 'Etz',
|
||||||
|
fullName: 'Bevo Etz',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
isReserved: false,
|
||||||
|
number: '317L',
|
||||||
|
schedule: {
|
||||||
|
meetings: [],
|
||||||
|
},
|
||||||
|
semester: {
|
||||||
|
code: '12346',
|
||||||
|
season: 'Spring',
|
||||||
|
year: 2024,
|
||||||
|
},
|
||||||
|
status: Status.CLOSED,
|
||||||
|
uniqueId: 12346,
|
||||||
|
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
|
||||||
|
});
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Calendar/CalendarBottomBar',
|
||||||
|
component: CalendarBottomBar,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {},
|
||||||
|
} satisfies Meta<typeof CalendarBottomBar>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
courses: [
|
||||||
|
{
|
||||||
|
colors: getCourseColors('pink', 200),
|
||||||
|
courseDeptAndInstr: `${exampleGovCourse.department} ${exampleGovCourse.number} – ${exampleGovCourse.instructors[0].lastName}`,
|
||||||
|
status: exampleGovCourse.status,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
colors: getCourseColors('slate', 500),
|
||||||
|
courseDeptAndInstr: `${examplePsyCourse.department} ${examplePsyCourse.number} – ${examplePsyCourse.instructors[0].lastName}`,
|
||||||
|
status: examplePsyCourse.status,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
render: props => (
|
||||||
|
<div className='outline-red outline w-292.5!'>
|
||||||
|
<CalendarBottomBar {...props} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Course, Status } from '@shared/types/Course';
|
||||||
import React from 'react';
|
import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting';
|
||||||
import { Course, Status } from 'src/shared/types/Course';
|
import { CourseSchedule } from '@shared/types/CourseSchedule';
|
||||||
import { CourseMeeting, DAY_MAP } from 'src/shared/types/CourseMeeting';
|
import Instructor from '@shared/types/Instructor';
|
||||||
import { CourseSchedule } from 'src/shared/types/CourseSchedule';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
import Instructor from 'src/shared/types/Instructor';
|
import CalendarCourse from '@views/components/calendar/CalendarCourseBlock/CalendarCourseMeeting';
|
||||||
import CalendarCourseCell from 'src/views/components/common/CalendarCourseCell/CalendarCourseCell';
|
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: 'Components/Common/CalendarCourseCell',
|
title: 'Components/Calendar/CalendarCourseMeeting',
|
||||||
component: CalendarCourseCell,
|
component: CalendarCourse,
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: 'centered',
|
layout: 'centered',
|
||||||
},
|
},
|
||||||
@@ -17,13 +16,9 @@ const meta = {
|
|||||||
course: { control: 'object' },
|
course: { control: 'object' },
|
||||||
meetingIdx: { control: 'number' },
|
meetingIdx: { control: 'number' },
|
||||||
color: { control: 'color' },
|
color: { control: 'color' },
|
||||||
|
rightIcon: { control: 'object' },
|
||||||
},
|
},
|
||||||
render: (args: any) => (
|
} satisfies Meta<typeof CalendarCourse>;
|
||||||
<div className="w-45">
|
|
||||||
<CalendarCourseCell {...args} />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
} satisfies Meta<typeof CalendarCourseCell>;
|
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|
||||||
type Story = StoryObj<typeof meta>;
|
type Story = StoryObj<typeof meta>;
|
||||||
@@ -37,7 +32,7 @@ export const Default: Story = {
|
|||||||
courseName: "Bevo's Default Course",
|
courseName: "Bevo's Default Course",
|
||||||
department: 'BVO',
|
department: 'BVO',
|
||||||
creditHours: 3,
|
creditHours: 3,
|
||||||
status: Status.WAITLISTED,
|
status: Status.OPEN,
|
||||||
instructors: [new Instructor({ firstName: '', lastName: 'Bevo', fullName: 'Bevo' })],
|
instructors: [new Instructor({ firstName: '', lastName: 'Bevo', fullName: 'Bevo' })],
|
||||||
isReserved: false,
|
isReserved: false,
|
||||||
url: '',
|
url: '',
|
||||||
115
src/stories/components/calendar/CalendarCourseCell.stories.tsx
Normal file
115
src/stories/components/calendar/CalendarCourseCell.stories.tsx
Normal 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>
|
||||||
|
),
|
||||||
|
};
|
||||||
121
src/stories/components/calendar/CalendarGrid.stories.tsx
Normal file
121
src/stories/components/calendar/CalendarGrid.stories.tsx
Normal 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,
|
||||||
|
},
|
||||||
|
};
|
||||||
18
src/stories/components/calendar/CalendarGridCell.stories.tsx
Normal file
18
src/stories/components/calendar/CalendarGridCell.stories.tsx
Normal 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 = {};
|
||||||
16
src/stories/components/calendar/CalendarHeader.stories.tsx
Normal file
16
src/stories/components/calendar/CalendarHeader.stories.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import CalendarHeader from '@views/components/calendar/CalendarHeader/CalenderHeader';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Calendar/CalendarHeader',
|
||||||
|
component: CalendarHeader,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
} satisfies Meta<typeof CalendarHeader>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {};
|
||||||
144
src/stories/components/calendar/CalendarSchedules.stories.tsx
Normal file
144
src/stories/components/calendar/CalendarSchedules.stories.tsx
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Calendar/CalendarSchedules',
|
||||||
|
component: CalendarSchedules,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
tags: ['autodocs'],
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
dummySchedules: { control: 'object' },
|
||||||
|
dummyActiveIndex: { control: 'number' },
|
||||||
|
},
|
||||||
|
render: (args: any) => (
|
||||||
|
<div>
|
||||||
|
<CalendarSchedules {...args} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
} satisfies Meta<typeof CalendarSchedules>;
|
||||||
|
|
||||||
|
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, // Add the missing 'hours' property
|
||||||
|
}),
|
||||||
|
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: 'Spring',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
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, // Add the missing 'hours' property
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
dummySchedules: schedules,
|
||||||
|
dummyActiveIndex: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
68
src/stories/injected/CourseCatalogInjectedPopup.stories.ts
Normal file
68
src/stories/injected/CourseCatalogInjectedPopup.stories.ts
Normal 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,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Course, Status } from 'src/shared/types/Course';
|
import { Course, Status } from '@shared/types/Course';
|
||||||
import { CourseMeeting } from 'src/shared/types/CourseMeeting';
|
import { CourseMeeting } from '@shared/types/CourseMeeting';
|
||||||
import { UserSchedule } from 'src/shared/types/UserSchedule';
|
import Instructor from '@shared/types/Instructor';
|
||||||
import CoursePopup from 'src/views/components/injected/CoursePopup/CoursePopup';
|
import { UserSchedule } from '@shared/types/UserSchedule';
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import CoursePopup from '@views/components/injected/CoursePopupOld/CoursePopup';
|
||||||
|
|
||||||
const exampleCourse: Course = new Course({
|
const exampleCourse: Course = new Course({
|
||||||
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
||||||
@@ -18,7 +19,20 @@ const exampleCourse: Course = new Course({
|
|||||||
flags: ['Quantitative Reasoning'],
|
flags: ['Quantitative Reasoning'],
|
||||||
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
||||||
instructionMode: 'Online',
|
instructionMode: 'Online',
|
||||||
instructors: [],
|
instructors: [
|
||||||
|
new Instructor({
|
||||||
|
firstName: 'William',
|
||||||
|
lastName: 'Young',
|
||||||
|
middleInitial: 'D',
|
||||||
|
fullName: 'William D Young',
|
||||||
|
}),
|
||||||
|
new Instructor({
|
||||||
|
firstName: 'William',
|
||||||
|
lastName: 'Young',
|
||||||
|
middleInitial: 'D',
|
||||||
|
fullName: 'William D Young',
|
||||||
|
}),
|
||||||
|
],
|
||||||
isReserved: false,
|
isReserved: false,
|
||||||
number: '303E',
|
number: '303E',
|
||||||
schedule: {
|
schedule: {
|
||||||
@@ -43,6 +57,7 @@ const exampleCourse: Course = new Course({
|
|||||||
const exampleSchedule: UserSchedule = new UserSchedule({
|
const exampleSchedule: UserSchedule = new UserSchedule({
|
||||||
courses: [exampleCourse],
|
courses: [exampleCourse],
|
||||||
name: 'Example Schedule',
|
name: 'Example Schedule',
|
||||||
|
hours: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
@@ -82,6 +97,7 @@ export const Open: Story = {
|
|||||||
activeSchedule: new UserSchedule({
|
activeSchedule: new UserSchedule({
|
||||||
courses: [],
|
courses: [],
|
||||||
name: 'Example Schedule',
|
name: 'Example Schedule',
|
||||||
|
hours: 0,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,13 +2,7 @@
|
|||||||
"extends": "../tsconfig.json",
|
"extends": "../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"lib": [
|
"lib": ["DOM", "es2021"],
|
||||||
"DOM",
|
"types": ["chrome", "node"]
|
||||||
"es2021"
|
}
|
||||||
],
|
|
||||||
"types": [
|
|
||||||
"chrome",
|
|
||||||
"node",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { Course, ScrapedRow } from '@shared/types/Course';
|
import type { Course, ScrapedRow } from '@shared/types/Course';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { useKeyPress } from '../hooks/useKeyPress';
|
import { useKeyPress } from '../hooks/useKeyPress';
|
||||||
import useSchedules from '../hooks/useSchedules';
|
import useSchedules from '../hooks/useSchedules';
|
||||||
import { CourseCatalogScraper } from '../lib/CourseCatalogScraper';
|
import { CourseCatalogScraper } from '../lib/CourseCatalogScraper';
|
||||||
import getCourseTableRows from '../lib/getCourseTableRows';
|
import getCourseTableRows from '../lib/getCourseTableRows';
|
||||||
import { SiteSupport } from '../lib/getSiteSupport';
|
import type { SiteSupport } from '../lib/getSiteSupport';
|
||||||
import { populateSearchInputs } from '../lib/populateSearchInputs';
|
import { populateSearchInputs } from '../lib/populateSearchInputs';
|
||||||
import ExtensionRoot from './common/ExtensionRoot/ExtensionRoot';
|
import ExtensionRoot from './common/ExtensionRoot/ExtensionRoot';
|
||||||
import AutoLoad from './injected/AutoLoad/AutoLoad';
|
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 RecruitmentBanner from './injected/RecruitmentBanner/RecruitmentBanner';
|
||||||
import TableHead from './injected/TableHead';
|
import TableHead from './injected/TableHead';
|
||||||
import TableRow from './injected/TableRow/TableRow';
|
import TableRow from './injected/TableRow/TableRow';
|
||||||
@@ -79,7 +81,7 @@ export default function CourseCatalogMain({ support }: Props) {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{selectedCourse && (
|
{selectedCourse && (
|
||||||
<CoursePopup
|
<CourseCatalogInjectedPopup
|
||||||
course={selectedCourse}
|
course={selectedCourse}
|
||||||
activeSchedule={activeSchedule}
|
activeSchedule={activeSchedule}
|
||||||
onClose={handleClearSelectedCourse}
|
onClose={handleClearSelectedCourse}
|
||||||
|
|||||||
@@ -1,24 +1,161 @@
|
|||||||
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 React from 'react';
|
||||||
import useSchedules from '../hooks/useSchedules';
|
import { FaCalendarAlt, FaCog, FaRedo } from 'react-icons/fa'; // Added FaRedo for the refresh icon
|
||||||
import { Button } from './common/Button/Button';
|
import { TestColors } from 'src/stories/components/PopupCourseBlock.stories';
|
||||||
import ExtensionRoot from './common/ExtensionRoot/ExtensionRoot';
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the main popup component.
|
||||||
|
* This component displays the main schedule, courses, and options buttons.
|
||||||
|
*/
|
||||||
export default function PopupMain() {
|
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 (
|
return (
|
||||||
<ExtensionRoot>
|
<ExtensionRoot>
|
||||||
<Button
|
<div className='mx-auto max-w-sm rounded-lg bg-white p-4 shadow-md'>
|
||||||
onClick={() => {
|
<div className='mb-2 flex items-center justify-between bg-white'>
|
||||||
if (!activeSchedule) return;
|
<div className='flex items-center'>
|
||||||
background.clearCourses({ scheduleName: activeSchedule?.name });
|
<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}
|
||||||
|
>
|
||||||
|
<FaCalendarAlt 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}
|
||||||
|
>
|
||||||
|
<FaCog 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',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Clear Courses
|
<StatusIcon status={Status.WAITLISTED} className='h-5 w-5 text-white' />
|
||||||
</Button>
|
</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>
|
||||||
|
<FaRedo className='ml-2 h-4 w-4 text-gray-600' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ExtensionRoot>
|
</ExtensionRoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/views/components/Settings.tsx
Normal file
14
src/views/components/Settings.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to hold everything for the settings page
|
||||||
|
* @param props className
|
||||||
|
* @returns The content for the settings page
|
||||||
|
*/
|
||||||
|
export default function Settings({ className }: Props) {
|
||||||
|
return <div className={className}>this will be finished laterrrrrrr</div>;
|
||||||
|
}
|
||||||
41
src/views/components/calendar/Calendar/Calendar.tsx
Normal file
41
src/views/components/calendar/Calendar/Calendar.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import clsx from 'clsx';
|
||||||
|
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[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const CalendarBottomBar = ({ courses }: CalendarBottomBarProps): JSX.Element => {
|
||||||
|
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 => (
|
||||||
|
<CalendarCourseBlock
|
||||||
|
courseDeptAndInstr={course.courseDeptAndInstr}
|
||||||
|
status={course.status}
|
||||||
|
colors={course.colors}
|
||||||
|
key={course.courseDeptAndInstr}
|
||||||
|
className={clsx(course.className, 'w-35!')}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center pl-2.5 pr-7.5'>
|
||||||
|
<Button variant='single' color='ut-black' icon={CalendarMonthIcon}>
|
||||||
|
Save as .CAL
|
||||||
|
</Button>
|
||||||
|
<Button variant='single' color='ut-black' icon={ImageIcon}>
|
||||||
|
Save as .PNG
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
.component {
|
||||||
|
display: flex;
|
||||||
|
padding: 7px 7px 9px 7px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 5px;
|
||||||
|
flex: 1 0 0;
|
||||||
|
align-self: stretch;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #cbd5e1;
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
gap: 7px;
|
||||||
|
.course-detail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
width: 154px;
|
||||||
|
.course {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.time-and-location {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 11px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import type { Course } from 'src/shared/types/Course';
|
||||||
|
import type { CourseMeeting } from 'src/shared/types/CourseMeeting';
|
||||||
|
|
||||||
|
import styles from './CalendarCourseMeeting.module.scss';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the CalendarCourseMeeting component.
|
||||||
|
*/
|
||||||
|
export interface CalendarCourseMeetingProps {
|
||||||
|
/** The Course that the meeting is for. */
|
||||||
|
course: Course;
|
||||||
|
/* index into course meeting array to display */
|
||||||
|
meetingIdx?: number;
|
||||||
|
/** The background color for the course. */
|
||||||
|
color: string;
|
||||||
|
/** The icon to display on the right side of the course. This is optional. */
|
||||||
|
rightIcon?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `CalendarCourseMeeting` is a functional component that displays a course meeting.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <CalendarCourseMeeting course={course} meeting={meeting} color="red" rightIcon={<Icon />} />
|
||||||
|
*/
|
||||||
|
const CalendarCourseMeeting: React.FC<CalendarCourseMeetingProps> = ({
|
||||||
|
course,
|
||||||
|
meetingIdx,
|
||||||
|
color,
|
||||||
|
rightIcon,
|
||||||
|
}: CalendarCourseMeetingProps) => {
|
||||||
|
let meeting: CourseMeeting | null = meetingIdx !== undefined ? course.schedule.meetings[meetingIdx] : null;
|
||||||
|
return (
|
||||||
|
<div className={styles.component}>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<div className={styles['course-detail']}>
|
||||||
|
<div className={styles.course}>
|
||||||
|
{course.department} {course.number} - {course.instructors[0].lastName}
|
||||||
|
</div>
|
||||||
|
<div className={styles['time-and-location']}>
|
||||||
|
{`${meeting.getTimeString({ separator: '-', capitalize: true })}${
|
||||||
|
meeting.location ? ` - ${meeting.location.building}` : ''
|
||||||
|
}`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CalendarCourseMeeting;
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { Status } from '@shared/types/Course';
|
||||||
|
import Text from '@views/components/common/Text/Text';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import React from 'react';
|
||||||
|
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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the CalendarCourseCell component.
|
||||||
|
*/
|
||||||
|
export interface CalendarCourseCellProps {
|
||||||
|
courseDeptAndInstr: string;
|
||||||
|
timeAndLocation?: string;
|
||||||
|
status: Status;
|
||||||
|
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,
|
||||||
|
status,
|
||||||
|
colors,
|
||||||
|
className,
|
||||||
|
}: CalendarCourseCellProps) => {
|
||||||
|
let rightIcon: React.ReactNode | null = null;
|
||||||
|
if (status === Status.WAITLISTED) {
|
||||||
|
rightIcon = <WaitlistIcon className='h-5 w-5' />;
|
||||||
|
} else if (status === Status.CLOSED) {
|
||||||
|
rightIcon = <ClosedIcon className='h-5 w-5' />;
|
||||||
|
} else if (status === Status.CANCELLED) {
|
||||||
|
rightIcon = <CancelledIcon className='h-5 w-5' />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// whiteText based on secondaryColor
|
||||||
|
const fontColor = pickFontColor(colors.primaryColor);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
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'>
|
||||||
|
<Text
|
||||||
|
variant='h1-course'
|
||||||
|
className={clsx('-my-0.8 leading-tight', {
|
||||||
|
truncate: timeAndLocation,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{courseDeptAndInstr}
|
||||||
|
</Text>
|
||||||
|
{timeAndLocation && (
|
||||||
|
<Text variant='h3-course' className='-mb-0.5'>
|
||||||
|
{timeAndLocation}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{rightIcon && (
|
||||||
|
<div
|
||||||
|
className='h-fit flex items-center justify-center justify-self-start rounded p-0.5 text-white'
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.secondaryColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{rightIcon}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CalendarCourseCell;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user