Compare commits

..

2 Commits

Author SHA1 Message Date
697e81b7c2 Safely commenting it out 2024-02-17 17:42:48 -06:00
f0329f33aa Idk why this is erroring out 2024-02-17 17:22:39 -06:00
165 changed files with 3060 additions and 4989 deletions

105
.eslintrc
View File

@@ -4,9 +4,12 @@
"browser": true, "browser": true,
"es6": true, "es6": true,
"node": true, "node": true,
"webextensions": true, "webextensions": true
}, },
"ignorePatterns": ["*.html", "tsconfig.json"], "ignorePatterns": [
"*.html",
"tsconfig.json"
],
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",
"plugin:react/recommended", "plugin:react/recommended",
@@ -18,14 +21,18 @@
"@unocss", "@unocss",
"prettier", "prettier",
], ],
"plugins": ["import", "jsdoc", "react-prefer-function-component", "@typescript-eslint", "simple-import-sort"], "plugins": [
"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": {
@@ -35,33 +42,36 @@
"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": [".ts", ".tsx"], "@typescript-eslint/parser": [
".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-plusplus": "off",
"no-inner-declarations": "off", "no-inner-declarations": "off",
@@ -73,16 +83,20 @@
"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": ["off"], "@typescript-eslint/no-shadow": [
"@typescript-eslint/no-use-before-define": ["off"], "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",
@@ -96,8 +110,8 @@
"error", "error",
{ {
"before": true, "before": true,
"after": true, "after": true
}, }
], ],
"no-continue": "off", "no-continue": "off",
"space-before-blocks": [ "space-before-blocks": [
@@ -105,22 +119,24 @@
{ {
"functions": "always", "functions": "always",
"keywords": "always", "keywords": "always",
"classes": "always", "classes": "always"
}, }
], ],
"react/jsx-filename-extension": [ "react/jsx-filename-extension": [
1, 1,
{ {
"extensions": [".tsx"], "extensions": [
}, ".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",
@@ -138,7 +154,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\"])",
@@ -153,9 +169,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",
@@ -170,36 +186,31 @@
{ {
"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": ["./src/shared", "./node_modules"], "except": [
"message": "You cannot import into `shared` from an external directory.", "./src/shared",
}, "./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",
},
} }

View File

@@ -1,43 +0,0 @@
name: Best Practices
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 8
- name: Install dependencies
run: pnpm install
- name: Run ESLint
run: pnpm run lint
format:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 8
- name: Install dependencies
run: pnpm install
- name: Run Prettier
run: pnpm run prettier

View File

@@ -1,6 +1,6 @@
name: 'Chromatic' name: "Chromatic"
on: [push, pull_request] on: [push, pull_request_target]
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"

View File

@@ -1,24 +0,0 @@
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

View File

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

View File

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

View File

@@ -37,7 +37,4 @@
"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"
}
} }

View File

@@ -14,17 +14,5 @@
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
- If you want to run the development build: 4. Run `pnpm build` to build the extension for production
- 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

View File

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

9
package-lock.json generated
View File

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

View File

@@ -9,23 +9,14 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build",
"prettier": "prettier src --check", "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"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", "@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",
@@ -33,35 +24,34 @@
"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", "html2canvas": "^1.4.1",
"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.71.1", "react-window": "^1.8.10",
"sass": "^1.70.0",
"sql.js": "1.10.2", "sql.js": "1.10.2",
"styled-components": "^6.1.8", "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.73", "@iconify-json/material-symbols": "^1.1.72",
"@storybook/addon-designs": "^7.0.9", "@storybook/addon-designs": "^7.0.9",
"@storybook/addon-essentials": "^7.6.17", "@storybook/addon-essentials": "^7.6.13",
"@storybook/addon-links": "^7.6.17", "@storybook/addon-links": "^7.6.13",
"@storybook/blocks": "^7.6.17", "@storybook/blocks": "^7.6.13",
"@storybook/react": "^7.6.17", "@storybook/react": "^7.6.13",
"@storybook/react-vite": "^7.6.17", "@storybook/react-vite": "^7.6.13",
"@storybook/test": "^7.6.17", "@storybook/test": "^7.6.13",
"@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.19", "@types/node": "^20.11.17",
"@types/prompts": "^2.4.9", "@types/prompts": "^2.4.9",
"@types/react": "^18.2.57", "@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19", "@types/react-dom": "^18.2.19",
"@types/semver": "^7.5.7", "@types/semver": "^7.5.6",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0", "@typescript-eslint/parser": "^6.21.0",
@@ -73,12 +63,10 @@
"@unocss/transformer-directives": "^0.58.5", "@unocss/transformer-directives": "^0.58.5",
"@unocss/transformer-variant-group": "^0.58.5", "@unocss/transformer-variant-group": "^0.58.5",
"@vitejs/plugin-react-swc": "^3.6.0", "@vitejs/plugin-react-swc": "^3.6.0",
"@vitest/coverage-v8": "^1.3.1", "chromatic": "^10.9.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.5", "dotenv": "^16.4.1",
"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",
@@ -87,27 +75,24 @@
"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.1.0", "eslint-plugin-jsdoc": "^48.0.6",
"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.35", "postcss": "^8.4.35",
"prettier": "^3.2.5", "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.17", "storybook": "^7.6.13",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"unocss": "^0.58.5", "unocss": "^0.58.5",
"unplugin-icons": "^0.18.5", "unplugin-icons": "^0.18.5",
"vite": "^5.1.4", "vite": "^5.1.1",
"vite-plugin-inspect": "^0.8.3", "vite-plugin-inspect": "^0.8.3"
"vitest": "^1.3.1"
}, },
"pnpm": { "pnpm": {
"patchedDependencies": { "patchedDependencies": {

4229
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,5 +1,4 @@
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)

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
import type { UserScheduleMessages } from '@shared/messages/UserScheduleMessages'; import { UserScheduleMessages } from '@shared/messages/UserScheduleMessages';
import { Course } from '@shared/types/Course'; import { Course } from '@shared/types/Course';
import type { MessageHandler } from 'chrome-extension-toolkit'; import { 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';

View File

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

View File

@@ -1,10 +1,5 @@
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);

View File

@@ -14,7 +14,6 @@ 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);

View File

@@ -1,11 +1,5 @@
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'),

View File

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

View File

@@ -1,11 +1,5 @@
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);

View File

@@ -1,11 +1,5 @@
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');

View File

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

View File

@@ -1,16 +1,18 @@
<!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>

View File

@@ -1,6 +1,5 @@
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 />);

View File

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

View File

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

View File

@@ -1,16 +1,18 @@
<!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>

View File

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

View File

@@ -1,16 +1,18 @@
<!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>

View File

@@ -1,6 +1,5 @@
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 />);

View File

@@ -1,16 +1,18 @@
<!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>

View File

@@ -1,6 +1,5 @@
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 />);

View File

@@ -1,4 +1,3 @@
/* 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;

View File

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

View File

@@ -1,8 +1,5 @@
import type { Course } from '@shared/types/Course'; import { Course } from '../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

View File

@@ -1,9 +1,8 @@
import { createMessenger } from 'chrome-extension-toolkit'; import { createMessenger } from 'chrome-extension-toolkit';
import BrowserActionMessages from './BrowserActionMessages';
import type BrowserActionMessages from './BrowserActionMessages'; import TabManagementMessages from './TabManagementMessages';
import type TabManagementMessages from './TabManagementMessages'; import TAB_MESSAGES from './TabMessages';
import type TAB_MESSAGES from './TabMessages'; import { UserScheduleMessages } from './UserScheduleMessages';
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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import type { Serialized } from 'chrome-extension-toolkit'; /* eslint-disable max-classes-per-file */
import { Serialized } from 'chrome-extension-toolkit';
import type { CourseMeeting } from './CourseMeeting'; import { CourseMeeting } from './CourseMeeting';
import { CourseSchedule } from './CourseSchedule'; import { CourseSchedule } from './CourseSchedule';
import Instructor from './Instructor'; import Instructor from './Instructor';
@@ -12,17 +12,12 @@ 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 const Status = { export enum 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
@@ -54,7 +49,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: StatusType; status: Status;
/** 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. */

View File

@@ -1,4 +1,4 @@
import type { Serialized } from 'chrome-extension-toolkit'; import { 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

View File

@@ -1,7 +1,5 @@
import type { Serialized } from 'chrome-extension-toolkit'; import { 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

View File

@@ -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

View File

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

View File

@@ -1,5 +1,4 @@
import type { Serialized } from 'chrome-extension-toolkit'; import { Serialized } from 'chrome-extension-toolkit';
import { Course } from './Course'; import { Course } from './Course';
/** /**
@@ -8,15 +7,10 @@ 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 {

View File

@@ -1,20 +1,12 @@
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
* Calculates the luminance of a given hexadecimal color. function getLuminance(hex: string): number {
*
* @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);

View File

@@ -1,18 +1,15 @@
import type { StatusType } from '@shared/types/Course'; import React, { SVGProps } from 'react';
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: StatusType }): React.ReactElement { export function StatusIcon(props: SVGProps<SVGSVGElement> & { status: Status }): React.ReactElement {
const { status, ...rest } = props; const { status, ...rest } = props;
switch (props.status) { switch (props.status) {

View File

@@ -18,8 +18,6 @@ export function capitalize(input: string): string {
} }
capitalized += ' '; capitalized += ' ';
} }
capitalized = capitalized.trim(); // Remove extra space
return capitalized; return capitalized;
} }
@@ -33,7 +31,7 @@ export function capitalizeFirstLetter(input: string): string {
} }
/** /**
* Cuts the input string to the specified length and adds an ellipsis if the string is longer than the specified length. * Cuts the
* @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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,36 +2,21 @@ export const colors = {
ut: { ut: {
burntorange: '#BF5700', burntorange: '#BF5700',
black: '#333F48', black: '#333F48',
orange: '#F8971F', orange: '#f8971f',
yellow: '#FFD600', yellow: '#ffd600',
lightgreen: '#A6CD57', lightgreen: '#a6cd57',
green: '#579D42', green: '#579d42',
teal: '#00A9B7', teal: '#00a9b7',
blue: '#005F86', blue: '#005f86',
gray: '#9CADB7', gray: '#9cadb7',
offwhite: '#D6D2C4', offwhite: '#d6d2c4',
concrete: '#95A5A6', 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: { theme: {
red: '#AF2E2D', red: '#af2e2d',
black: '#1A2024', black: '#1a2024',
}, },
gradeDistribution: { } as const;
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> = { type NestedKeys<T> = {
[K in keyof T]: T[K] extends Record<string, any> ? `${string & K}-${string & keyof T[K]}` : never; [K in keyof T]: T[K] extends Record<string, any> ? `${string & K}-${string & keyof T[K]}` : never;
@@ -42,10 +27,6 @@ type NestedKeys<T> = {
*/ */
export type ThemeColor = NestedKeys<typeof colors>; export type ThemeColor = NestedKeys<typeof colors>;
/**
* Flattened colors object.
* @type {Record<ThemeColor, string>}
*/
export const colorsFlattened = Object.entries(colors).reduce( export const colorsFlattened = Object.entries(colors).reduce(
(acc, [prefix, group]) => { (acc, [prefix, group]) => {
for (const [name, hex] of Object.entries(group)) { for (const [name, hex] of Object.entries(group)) {
@@ -56,18 +37,9 @@ export const colorsFlattened = Object.entries(colors).reduce(
{} as Record<ThemeColor, string> {} as Record<ThemeColor, string>
); );
/** const hexToRgb = (hex: string) =>
* Converts a hexadecimal color code to an RGB color array.
* @param hex The hexadecimal color code to convert.
* @returns An array representing the RGB color values.
*/
export const hexToRgb = (hex: string) =>
hex.match(/[0-9a-f]{2}/gi).map(partialHex => parseInt(partialHex, 16)) as [number, number, number]; 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( const colorsFlattenedRgb = Object.fromEntries(
Object.entries(colorsFlattened).map(([name, hex]) => [name, hexToRgb(hex)]) Object.entries(colorsFlattened).map(([name, hex]) => [name, hexToRgb(hex)])
) as Record<ThemeColor, ReturnType<typeof hexToRgb>>; ) as Record<ThemeColor, ReturnType<typeof hexToRgb>>;

View File

@@ -5,9 +5,7 @@ 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));

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,12 @@
import { Course, Status } from '@shared/types/Course'; import { Course, Status } from '@shared/types/Course';
import { getCourseColors } from '@shared/util/colors'; import { getCourseColors } from '@shared/util/colors';
import type { Meta, StoryObj } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import CalendarCourseCell from '@views/components/calendar/CalendarCourseCell/CalendarCourseCell'; import CalendarCourseCell from '@views/components/common/CalendarCourseCell/CalendarCourseCell';
import React from 'react'; import React from 'react';
import { exampleCourse } from './PopupCourseBlock.stories';
import { ExampleCourse } from '../PopupCourseBlock.stories';
const meta = { const meta = {
title: 'Components/Calendar/CalendarCourseCell', title: 'Components/Common/CalendarCourseCell',
component: CalendarCourseCell, component: CalendarCourseCell,
parameters: { parameters: {
layout: 'centered', layout: 'centered',
@@ -26,10 +25,10 @@ const meta = {
</div> </div>
), ),
args: { args: {
courseDeptAndInstr: ExampleCourse.department, courseDeptAndInstr: exampleCourse.department,
className: ExampleCourse.number, className: exampleCourse.number,
status: ExampleCourse.status, status: exampleCourse.status,
timeAndLocation: ExampleCourse.schedule.meetings[0].getTimeString({ separator: '-' }), timeAndLocation: exampleCourse.schedule.meetings[0].getTimeString({ separator: '-' }),
colors: getCourseColors('emerald', 500), colors: getCourseColors('emerald', 500),
}, },

View File

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

View File

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

View File

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

View File

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

View File

@@ -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

View File

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

View File

@@ -1,97 +1,6 @@
import { Course, Status } from '@shared/types/Course'; import { Meta, StoryObj } from '@storybook/react';
import { CourseMeeting } from '@shared/types/CourseMeeting';
import Instructor from '@shared/types/Instructor';
import type { Meta, StoryObj } from '@storybook/react';
import ConflictsWithWarning from '@views/components/common/ConflictsWithWarning/ConflictsWithWarning'; import 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 = { const meta = {
title: 'Components/Common/ConflictsWithWarning', title: 'Components/Common/ConflictsWithWarning',
component: ConflictsWithWarning, component: ConflictsWithWarning,
@@ -100,10 +9,8 @@ const meta = {
}, },
tags: ['autodocs'], tags: ['autodocs'],
argTypes: { argTypes: {
conflicts: { control: 'object' }, ConflictingCourse: { control: 'string' },
}, SectionNumber: { control: 'string' },
args: {
conflicts: [ExampleCourse, ExampleCourse2],
}, },
} satisfies Meta<typeof ConflictsWithWarning>; } satisfies Meta<typeof ConflictsWithWarning>;
export default meta; export default meta;
@@ -112,6 +19,7 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = { export const Default: Story = {
args: { args: {
conflicts: [ExampleCourse, ExampleCourse2], ConflictingCourse: 'BVO 311C',
SectionNumber: '47280',
}, },
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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',

View File

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

View File

@@ -1,19 +1,13 @@
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 PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock'; import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock';
import React from '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 { getCourseColors } from 'src/shared/util/colors';
import { theme } from 'unocss/preset-mini'; import { theme } from 'unocss/preset-mini';
/** export const exampleCourse: Course = new Course({
* Represents an example course.
*
* @remarks
* This is a sample course object that provides information about a specific course.
*/
export const ExampleCourse: Course = new Course({
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB', courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB',
creditHours: 3, creditHours: 3,
department: 'C S', department: 'C S',
@@ -68,7 +62,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: {
@@ -93,15 +87,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>
), ),
}; };
export const TestColors = Object.keys(theme.colors) export const test_colors = 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)
@@ -110,8 +104,8 @@ export const TestColors = Object.keys(theme.colors)
export const AllColors: Story = { export const AllColors: Story = {
render: props => ( render: props => (
<div className='grid grid-flow-col grid-cols-2 grid-rows-9 max-w-2xl w-90vw gap-x-4 gap-y-2'> <div className='grid grid-flow-col grid-cols-2 grid-rows-9 max-w-2xl w-90vw gap-x-4 gap-y-2'>
{TestColors.map((color, i) => ( {test_colors.map((color, i) => (
<PopupCourseBlock key={color.primaryColor} course={ExampleCourse} colors={color} /> <PopupCourseBlock key={color.primaryColor} course={exampleCourse} colors={color} />
))} ))}
</div> </div>
), ),

View File

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

View File

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

View File

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

View File

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

View File

@@ -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',

View File

@@ -1,6 +1,7 @@
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 = {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,16 +1,14 @@
import type { Course, ScrapedRow } from '@shared/types/Course'; import { 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 type { SiteSupport } from '../lib/getSiteSupport'; import { 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 CourseCatalogInjectedPopup from './injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup'; import CoursePopup from './injected/CoursePopup/CoursePopup';
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';
@@ -81,7 +79,7 @@ export default function CourseCatalogMain({ support }: Props) {
); );
})} })}
{selectedCourse && ( {selectedCourse && (
<CourseCatalogInjectedPopup <CoursePopup
course={selectedCourse} course={selectedCourse}
activeSchedule={activeSchedule} activeSchedule={activeSchedule}
onClose={handleClearSelectedCourse} onClose={handleClearSelectedCourse}

View File

@@ -1,10 +1,8 @@
import clsx from 'clsx';
import React from 'react'; import React from 'react';
import clsx from 'clsx';
import Text from './common/Text/Text';
import OutwardArrowIcon from '~icons/material-symbols/arrow-outward'; import OutwardArrowIcon from '~icons/material-symbols/arrow-outward';
import Text from '../common/Text/Text';
type Props = { type Props = {
className?: string; className?: string;
}; };

View File

@@ -1,161 +1,24 @@
import logoImage from '@assets/logo.png'; // Adjust the path as necessary import { background } from '@shared/messages';
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 { FaCalendarAlt, FaCog, FaRedo } from 'react-icons/fa'; // Added FaRedo for the refresh icon import useSchedules from '../hooks/useSchedules';
import { TestColors } from 'src/stories/components/PopupCourseBlock.stories'; import { Button } from './common/Button/Button';
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] = useSchedules(); const [activeSchedule, schedules] = useSchedules();
const draggableElements = activeSchedule?.courses.map((course, i) => ( // TODO: Add a button to to switch the active schedule
<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>
<div className='mx-auto max-w-sm rounded-lg bg-white p-4 shadow-md'> <Button
<div className='mb-2 flex items-center justify-between bg-white'> onClick={() => {
<div className='flex items-center'> if (!activeSchedule) return;
<img src={logoImage} alt='Logo' style={{ width: '40px', height: '40px', marginRight: '8px' }} /> background.clearCourses({ scheduleName: activeSchedule?.name });
<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',
}} }}
> >
<StatusIcon status={Status.WAITLISTED} className='h-5 w-5 text-white' /> Clear Courses
</div> </Button>
<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>
); );
} }

View File

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

View File

@@ -1,223 +0,0 @@
import type { CalendarGridCourse } from '@views/hooks/useFlattenedCourseSchedule';
import React, { useEffect, useRef, useState } from 'react';
// import html2canvas from 'html2canvas';
import { DAY_MAP } from 'src/shared/types/CourseMeeting';
import CalendarCourseCell from '../CalendarCourseCell/CalendarCourseCell';
/* import calIcon from 'src/assets/icons/cal.svg';
import pngIcon from 'src/assets/icons/png.svg';
*/
import CalendarCell from '../CalendarGridCell/CalendarGridCell';
import styles from './CalendarGrid.module.scss';
/* const daysOfWeek = Object.keys(DAY_MAP).filter(key => !['S', 'SU'].includes(key));
const hoursOfDay = Array.from({ length: 14 }, (_, index) => index + 8);
const grid = [];
for (let i = 0; i < 13; i++) {
const row = [];
let hour = hoursOfDay[i];
row.push(
<div key={hour} className={styles.timeBlock}>
<div className={styles.timeLabelContainer}>
<p>{(hour % 12 === 0 ? 12 : hour % 12) + (hour < 12 ? ' AM' : ' PM')}</p>
</div>
</div>
);
row.push(Array.from({ length: 5 }, (_, j) => <CalendarCell key={j} />));
grid.push(row);
} */
interface Props {
courseCells?: CalendarGridCourse[];
saturdayClass?: boolean;
}
/**
* Grid of CalendarGridCell components forming the user's course schedule calendar view
* @param props
*/
function CalendarGrid({ courseCells, saturdayClass }: React.PropsWithChildren<Props>): JSX.Element {
const [grid, setGrid] = useState([]);
const calendarRef = useRef(null); // Create a ref for the calendar grid
const daysOfWeek = Object.keys(DAY_MAP).filter(key => !['S', 'SU'].includes(key));
const hoursOfDay = Array.from({ length: 14 }, (_, index) => index + 8);
/* const saveAsPNG = () => {
htmlToImage
.toPng(calendarRef.current, {
backgroundColor: 'white',
style: {
background: 'white',
marginTop: '20px',
marginBottom: '20px',
marginRight: '20px',
marginLeft: '20px',
},
})
.then(dataUrl => {
let img = new Image();
img.src = dataUrl;
fetch(dataUrl)
.then(response => response.blob())
.then(blob => {
const href = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = href;
link.download = 'my-schedule.png';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
.catch(error => console.error('Error downloading file:', error));
})
.catch(error => {
console.error('oops, something went wrong!', error);
});
}; */
useEffect(() => {
const newGrid = [];
for (let i = 0; i < 13; i++) {
const row = [];
let hour = hoursOfDay[i];
let styleProp = {
gridColumn: '1',
gridRow: `${2 * i + 2}`,
};
row.push(
<div key={hour} className={styles.timeBlock} style={styleProp}>
<div className={styles.timeLabelContainer}>
<p>{(hour % 12 === 0 ? 12 : hour % 12) + (hour < 12 ? ' AM' : ' PM')}</p>
</div>
</div>
);
for (let k = 0; k < 5; k++) {
// let shouldRender = false;
styleProp = {
gridColumn: `${k + 2}`,
gridRow: `${2 * i + 2} / ${2 * i + 4}`,
};
/* let shouldRenderChild = courseCells[iterator]?.calendarGridPoint &&
k === courseCells[iterator].calendarGridPoint.dayIndex && i === courseCells[iterator].calendarGridPoint.startIndex;
let childElement = <div className={styles.dot}/>; */
/* let completeGridCell = shouldRenderChild ? <CalendarCell key={k} children={childElement}/>
: <CalendarCell key={k} />; */
row.push(<CalendarCell key={k} styleProp={styleProp} />);
}
newGrid.push(row);
}
setGrid(newGrid);
}, [hoursOfDay]);
return (
<div className={styles.calendarGrid}>
{/* Displaying day labels */}
<div className={styles.timeBlock} />
{daysOfWeek.map(day => (
<div key={day} className={styles.day}>
{day}
</div>
))}
{grid.map((row, rowIndex) => row)}
{courseCells ? <AccountForCourseConflicts courseCells={courseCells} /> : null}
{/* courseCells.map((block: CalendarGridCourse) => (
<div
key={`${block}`}
style={{
gridColumn: `${block.calendarGridPoint.dayIndex + 1}`,
gridRow: `${block.calendarGridPoint.startIndex + 1} / ${block.calendarGridPoint.endIndex + 1}`,
}}
>
<CalendarCourseCell
courseDeptAndInstr={block.componentProps.courseDeptAndInstr}
timeAndLocation={block.componentProps.timeAndLocation}
status={block.componentProps.status}
colors={block.componentProps.colors}
/>
</div>
)) */}
</div>
);
}
export default CalendarGrid;
interface AccountForCourseConflictsProps {
courseCells: CalendarGridCourse[];
}
function AccountForCourseConflicts({ courseCells }: AccountForCourseConflictsProps): JSX.Element[] {
// Groups by dayIndex to identify overlaps
const days = courseCells.reduce((acc, cell: CalendarGridCourse) => {
const { dayIndex } = cell.calendarGridPoint;
if (!acc[dayIndex]) {
acc[dayIndex] = [];
}
acc[dayIndex].push(cell);
return acc;
}, {});
// Check for overlaps within each day and adjust gridColumnIndex and totalColumns
Object.values(days).forEach((dayCells: CalendarGridCourse[]) => {
// Sort by start time to ensure proper columnIndex assignment
dayCells.sort((a, b) => a.calendarGridPoint.startIndex - b.calendarGridPoint.startIndex);
dayCells.forEach((cell, _, arr) => {
let columnIndex = 1;
cell.totalColumns = 1;
// Check for overlaps and adjust columnIndex as needed
for (let otherCell of arr) {
if (otherCell !== cell) {
const isOverlapping =
otherCell.calendarGridPoint.startIndex < cell.calendarGridPoint.endIndex &&
otherCell.calendarGridPoint.endIndex > cell.calendarGridPoint.startIndex;
if (isOverlapping) {
console.log('Found overlapping element');
// Adjust columnIndex to not overlap with the otherCell
if (otherCell.gridColumnStart && otherCell.gridColumnStart >= columnIndex) {
columnIndex = otherCell.gridColumnStart + 1;
console.log(columnIndex);
}
cell.totalColumns += 1;
}
}
}
cell.gridColumnStart = columnIndex;
cell.gridColumnEnd = columnIndex + 1;
});
});
return courseCells.map(block => (
<div
key={`${block}`}
style={{
gridColumn: `${block.calendarGridPoint.dayIndex + 1}`,
gridRow: `${block.calendarGridPoint.startIndex + 1} / ${block.calendarGridPoint.endIndex + 1}`,
width: `calc(100% / ${block.totalColumns})`,
marginLeft: `calc(100% * ${(block.gridColumnStart - 1) / block.totalColumns})`,
padding: '0px 10px 4px 0px',
}}
>
<CalendarCourseCell
courseDeptAndInstr={block.componentProps.courseDeptAndInstr}
timeAndLocation={block.componentProps.timeAndLocation}
status={block.componentProps.status}
colors={block.componentProps.colors}
/>
</div>
));
}
/* <div className={styles.buttonContainer}>
<div className={styles.divider} />
<button className={styles.calendarButton}>
<img src={calIcon} className={styles.buttonIcon} alt='CAL' />
Save as .CAL
</button>
<div className={styles.divider} />
<button onClick={saveAsPNG} className={styles.calendarButton}>
<img src={pngIcon} className={styles.buttonIcon} alt='PNG' />
Save as .PNG
</button>
</div> */

View File

@@ -1,21 +0,0 @@
import React from 'react';
import styles from './CalendarGridCell.module.scss';
interface Props {
styleProp: any;
}
/**
* Component representing each 1 hour time block of a calendar
* @param props
*/
function CalendarCell({ styleProp }: Props): JSX.Element {
return (
<div className={styles.calendarCell} style={styleProp}>
<div className={styles.hourLine} />
</div>
);
}
export default CalendarCell;

View File

@@ -1,10 +1,7 @@
import clsx from 'clsx'; import clsx from 'clsx';
import React from 'react'; import React from 'react';
import type IconComponent from '~icons/material-symbols'; import type IconComponent from '~icons/material-symbols';
import { ThemeColor, getThemeColorHexByName, getThemeColorRgbByName } from '../../../../shared/util/themeColors';
import type { ThemeColor } from '../../../../shared/util/themeColors';
import { getThemeColorHexByName, getThemeColorRgbByName } from '../../../../shared/util/themeColors';
import Text from '../Text/Text'; import Text from '../Text/Text';
interface Props { interface Props {

View File

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

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