Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fac10a743 | ||
|
|
1aa4e8c5fb | ||
|
|
ee4c6ce699 | ||
|
|
b171f01d01 | ||
|
|
846070ebb5 | ||
|
|
9c766c2695 | ||
|
|
4752f5860a | ||
|
|
c2328e461e | ||
|
|
aa29bcf9fe | ||
|
|
f0b257aa12 | ||
|
|
93733e3721 | ||
|
|
501f506677 | ||
|
|
009de62828 | ||
|
|
b74c698866 | ||
| cd05e5e7fc | |||
|
|
f425510e11 | ||
|
|
1f635d2515 | ||
|
|
a61bddf0e8 | ||
|
|
843cb5b443 | ||
|
|
52347fd56d | ||
|
|
a20332e53d | ||
|
|
0aa469af81 | ||
|
|
0d73b13b28 | ||
|
|
24256798ba | ||
|
|
e61ab565c3 | ||
|
|
9cbcfa4720 | ||
|
|
c712fc741e | ||
|
|
ca5e4c13d3 | ||
|
|
37bd7e79d9 | ||
|
|
918f4e419c | ||
| 3abb11734a | |||
|
|
a5e921fd75 | ||
|
|
7dd93690d6 | ||
| 7dbffc6eef | |||
|
|
8b922082a7 | ||
|
|
19e3838df2 | ||
| 25e3ba1ed4 | |||
|
|
598bafe67f | ||
|
|
ebcc0aa76a | ||
|
|
e987fbbe8e | ||
| c41467c617 | |||
|
|
9ad32390d1 | ||
|
|
0d51cae4c8 | ||
|
|
91fa78e2d0 | ||
|
|
b13e217a5b | ||
|
|
b732a80eaa | ||
|
|
46c76b1703 | ||
|
|
db04bbb52e | ||
|
|
e425daf041 |
242
.dockerignore
Normal file
242
.dockerignore
Normal file
@@ -0,0 +1,242 @@
|
||||
# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,node,react,storybookjs
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,macos,node,react,storybookjs
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### macOS Patch ###
|
||||
# iCloud generated files
|
||||
*.icloud
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-te
|
||||
node_modulesst
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
### Node Patch ###
|
||||
# Serverless Webpack directories
|
||||
.webpack/
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
# SvelteKit build / generate output
|
||||
.svelte-kit
|
||||
|
||||
### react ###
|
||||
.DS_*
|
||||
**/*.backup.*
|
||||
**/*.back.*
|
||||
|
||||
*.sublime*
|
||||
|
||||
psd
|
||||
thumb
|
||||
sketch
|
||||
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
.ionide
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,node,react,storybookjs
|
||||
|
||||
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
|
||||
package-lock.json
|
||||
storybook-static/
|
||||
package/
|
||||
|
||||
# Version control
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# Dependencies
|
||||
.pnpm-store
|
||||
!pnpm-lock.yaml
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
.nyc_output
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Docker
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
docker-compose*
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
CHANGELOG.md
|
||||
DOCKER_DEV_SETUP.md
|
||||
docs/
|
||||
@@ -23,6 +23,7 @@ module.exports = {
|
||||
'import',
|
||||
'import-essentials',
|
||||
'jsdoc',
|
||||
'eslint-plugin-tsdoc',
|
||||
'react-prefer-function-component',
|
||||
'@typescript-eslint',
|
||||
'simple-import-sort',
|
||||
@@ -143,7 +144,7 @@ module.exports = {
|
||||
'jsdoc/newline-after-description': 'off',
|
||||
'react/require-default-props': 'off',
|
||||
'jsdoc/require-jsdoc': [
|
||||
'warn',
|
||||
'error',
|
||||
{
|
||||
enableFixer: false,
|
||||
publicOnly: true,
|
||||
@@ -171,6 +172,7 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
],
|
||||
'tsdoc/syntax': 'error',
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
|
||||
58
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
58
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: '[BUG] '
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Pre-submission Checklist**
|
||||
|
||||
<!-- Please check all applicable boxes before submitting your issue -->
|
||||
|
||||
- [ ] I have searched existing issues to make sure this is not a duplicate
|
||||
- [ ] I have cleared my browser cache and confirmed the issue persists
|
||||
- [ ] I have checked this issue affects the latest version of the extension
|
||||
- [ ] I have disabled other extensions to ensure this isn't a conflict issue
|
||||
- [ ] I have included all the information requested below
|
||||
|
||||
**Bug Description**
|
||||
|
||||
<!-- A clear and concise description of what the bug is -->
|
||||
|
||||
**Steps To Reproduce**
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected Behavior**
|
||||
|
||||
<!-- A clear and concise description of what you expected to happen -->
|
||||
|
||||
**Current Behavior**
|
||||
|
||||
<!-- A clear and concise description of what actually happened -->
|
||||
|
||||
**Screenshots**
|
||||
|
||||
<!-- If applicable, add screenshots to help explain your problem -->
|
||||
|
||||
**Debug Information**
|
||||
|
||||
- UTRP Extension Version: [e.g. 1.2.0]
|
||||
- Browser Info: [e.g. Chrome 120.0.0]
|
||||
- OS: [e.g. Windows 11, macOS Sonoma]
|
||||
|
||||
**Console Logs**
|
||||
|
||||
<!-- Please check the browser console (F12) and paste any relevant error messages -->
|
||||
|
||||
```
|
||||
// Paste console logs here
|
||||
```
|
||||
|
||||
**Additional Context**
|
||||
|
||||
<!-- Add any other context about the problem here -->
|
||||
33
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
33
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for this project
|
||||
title: '[FEATURE] '
|
||||
labels: 'feature'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Pre-submission Checklist**
|
||||
|
||||
<!-- Please check all applicable boxes before submitting your feature request -->
|
||||
|
||||
- [ ] I have searched existing issues to ensure this feature hasn't been requested
|
||||
- [ ] I have searched closed issues to check if this was previously rejected/implemented
|
||||
- [ ] I have checked the project roadmap (if available) for planned similar features
|
||||
- [ ] I have reviewed the documentation to confirm this feature doesn't exist
|
||||
- [ ] I have completed all sections below with detailed information
|
||||
|
||||
**Feature Description**
|
||||
|
||||
<!-- A clear and concise description of the feature you'd like to see -->
|
||||
|
||||
**Proposed Solution**
|
||||
|
||||
<!-- A clear and concise description of what you want to happen -->
|
||||
|
||||
**UI/UX Considerations**
|
||||
|
||||
<!-- If this feature involves UI changes, please describe the visual aspects -->
|
||||
|
||||
**Technical Implementation Details**
|
||||
|
||||
<!-- If you have specific technical suggestions, list them here -->
|
||||
570
CHANGELOG.md
570
CHANGELOG.md
@@ -1,287 +1,335 @@
|
||||
## [2.1.0](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/v2.0.2...v2.1.0) (2025-02-20)
|
||||
|
||||
### Features
|
||||
|
||||
- add 'new search' link to the course catalog page ([#456](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/456)) ([ca5e4c1](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ca5e4c13d31aeb603660972536712ea161c6f870))
|
||||
- add 404 page ([#426](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/426)) ([46c76b1](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/46c76b1703ea2344c3330c4cfa82560663be1c4c))
|
||||
- add CacheStore for GitHub stats and use names instead of usernames ([#405](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/405)) ([b732a80](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b732a80eaa6f6cbf5b627ffc31ab321de3a8e8f4))
|
||||
- add eslint-plugin-tsdoc ([#430](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/430)) ([e987fbb](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e987fbbe8e733de6767d62811c2d4d6eaccf2d24))
|
||||
- add explanation to grade distribution ([#325](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/325)) ([9ad3239](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9ad32390d13e1d413ca01b1bc45d02c695bc23dd))
|
||||
- add open calendar button ([#457](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/457)) ([93733e3](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/93733e37214f00543479e77209ca03864776a1a6))
|
||||
- add spacing system ([#474](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/474)) ([e61ab56](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e61ab565c35a197609d205adcaac9c7ffe0fc6da))
|
||||
- **build:** add Docker support ([#322](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/322)) ([a5e921f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a5e921fd75109a50f17d05c9682e73ba246f3dd1))
|
||||
- **build:** add vite-build-logger ([#507](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/507)) ([1aa4e8c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/1aa4e8c5fb2d1aea308e4cccea3e2818a9c946ec))
|
||||
- **build:** refactor gulpfile to use gulp-execa ([#323](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/323)) ([db04bbb](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/db04bbb52e4ab19730dd66f417e68ab013ce2f79))
|
||||
- export/import functionality (backup/restore/share with friends) + a new input component ([#433](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/433)) ([7dbffc6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7dbffc6eef346747042f1596da627ad0a2fcae1a))
|
||||
- injected button - add all courses from MyUT AND passing URL to handler ([#291](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/291)) ([c41467c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/c41467c6176d31f70cd3ecdcf56eeb73696c6c23))
|
||||
- modify Course Block text style and time and location text ([#409](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/409)) ([0d51cae](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0d51cae4c8b27201b09c0898f4d7a6e7abb3c100))
|
||||
- **settings:** add option to always open calendar in new tab ([#488](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/488)) ([009de62](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/009de628285ce9c6571e492f8e3f52cdeeed4459))
|
||||
- **settings:** allow disabling of auto-loading courses ([#489](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/489)) ([b74c698](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b74c698866c1074ce2236ede549c94555667e6a5))
|
||||
- **ui:** add schedule list icons ([#500](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/500)) ([f0b257a](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/f0b257aa124a8fff0bb31e3396715aecb09948d5))
|
||||
- **ui:** added shadows to popup buttons and course blocks ([#378](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/378)) ([a20332e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a20332e53da9bda873fabb8395afe6ff6303799c))
|
||||
- **ui:** calendar header redesign ([#479](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/479)) ([9c766c2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9c766c26959673389e372c3a440c1a73d5887b1d))
|
||||
- **ui:** calendar sidebar redesign ([#464](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/464)) ([843cb5b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/843cb5b4430885527592feee33656dfae50c95c2))
|
||||
- **ui:** change icons to phosphor-icons [#467](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/467) ([#469](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/469)) ([37bd7e7](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/37bd7e79d9202d55c7d0f566518526b0aed53c68))
|
||||
- **ui:** change red text when instructor not found ([#483](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/483)) ([52347fd](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/52347fd56dff95190915d9f375dbebe1383e76c8))
|
||||
- **ui:** color picker final touches ([#491](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/491)) ([c2328e4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/c2328e461ea0a846f399a22bc525540da2796dcd))
|
||||
- **ui:** course color picker ([#382](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/382)) ([1f635d2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/1f635d2515fc403ad7f08fc3a244a17d262e3f7b))
|
||||
- **ui:** course unique number copy button ([#490](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/490)) ([501f506](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/501f50667725aa1515391869b133908b8383d868))
|
||||
- **ui:** Modify Calendar Footer design and Unscheduled courses ([#503](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/503)) ([b171f01](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b171f01d01f47c75e7df5e5c8e4e7b15b7397c52))
|
||||
- **ui:** redesign grade distribution tooltip ([#485](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/485)) ([a61bddf](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a61bddf0e8a76c89d4963d9e580fb063309bee92))
|
||||
- **ui:** update button variants following figma ([#482](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/482)) ([0aa469a](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0aa469af81e7912a6b26ee1b80c5920f677b5fbd))
|
||||
- **ui:** update popup and course blocks ([#506](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/506)) ([ee4c6ce](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ee4c6ce6999ad35821b9be5d657790f2dee017b3))
|
||||
- **ui:** update theme colors [#466](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/466) ([#473](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/473)) ([0d73b13](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0d73b13b288bf8b18444f26455da24c2f4acedf6))
|
||||
- update SWE list ([aa29bcf](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/aa29bcf9fe4bad7812336d55d7575a6032aea91c))
|
||||
- update text styles ([#468](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/468)) ([918f4e4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/918f4e419cf5fa71bda1112597e9a373daca69ea)), closes [#465](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/465)
|
||||
- validate login passed to background and implemented into add all injected button ([#443](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/443)) ([cd05e5e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/cd05e5e7fcaa02dab30f230c178f189d8052a7c9))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 4th attempt for: now able to delete schedule even if active ([#435](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/435)) ([2425679](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/24256798ba7b877dd170d77ce4970b13b8a69a68))
|
||||
- instructor formatting errors ([#425](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/425)) ([8b92208](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8b922082a79ebdbf50a09f37ecb9dfea1be6e1f3))
|
||||
- place hours and courses under schedule name ([#388](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/388)) ([7dd9369](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7dd93690d6dbd206cb50442f42ec85d6bbfc1da8))
|
||||
- transition added ([#381](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/381)) ([598bafe](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/598bafe67f47ad57b3801a425e61f86093ba2be8))
|
||||
- **ui:** fix longstanding drag-and-drop duplication issue ([#502](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/502)) ([4752f58](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4752f5860a96e08a1177c0ddf57a0fa269d89072))
|
||||
- **ui:** reduce left side grade distribution margin/padding ([#427](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/427)) ([91fa78e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/91fa78e2d0532a73e24ecc013d2ba6c0f62c1fcd))
|
||||
- **ui:** stop import button dropdown from squishing ([#504](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/504)) ([846070e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/846070ebb5e13e0fc0df94666dbce75194100049))
|
||||
- updated text when time/location not provided ([#289](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/289)) ([ebcc0aa](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ebcc0aa76a89f0d1a9e90cfc50d70f017c9bed42))
|
||||
|
||||
## [2.0.2](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/v2.0.1...v2.0.2) (2024-11-05)
|
||||
|
||||
### Features
|
||||
|
||||
- add core curriculum chips to injected popup ([#372](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/372)) ([6f1afc5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6f1afc5b25441c6a1fbfdf57b3c8b5b74e36f5a0))
|
||||
- Add linkedin social to calendar ([#368](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/368)) ([b6eccac](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b6eccaca6a2cdba9b57d2f49f064ae8504bbd5cb))
|
||||
- add more relevant links to the From the Team section ([#380](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/380)) ([643ea13](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/643ea1320798aabb7783d267f5e6fd7c00fc2e3f))
|
||||
- bold course number in grade distribution chart, change text to ut-black ([#406](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/406)) ([638ee88](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/638ee88c96510a779c157b524903caaeffc9ef19))
|
||||
- disable/some actions when no instructor ([#319](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/319)) ([839f9c6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/839f9c6d6afd4a1eae1a0bdf8893ab2e19b9fdff))
|
||||
- **ui:** changed popup close icon to ut-black ([#394](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/394)) ([0077ae7](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0077ae70d22f24549c4c3b243188d19adbfbac14)), closes [#333F48](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/333F48)
|
||||
- update senior swe admins ([#326](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/326)) ([b967240](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b967240f8fbb7a790a78f4aa256f0a77a491abb8))
|
||||
- update useful links ([#367](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/367)) ([cef99c2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/cef99c2d72d3a2800f8a918d01cb116f8795d0c8))
|
||||
- use "copy of" for duplicated schedules and place them under the original schedule [#358](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/358) ([#397](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/397)) ([94744e0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/94744e01b94819fb4f5d64616ea56857b906c2dd))
|
||||
- add core curriculum chips to injected popup ([#372](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/372)) ([6f1afc5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6f1afc5b25441c6a1fbfdf57b3c8b5b74e36f5a0))
|
||||
- Add linkedin social to calendar ([#368](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/368)) ([b6eccac](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b6eccaca6a2cdba9b57d2f49f064ae8504bbd5cb))
|
||||
- add more relevant links to the From the Team section ([#380](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/380)) ([643ea13](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/643ea1320798aabb7783d267f5e6fd7c00fc2e3f))
|
||||
- bold course number in grade distribution chart, change text to ut-black ([#406](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/406)) ([638ee88](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/638ee88c96510a779c157b524903caaeffc9ef19))
|
||||
- disable/some actions when no instructor ([#319](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/319)) ([839f9c6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/839f9c6d6afd4a1eae1a0bdf8893ab2e19b9fdff))
|
||||
- **ui:** changed popup close icon to ut-black ([#394](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/394)) ([0077ae7](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0077ae70d22f24549c4c3b243188d19adbfbac14)), closes [#333F48](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/333F48)
|
||||
- update senior swe admins ([#326](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/326)) ([b967240](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b967240f8fbb7a790a78f4aa256f0a77a491abb8))
|
||||
- update useful links ([#367](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/367)) ([cef99c2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/cef99c2d72d3a2800f8a918d01cb116f8795d0c8))
|
||||
- use "copy of" for duplicated schedules and place them under the original schedule [#358](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/358) ([#397](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/397)) ([94744e0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/94744e01b94819fb4f5d64616ea56857b906c2dd))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- added descending sort for commits on contributor section in settings page ([#365](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/365)) ([a715bbd](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a715bbd933a87742e7bce3a44e8ba1bd419ad5eb)), closes [#363](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/363) [#363](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/363) [#363](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/363)
|
||||
- change schedule total courses text color to UTRP black ([#369](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/369)) ([b00bf6c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b00bf6c180f1c6c3a61c5ef855e160ddf4af3ea4))
|
||||
- changed the font-weight of h1-course ([#370](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/370)) ([4f609ae](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4f609aeec797c1f99f0a57e5aeef7b82756ea4bc)), closes [#347](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/347)
|
||||
- ensure input elements take full width of parent ([#364](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/364)) ([c2007ef](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/c2007ef090aab3bbfcb8bca1ebc476255d09cb90))
|
||||
- remove screenshot padding class for png download for [#344](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/344) ([#376](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/376)) ([768ac77](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/768ac776ed4d5ca2113a032a93c2dc7432915aa1)), closes [#334](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/334)
|
||||
- sentry issues ([#389](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/389)) ([2d0804f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2d0804f90e5d7a9ff83f7fd5c5acfdc7c1b1cc84))
|
||||
- typo in settings page ([#386](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/386)) ([d357735](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d3577358d0d1fb60f2c776ae4b01e255fcf9109e))
|
||||
- **ui:** add space before/after forward slash in "ASYNC/OTHER" text ([#366](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/366)) ([86792eb](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/86792eb56f04b615f7d52b2f417b88f4cb9a82ec))
|
||||
- **ui:** duplicate schedule warning ([#295](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/295)) ([7346720](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/73467208947e0116ce8538052ee75dea1d8038f9))
|
||||
- **ui:** main popup now shows 0 for empty schedule ([#395](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/395)) ([8de88d6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8de88d6ad7d4c2b5c3aa08e1efc59f7226b40c6b))
|
||||
- **ui:** multiple instructors are formatted properly, displays last name only, and are capitalized in all course blocks ([#342](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/342)) ([#403](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/403)) ([50e88fa](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/50e88fa015e0290fbe0dab8a19f8fcdbc4dd02b0))
|
||||
- **ui:** placeholder text for no instructor course [#400](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/400) ([#402](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/402)) ([b3ae91d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b3ae91d8f3cebb89e5e5cea7f1200d28326afb4d))
|
||||
- added descending sort for commits on contributor section in settings page ([#365](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/365)) ([a715bbd](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a715bbd933a87742e7bce3a44e8ba1bd419ad5eb)), closes [#363](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/363) [#363](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/363) [#363](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/363)
|
||||
- change schedule total courses text color to UTRP black ([#369](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/369)) ([b00bf6c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b00bf6c180f1c6c3a61c5ef855e160ddf4af3ea4))
|
||||
- changed the font-weight of h1-course ([#370](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/370)) ([4f609ae](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4f609aeec797c1f99f0a57e5aeef7b82756ea4bc)), closes [#347](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/347)
|
||||
- ensure input elements take full width of parent ([#364](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/364)) ([c2007ef](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/c2007ef090aab3bbfcb8bca1ebc476255d09cb90))
|
||||
- remove screenshot padding class for png download for [#344](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/344) ([#376](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/376)) ([768ac77](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/768ac776ed4d5ca2113a032a93c2dc7432915aa1)), closes [#334](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/334)
|
||||
- sentry issues ([#389](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/389)) ([2d0804f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2d0804f90e5d7a9ff83f7fd5c5acfdc7c1b1cc84))
|
||||
- typo in settings page ([#386](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/386)) ([d357735](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d3577358d0d1fb60f2c776ae4b01e255fcf9109e))
|
||||
- **ui:** add space before/after forward slash in "ASYNC/OTHER" text ([#366](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/366)) ([86792eb](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/86792eb56f04b615f7d52b2f417b88f4cb9a82ec))
|
||||
- **ui:** duplicate schedule warning ([#295](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/295)) ([7346720](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/73467208947e0116ce8538052ee75dea1d8038f9))
|
||||
- **ui:** main popup now shows 0 for empty schedule ([#395](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/395)) ([8de88d6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8de88d6ad7d4c2b5c3aa08e1efc59f7226b40c6b))
|
||||
- **ui:** multiple instructors are formatted properly, displays last name only, and are capitalized in all course blocks ([#342](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/342)) ([#403](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/403)) ([50e88fa](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/50e88fa015e0290fbe0dab8a19f8fcdbc4dd02b0))
|
||||
- **ui:** placeholder text for no instructor course [#400](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/400) ([#402](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/402)) ([b3ae91d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b3ae91d8f3cebb89e5e5cea7f1200d28326afb4d))
|
||||
|
||||
## [2.0.1](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/v2.0.0...v2.0.1) (2024-10-17)
|
||||
|
||||
### Features
|
||||
|
||||
- spring 2024 instructors db ([#317](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/317)) ([79dd29c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/79dd29cfc9e849b09e7d91bd0eed51c1c93b3352))
|
||||
- spring 2024 instructors db ([#317](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/317)) ([79dd29c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/79dd29cfc9e849b09e7d91bd0eed51c1c93b3352))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- add a little error checking to settings ([#315](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/315)) ([e261641](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e261641e5985d8bd5047d8a0be5d1caae844e40f))
|
||||
- gulp zip ([#314](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/314)) ([05f00b2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/05f00b23d26b90f564710db4364426e90c8d6831))
|
||||
- migration loop ([aeff5e0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/aeff5e09a238503293c3882d97d40270da1e4883))
|
||||
- show calendar in active window ([#312](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/312)) ([ceba38b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ceba38b1ac74ec9e6630222183bd466a8d12c27d))
|
||||
- add a little error checking to settings ([#315](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/315)) ([e261641](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e261641e5985d8bd5047d8a0be5d1caae844e40f))
|
||||
- gulp zip ([#314](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/314)) ([05f00b2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/05f00b23d26b90f564710db4364426e90c8d6831))
|
||||
- migration loop ([aeff5e0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/aeff5e09a238503293c3882d97d40270da1e4883))
|
||||
- show calendar in active window ([#312](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/312)) ([ceba38b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ceba38b1ac74ec9e6630222183bd466a8d12c27d))
|
||||
|
||||
## [2.0.0](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/b4e8c7589e53f1064d70703459cc6d66fae1b04c...v2.0.0) (2024-10-15)
|
||||
|
||||
### Features
|
||||
|
||||
- abhinavchadaga/course-catalog-popup ([#128](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/128)) ([745f9dd](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/745f9dd6fb96ccc0eace7189b87abf9a38f03828))
|
||||
- abhinavchadaga/reusable-popup-prompt ([#148](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/148)) ([44af9e1](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/44af9e16e4bf3b92c9a4fa0e779197f4f9ecb237))
|
||||
- actually sum for duplicate semesters (different uniques) ([#202](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/202)) ([d3f64ec](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d3f64ec79eb64ebe72ae28991127dbe521823842))
|
||||
- add button to the rows, use new ConflictsWithWarning component ([bec2649](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bec2649fc1244bffb5cb03809db62cc0ea477260))
|
||||
- add buttons with icons in tailwind ([93b65ac](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/93b65ac2edef3fc728305cc51d1a32ecf5073274))
|
||||
- add cal save buttons (no functionality) ([53e7c7f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/53e7c7fedaf1f268d2d7d06ff1a5df9e2af2306d))
|
||||
- add Calendar Component ([e0212d5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e0212d510928e5e95d3ba75e391c34714f1bb035))
|
||||
- add Calendar schedules component (clicking on storybook not working) ([d1a336e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d1a336e903f09b04b89f11ba7ede2f7d26e0ac3c))
|
||||
- add CalendarHeader and its Storybook, need to resize ([23276e5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/23276e5c7c907261b1d02e9006d8021d99b43984))
|
||||
- add check-path-alias custom ESLint rule ([#123](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/123)) ([208103d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/208103d7081abcf5b31bf36335320aaf7d213464))
|
||||
- add chrome.storage api mocks for storybook use ([#141](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/141)) ([3839bff](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/3839bff29ef6f0ecfad1e975e685df9a64d96c92))
|
||||
- add CSS for calendarCell div ([6cdcf49](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6cdcf4930d9868c0ed45820f66102d89a644cfb6))
|
||||
- add CSS for hourLine div ([0ba6153](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0ba61534cbfc83408cb64eb1145ecacef1ee8f04))
|
||||
- add CSS for timeLabelContainer div ([56f6456](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/56f6456ce85f77edb9d6fa64ecb82a8388feec41))
|
||||
- add custom ESLint rule restrict-import-depth ([#110](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/110)) ([8c069b7](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8c069b7ad339ed8db97bb5e3e6e88ab8d332489b))
|
||||
- add downloadBlob util ([2af351e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2af351efa85264da5abeee27fbd091d4ce637041))
|
||||
- add empty settings component - waiting on design ([9d0f210](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9d0f2105481cc3d5affdb4bd69d428379ea92de3))
|
||||
- add ImportantLinks Component ([5dbee6f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5dbee6f0c39e620c3b65a5e0156fd59ec8217ca7))
|
||||
- add List component ([e6b4049](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e6b40494031ca88894b2f918bb18448b70124481))
|
||||
- add MIMEType ([0c76052](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0c760524781556cf089f1885dc4ff3fd567bfc2b))
|
||||
- add new db powered by UT_Grade_Parser ([#163](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/163)) ([60d1f48](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/60d1f48bd95132f0517258f017a7644d8aff5101))
|
||||
- add react-loading-skeleton package ([#244](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/244)) ([b4dd91a](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b4dd91ad2595d9583a60530c2c2ff6d829d2d5e5))
|
||||
- add skeleton loader for course description + distribution ([#267](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/267)) ([c2cab40](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/c2cab407f3a07c1d3073e936c9d23cb1cecc9cb9))
|
||||
- add story for CalendarGridCell ([fd91c3b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/fd91c3b12ef5670563e689d190be75ef1f695d1c))
|
||||
- add Storybook story ([cb3cb5d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/cb3cb5d5fc18a68ca5a825f97b808b535aa1fe29))
|
||||
- add tailwind version of Button component ([28d93b3](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/28d93b3c25e1cbca3baea992a299d626ca823570))
|
||||
- add tickmarks to day div ([8b9cb06](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8b9cb065c28b5dd38b7e1681839a81f55691a3c6))
|
||||
- add time column to Calendar Component ([9d68211](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9d6821127e7ff9bf63cef659932ee7408de4c969))
|
||||
- add timeAndGrid div ([95e0544](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/95e0544b7301d382439b74038fb1b3e25dfe6d30))
|
||||
- added flag ;-; ([#195](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/195)) ([9b4d61c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9b4d61c2b0d8806b5d4a10296c1cc1266ac4229f))
|
||||
- added scrapedAt property ([#149](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/149)) ([8e181b3](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8e181b3010dfe25ea1e4b4ba181cbe9944d6e92e))
|
||||
- additional changes to [#201](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/201) ([#224](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/224)) ([bc354f3](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bc354f3798a02e6ebc3c66a8b5eee553c25a19d4))
|
||||
- aesthetically pleasing squishier course blocks when compressed ([#232](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/232)) ([438c82b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/438c82bfb5db0f570b261d15368a05447939b0fb))
|
||||
- align day labels and add to grid ([1bb6191](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/1bb6191244f40220a9af66cced4fe8f0c1e546a0))
|
||||
- alignment on calendar and header ([#109](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/109)) ([07ec5ab](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/07ec5abc3e1b40b801b7c977d21399b31222a4d3))
|
||||
- async course adding and async/other course block adjustments ([#273](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/273)) ([668c8d0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/668c8d00756a5e3e3daaade46380967c74eec1ae))
|
||||
- async text hiding on Calendar's Bottom Bar when there are no async courses ([#152](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/152)) ([0dff122](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0dff12232c29415fa7bc5ac3771393a406ab738d))
|
||||
- basic CalendarCourseMeeting component laid out - missing Text and Right Icon ([da9e7aa](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/da9e7aac4135afa5184e4949c36a288ef6ab62a7))
|
||||
- Best Practices ([#102](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/102)) ([5eb7be2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5eb7be246cd2a1db27c7ca777d81074ae7bb3b82))
|
||||
- beta builds ([#187](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/187)) ([8a5e12e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8a5e12ec63539ae7199f0f1561f642cb9cf19302))
|
||||
- better discord icon ([#205](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/205)) ([a3f5e0f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a3f5e0f27ffdfcb4e3ddd47140979aca379f9f42))
|
||||
- bold red refresh error ([#220](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/220)) ([229a8a2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/229a8a29e8d04dfbe15de200ceffc53b4f758413))
|
||||
- bottom bar for the calendar page ([#91](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/91)) ([0f730d6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0f730d6c50462c9f7d0428ebb379742305212ef6))
|
||||
- build without errors ([babc925](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/babc925967b01f1064b8d8e067e7bc792dd57b0a))
|
||||
- calendar components 3rd attempt at merging ([#60](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/60)) ([4faca8c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4faca8c43befe19c9b05ff4f3afba420e016bb59))
|
||||
- Calendar Components 3rd Attempt at Merging ([#60](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/60)) ([ab2cd68](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ab2cd688fae616742b39c0637291a65e508e23bd))
|
||||
- calendar course block component ([#75](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/75)) ([00e0019](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/00e00197efb807d420196f2b030fc4dc71e9cd42))
|
||||
- calendar course block component ([#75](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/75)) ([a41cb3e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a41cb3ed8711eff9a2fbbe435e867c073904f452))
|
||||
- calendar grid and grid cells ([#81](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/81)) ([bfeb239](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bfeb2398aacf9a1fba892737b6ef0286acbc326f))
|
||||
- Calendar Grid and Grid Cells ([#81](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/81)) ([dd2f696](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/dd2f696f8de2c0b291e917ef4d4cb2171b76712c))
|
||||
- calendar header formatting and data displaying ([#160](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/160)) ([5cce1c7](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5cce1c79fc2b50843426ed290a1b53849375e4eb))
|
||||
- calendar matchings ([#173](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/173)) ([791a42b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/791a42bcd49478024b6f1a237a2d2a9f6211749e))
|
||||
- Calendar Schedule component finished, fix: list didn't allow updates when adding a new schedule ([#115](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/115)) ([a99a557](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a99a55788a0b9791ff048c8bbbaad5bada47c41f))
|
||||
- calendar-course-cell-color-picker ([#157](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/157)) ([df18491](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/df1849180de2c793c10e3a49d2c862d236456ec2))
|
||||
- can open tabs, updated injected popup heading. basically done ([35fab34](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/35fab3444582b436f63c7b16a26ebd5989c4bf3e))
|
||||
- change Chip to tailwind css. Fixed eslint for ConflictsWithWarning ([3568b8e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/3568b8eb5ee9ce3fe17f77f5641c0583bacc17a4))
|
||||
- check-path-alias autofix ([#124](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/124)) ([265652c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/265652c4200a56b1ae62e6e94e0609fb4604c188))
|
||||
- chrome extension works ([35f3c72](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/35f3c7225049d760c6061ed827aa796514291e62))
|
||||
- color palette for calendar ([#118](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/118)) ([471e55d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/471e55dcea1ae439658d00ed41570e2f218f0c3d))
|
||||
- Conventional Commits ([#103](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/103)) ([fe599df](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/fe599dfe7514ead880596d3b75c8370bde796e09))
|
||||
- convert all LabelsAndDetails Components to Tailwind ([9e0f9df](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9e0f9df9de1a5c99657ddcef3b2eb101dd825b84))
|
||||
- course color generation ([#179](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/179)) ([5ed81e4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5ed81e4be99a8b7f3c68a3ba70358e4cbe5cc613))
|
||||
- course colors ([#175](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/175)) ([dc77cc2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/dc77cc27da7ab521e290a8c9ba810573e677b6b8))
|
||||
- Course Flag Tooltips ([#178](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/178)) ([ba2bc60](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ba2bc60add2b87ccd4e9894c454d7a8d8382f0dd))
|
||||
- course-catalog-injected-popup ([#98](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/98)) ([89d03f4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/89d03f4244d324915cadadaf75a827221525d43f))
|
||||
- CourseStatus Component implemented ([#83](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/83)) ([58d7df4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/58d7df499ca9a8dfd4dc3aa47bc9d76837f21bf7))
|
||||
- CourseStatus Component implemented ([#83](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/83)) ([fa1d737](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/fa1d7374bcafbe19673dc31f33b3f8075e45f37a))
|
||||
- create empty Popup story ([ad83ba4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ad83ba4cdc67f86096d0cbc2cb6659aa9be98a0b))
|
||||
- Create icon helper ([#77](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/77)) ([ccea0f4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ccea0f4bd1e8d66dc0717d92c9e970040d9fbe9e))
|
||||
- Create icon helper ([#77](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/77)) ([1b51d65](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/1b51d65c89a4b544f4f2a9c60106e14300f9a3b0))
|
||||
- Derek vinson/calendar header ([#94](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/94)) ([12d09b5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/12d09b54cbf840993d17323b96cf274ec24c67ce))
|
||||
- Derek/disable updating ([#239](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/239)) ([0ab83ef](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0ab83efd47d75fb6c278994c7dc8a6d22b4b6b83))
|
||||
- Derek/export png ([#95](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/95)) ([58c2b46](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/58c2b4634a9d065ca156d27c4af69a504b7c2d2d))
|
||||
- DialogProvider component ([#198](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/198)) ([d1b921a](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d1b921a5b000693d7f3dabaf84d8b9580c361941))
|
||||
- dividers in calendar bottom bar ([#120](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/120)) ([eba5d9f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/eba5d9f508576d2648bd2501e5d5aaff32592566))
|
||||
- **docs:** add extra acknowledgements ([e2cbfa3](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e2cbfa3f48cb0b10a43134cf9784a44b13aa542d))
|
||||
- drag only on vertical axis ([34a6449](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/34a644952995d4aedd18d11d327bd8589f5b2610))
|
||||
- early iteration of non-virtual list ([677aa62](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/677aa624d7fb2bbb922aaf8806abe979c3399e4e))
|
||||
- enable TS strict mode ([#168](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/168)) ([efed1c0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/efed1c0edbf93987c551952a03c9c4b3c461d819))
|
||||
- experimental toggle for icons, left off for now ([#237](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/237)) ([f34dd95](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/f34dd95d7765322b6cecb2e817a46e0549d929a5))
|
||||
- fall 2023 grades ([#226](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/226)) ([863d980](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/863d980b2d1c706b1fc648f1a0de2a31515e339e))
|
||||
- finally fix grid JSX.Element generation ([e49fc29](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e49fc295ba40a37f9bf59686adbd8b4f523cd9c6))
|
||||
- finish ScheduleTotalHoursAndCourses ([12f680d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/12f680d7e9aef826281c8f769825408dc6f9f9de))
|
||||
- fix icons on PopupMain and convert to tailwind ([#108](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/108)) ([87799d8](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/87799d8f02aa5986025982f768639afdb1b0267a))
|
||||
- fix save as button dividers ([#153](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/153)) ([7986549](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7986549fdd40834a85bedbfd0862cdb21c9ae657))
|
||||
- grades by professor ([#225](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/225)) ([78d749a](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/78d749a8a96f63c32663b464e5b663f932c28ed0))
|
||||
- html2canvas -> htmlToImage and fix derick's bugs ([bda0282](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bda02826b14b96fd65a6f24218950e510c5aa3d8))
|
||||
- icon added successfully ([3b588c2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/3b588c20394523ddf90456e1ca58546e5b820c74))
|
||||
- implement Chip component ([23e881f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/23e881f14cc80100d8f8deef6a25daaa72d83f07))
|
||||
- implement flatten course schedule helper function ([e54f488](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e54f488b170d5a9af646c1ac82e8b33579fef61d))
|
||||
- implemented ConflictsWithWarning ([93f3a30](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/93f3a307b4acce6cc55fc411f1ea2d7576006b00))
|
||||
- implemented InfoCard ([21b6430](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/21b643000d884442e025534802be6bb1f6ba68e2))
|
||||
- Initial Splash Text Commit ([#208](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/208)) ([0534f60](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0534f607a51f1dd0905f6f45f08519cba65ea05a))
|
||||
- limit schedules to 10 ([#272](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/272)) ([290b841](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/290b8415170bca631e5d71b08d88db80b02163f0))
|
||||
- link to your registered courses ([#228](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/228)) ([f83e012](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/f83e012d62e268d95dce19a30e54e34f3779142b))
|
||||
- list reordering ([#154](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/154)) ([038ebaa](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/038ebaa2689c14bc9906afb542ef62c21cb13177))
|
||||
- listed versioning for beta builds ([#192](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/192)) ([0c42979](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0c429794230a91c4fd949715b7eea210ac18fee3))
|
||||
- lowercase instructor! ([#268](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/268)) ([9ec05ef](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9ec05ef764b2451c05e2d084d92074fb1e984268))
|
||||
- made List more extensible ([cd34601](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/cd34601379d5cd2b53d967afbc954178834823b9))
|
||||
- match calendar designs & add functionality ([#176](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/176)) ([8027c3d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8027c3d1bf228053acbad3e232b030c9ddbaca6a))
|
||||
- migrate styles to TailwindCSS ([7e2f5ea](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7e2f5eaed7d782bc886545f7241e0891d55cfae0))
|
||||
- migration update showing ([#293](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/293)) ([aede681](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/aede681d4bb5b01723eb9d68527f4e64991adfab))
|
||||
- missed one chip toggle there ([#245](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/245)) ([5ca24da](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5ca24dab82509d34cf43af4359940476159d052f))
|
||||
- newer grades and parameterized queries ([#238](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/238)) ([75ad416](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/75ad4167b6639bbcf58e2dc6dafc80ed4656a899))
|
||||
- one single exclamation mark did all that ([#235](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/235)) ([be87e41](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/be87e4181430e0df41683be5c9d0a1ce992b2c61))
|
||||
- open an injected course page on course block click in popup main ([#146](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/146)) ([2709484](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/27094846f70feb8d83c8d464ab0fa8dcd99b3e71))
|
||||
- pad unique ids to 5 digits ([#170](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/170)) ([b4ad687](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b4ad6870bfc88f68bdd7f87fe1d94c23d0a02b95))
|
||||
- parallelize initializeDB.ts promises ([9f1dcc6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9f1dcc667df9857e3d0469fe7112a9f985aad490))
|
||||
- popout icon for ccip in calendar ([#221](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/221)) ([6812d68](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6812d685d08902f7b3d9d2e5293350c8170c6f06))
|
||||
- popup schedule select ([#126](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/126)) ([7f2a589](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7f2a5893d4dd82a0f077dca6dfd28e79f929766f))
|
||||
- PopupCourseBlock Component ([#79](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/79)) ([9accd17](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9accd17bd418f8c9334e80b0f7e38673092ba208))
|
||||
- PopupCourseBlock Component ([#79](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/79)) ([f045b40](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/f045b400a56569f7353eb00ecd0d081b68660fc5))
|
||||
- proper injected styles ([#164](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/164)) ([e919e96](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e919e96c53bc9bec7db3d09693d1ff0684b56e87))
|
||||
- readme animation ([#281](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/281)) ([da6d86c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/da6d86c785f92f70f52ef0b4cab3dc14e0bd9790))
|
||||
- refactor all components in common ([e544312](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e5443122b422266a126de497b475020e298b2f1c))
|
||||
- refactor all components in injected ([0c44849](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0c44849e15333c3a5cbdf8972eb31ada0e830b0a))
|
||||
- refactor calendar ([28f1924](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/28f192472b7f244253b6a0b5339491b9740880f7))
|
||||
- Refactor database initialization code ([5e98f45](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5e98f45210346f0e7bb69af5cf25e336c76416ca))
|
||||
- release notes ([#283](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/283)) ([bd17e33](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bd17e3353792df70ca2e33c8b7a32b5fb9acc7d2))
|
||||
- report issue popup ([#261](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/261)) ([65ff6bf](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/65ff6bfbbfc6b696621628e448865155d0405f7f))
|
||||
- rerouted directory to syllabus when click on professor name ([#211](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/211)) ([8959e0d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8959e0d9f7fdd82125f391f1ec51aa390ba9450d))
|
||||
- sam's jokes ([#278](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/278)) ([895cd31](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/895cd31d8eb40e7e99fc0969b543e95bd070f6b7))
|
||||
- save as PNG functionality ([ad18fbd](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ad18fbd16235be0c75121b527867fac708670300))
|
||||
- schedule list item action menu ([#230](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/230)) ([15fc369](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/15fc3699cfda3ad56e5a262bd37dca166e2685dd))
|
||||
- screenshot whole page, hide certain elements, screenshot fixed size ([#180](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/180)) ([7d4c5d7](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7d4c5d7be8b266b5f3caf2135c4f3fecb96d75be))
|
||||
- settings page ([#260](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/260)) ([7a5c3a2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7a5c3a2e62c3a726735f9fe921f9e08d4092d0f9))
|
||||
- show async courses in the bottom bar ([#204](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/204)) ([227de53](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/227de53e8453804f0791f269dae64ec388136390))
|
||||
- some small changes for colors and font ([#201](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/201)) ([bae1da4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bae1da43d3373e12d593682e9fb69e19557b5749))
|
||||
- splash text additions before v2 release ([#296](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/296)) ([e774f31](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e774f316e3f92c03e79274a55f1f729178c9a14a))
|
||||
- splash text has arrived! ([#246](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/246)) ([9971435](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9971435716dec11e954dc26c14bf7d1505563d0d))
|
||||
- Storybook for Vite ([#52](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/52)) ([9cc299c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9cc299ced6132644a5c2375b95a8a16a3482601b))
|
||||
- swe title updates ([#310](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/310)) ([4629626](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4629626a31934c491758ddca874e5018d9c22e57))
|
||||
- switch button ([#229](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/229)) ([abae7a5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/abae7a5c22ab944262d66ae897298b008a03c5f1))
|
||||
- temporary removal of waitlist etc ([#236](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/236)) ([d424ccc](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d424ccce49ba05305b24cb61aeb4ffeada5bab33))
|
||||
- UnoCSS (TailwindCSS) (Storybook only) ([#61](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/61)) ([85c7f78](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/85c7f7817c58f13f6a4e8bec13a45f6412ad87db))
|
||||
- UnoCSS (TailwindCSS) (Storybook only) ([#61](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/61)) ([6521a4b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6521a4b2a90af06e182c779f2fc7cb1a3b1e55c1))
|
||||
- unplugin-icons ([#62](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/62)) ([2d67b12](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2d67b1218ff852c59901b77909615c5f54794a67))
|
||||
- unplugin-icons ([#62](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/62)) ([945e09b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/945e09b49e4ffa9bd4b02442f6567bb99831923e))
|
||||
- update admin titles ([4cf8c3f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4cf8c3f9645cd8fda07244413cdc406105b12fec))
|
||||
- update badge count when schedule changes ([#150](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/150)) ([a5e9e3c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a5e9e3c2145d61d8cc5788eb50fa19718e6d13bf))
|
||||
- update Button to v2 design ([863521f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/863521fb3bb268131bd3f369064ae10a442b4fbc))
|
||||
- update dialog component to headlessui ([#159](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/159)) ([442be8c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/442be8cbee06ec403467b100d8d5300dd4a7ea72))
|
||||
- update with TailwindCSS ([f3a8a7d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/f3a8a7db560a6b28f96a2e976c8ed33af97fe77a))
|
||||
- updated calendar page and recruitment banner links ([#219](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/219)) ([a2303ee](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a2303ee35f10aba5fa7ff5e307d0092a39f18830))
|
||||
- updated divider component ([#99](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/99)) ([8ab60c9](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8ab60c9f018143df594e7bfd0d321666289cc28c))
|
||||
- updated Text component to latest design specification ([#70](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/70)) ([8b8433d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8b8433deaf8fe66f917fd66fb56cb0a748bc6e6e))
|
||||
- updated Text component to latest design specification ([#70](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/70)) ([bb727f7](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bb727f70bea5a4c2ff5dba7e9b4428c0c84de7b2))
|
||||
- updating joke styling and updating jokes array ([#277](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/277)) ([0da27e2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0da27e2c46e9fc32efb80808715053c1f9f5165a))
|
||||
- use display: grid for calendarGrid ([b535a6e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b535a6eb32ac1091c8b5309ca3cfb6bab0b62526))
|
||||
- use downloadBlob util ([#186](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/186)) ([2dfb10e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2dfb10e57b51a08fcde2dc6638a81b5ec9bbc7ab))
|
||||
- use filter() instead of pop() ([063349d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/063349d96d0ad008fa0dc1f0a8c7a27cf3f108ce))
|
||||
- use React-icons ([8df9ea5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8df9ea55a9f0bcb04c90314cb311bba9d1ebf2e3))
|
||||
- UTRP v2 migration ([#292](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/292)) ([d22237d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d22237d561ddf6a2f8ed699ca4c11088a8b408e8))
|
||||
- working PNG and CAL downloads ([#119](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/119)) ([d9ee23c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d9ee23c5bbe27240a0e2849aae2eae81d7960bb5))
|
||||
- abhinavchadaga/course-catalog-popup ([#128](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/128)) ([745f9dd](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/745f9dd6fb96ccc0eace7189b87abf9a38f03828))
|
||||
- abhinavchadaga/reusable-popup-prompt ([#148](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/148)) ([44af9e1](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/44af9e16e4bf3b92c9a4fa0e779197f4f9ecb237))
|
||||
- actually sum for duplicate semesters (different uniques) ([#202](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/202)) ([d3f64ec](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d3f64ec79eb64ebe72ae28991127dbe521823842))
|
||||
- add button to the rows, use new ConflictsWithWarning component ([bec2649](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bec2649fc1244bffb5cb03809db62cc0ea477260))
|
||||
- add buttons with icons in tailwind ([93b65ac](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/93b65ac2edef3fc728305cc51d1a32ecf5073274))
|
||||
- add cal save buttons (no functionality) ([53e7c7f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/53e7c7fedaf1f268d2d7d06ff1a5df9e2af2306d))
|
||||
- add Calendar Component ([e0212d5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e0212d510928e5e95d3ba75e391c34714f1bb035))
|
||||
- add Calendar schedules component (clicking on storybook not working) ([d1a336e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d1a336e903f09b04b89f11ba7ede2f7d26e0ac3c))
|
||||
- add CalendarHeader and its Storybook, need to resize ([23276e5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/23276e5c7c907261b1d02e9006d8021d99b43984))
|
||||
- add check-path-alias custom ESLint rule ([#123](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/123)) ([208103d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/208103d7081abcf5b31bf36335320aaf7d213464))
|
||||
- add chrome.storage api mocks for storybook use ([#141](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/141)) ([3839bff](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/3839bff29ef6f0ecfad1e975e685df9a64d96c92))
|
||||
- add CSS for calendarCell div ([6cdcf49](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6cdcf4930d9868c0ed45820f66102d89a644cfb6))
|
||||
- add CSS for hourLine div ([0ba6153](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0ba61534cbfc83408cb64eb1145ecacef1ee8f04))
|
||||
- add CSS for timeLabelContainer div ([56f6456](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/56f6456ce85f77edb9d6fa64ecb82a8388feec41))
|
||||
- add custom ESLint rule restrict-import-depth ([#110](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/110)) ([8c069b7](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8c069b7ad339ed8db97bb5e3e6e88ab8d332489b))
|
||||
- add downloadBlob util ([2af351e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2af351efa85264da5abeee27fbd091d4ce637041))
|
||||
- add empty settings component - waiting on design ([9d0f210](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9d0f2105481cc3d5affdb4bd69d428379ea92de3))
|
||||
- add ImportantLinks Component ([5dbee6f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5dbee6f0c39e620c3b65a5e0156fd59ec8217ca7))
|
||||
- add List component ([e6b4049](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e6b40494031ca88894b2f918bb18448b70124481))
|
||||
- add MIMEType ([0c76052](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0c760524781556cf089f1885dc4ff3fd567bfc2b))
|
||||
- add new db powered by UT_Grade_Parser ([#163](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/163)) ([60d1f48](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/60d1f48bd95132f0517258f017a7644d8aff5101))
|
||||
- add react-loading-skeleton package ([#244](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/244)) ([b4dd91a](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b4dd91ad2595d9583a60530c2c2ff6d829d2d5e5))
|
||||
- add skeleton loader for course description + distribution ([#267](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/267)) ([c2cab40](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/c2cab407f3a07c1d3073e936c9d23cb1cecc9cb9))
|
||||
- add story for CalendarGridCell ([fd91c3b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/fd91c3b12ef5670563e689d190be75ef1f695d1c))
|
||||
- add Storybook story ([cb3cb5d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/cb3cb5d5fc18a68ca5a825f97b808b535aa1fe29))
|
||||
- add tailwind version of Button component ([28d93b3](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/28d93b3c25e1cbca3baea992a299d626ca823570))
|
||||
- add tickmarks to day div ([8b9cb06](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8b9cb065c28b5dd38b7e1681839a81f55691a3c6))
|
||||
- add time column to Calendar Component ([9d68211](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9d6821127e7ff9bf63cef659932ee7408de4c969))
|
||||
- add timeAndGrid div ([95e0544](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/95e0544b7301d382439b74038fb1b3e25dfe6d30))
|
||||
- added flag ;-; ([#195](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/195)) ([9b4d61c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9b4d61c2b0d8806b5d4a10296c1cc1266ac4229f))
|
||||
- added scrapedAt property ([#149](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/149)) ([8e181b3](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8e181b3010dfe25ea1e4b4ba181cbe9944d6e92e))
|
||||
- additional changes to [#201](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/201) ([#224](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/224)) ([bc354f3](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bc354f3798a02e6ebc3c66a8b5eee553c25a19d4))
|
||||
- aesthetically pleasing squishier course blocks when compressed ([#232](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/232)) ([438c82b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/438c82bfb5db0f570b261d15368a05447939b0fb))
|
||||
- align day labels and add to grid ([1bb6191](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/1bb6191244f40220a9af66cced4fe8f0c1e546a0))
|
||||
- alignment on calendar and header ([#109](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/109)) ([07ec5ab](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/07ec5abc3e1b40b801b7c977d21399b31222a4d3))
|
||||
- async course adding and async/other course block adjustments ([#273](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/273)) ([668c8d0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/668c8d00756a5e3e3daaade46380967c74eec1ae))
|
||||
- async text hiding on Calendar's Bottom Bar when there are no async courses ([#152](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/152)) ([0dff122](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0dff12232c29415fa7bc5ac3771393a406ab738d))
|
||||
- basic CalendarCourseMeeting component laid out - missing Text and Right Icon ([da9e7aa](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/da9e7aac4135afa5184e4949c36a288ef6ab62a7))
|
||||
- Best Practices ([#102](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/102)) ([5eb7be2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5eb7be246cd2a1db27c7ca777d81074ae7bb3b82))
|
||||
- beta builds ([#187](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/187)) ([8a5e12e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8a5e12ec63539ae7199f0f1561f642cb9cf19302))
|
||||
- better discord icon ([#205](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/205)) ([a3f5e0f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a3f5e0f27ffdfcb4e3ddd47140979aca379f9f42))
|
||||
- bold red refresh error ([#220](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/220)) ([229a8a2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/229a8a29e8d04dfbe15de200ceffc53b4f758413))
|
||||
- bottom bar for the calendar page ([#91](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/91)) ([0f730d6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0f730d6c50462c9f7d0428ebb379742305212ef6))
|
||||
- build without errors ([babc925](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/babc925967b01f1064b8d8e067e7bc792dd57b0a))
|
||||
- calendar components 3rd attempt at merging ([#60](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/60)) ([4faca8c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4faca8c43befe19c9b05ff4f3afba420e016bb59))
|
||||
- Calendar Components 3rd Attempt at Merging ([#60](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/60)) ([ab2cd68](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ab2cd688fae616742b39c0637291a65e508e23bd))
|
||||
- calendar course block component ([#75](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/75)) ([00e0019](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/00e00197efb807d420196f2b030fc4dc71e9cd42))
|
||||
- calendar course block component ([#75](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/75)) ([a41cb3e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a41cb3ed8711eff9a2fbbe435e867c073904f452))
|
||||
- calendar grid and grid cells ([#81](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/81)) ([bfeb239](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bfeb2398aacf9a1fba892737b6ef0286acbc326f))
|
||||
- Calendar Grid and Grid Cells ([#81](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/81)) ([dd2f696](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/dd2f696f8de2c0b291e917ef4d4cb2171b76712c))
|
||||
- calendar header formatting and data displaying ([#160](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/160)) ([5cce1c7](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5cce1c79fc2b50843426ed290a1b53849375e4eb))
|
||||
- calendar matchings ([#173](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/173)) ([791a42b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/791a42bcd49478024b6f1a237a2d2a9f6211749e))
|
||||
- Calendar Schedule component finished, fix: list didn't allow updates when adding a new schedule ([#115](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/115)) ([a99a557](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a99a55788a0b9791ff048c8bbbaad5bada47c41f))
|
||||
- calendar-course-cell-color-picker ([#157](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/157)) ([df18491](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/df1849180de2c793c10e3a49d2c862d236456ec2))
|
||||
- can open tabs, updated injected popup heading. basically done ([35fab34](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/35fab3444582b436f63c7b16a26ebd5989c4bf3e))
|
||||
- change Chip to tailwind css. Fixed eslint for ConflictsWithWarning ([3568b8e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/3568b8eb5ee9ce3fe17f77f5641c0583bacc17a4))
|
||||
- check-path-alias autofix ([#124](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/124)) ([265652c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/265652c4200a56b1ae62e6e94e0609fb4604c188))
|
||||
- chrome extension works ([35f3c72](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/35f3c7225049d760c6061ed827aa796514291e62))
|
||||
- color palette for calendar ([#118](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/118)) ([471e55d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/471e55dcea1ae439658d00ed41570e2f218f0c3d))
|
||||
- Conventional Commits ([#103](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/103)) ([fe599df](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/fe599dfe7514ead880596d3b75c8370bde796e09))
|
||||
- convert all LabelsAndDetails Components to Tailwind ([9e0f9df](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9e0f9df9de1a5c99657ddcef3b2eb101dd825b84))
|
||||
- course color generation ([#179](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/179)) ([5ed81e4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5ed81e4be99a8b7f3c68a3ba70358e4cbe5cc613))
|
||||
- course colors ([#175](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/175)) ([dc77cc2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/dc77cc27da7ab521e290a8c9ba810573e677b6b8))
|
||||
- Course Flag Tooltips ([#178](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/178)) ([ba2bc60](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ba2bc60add2b87ccd4e9894c454d7a8d8382f0dd))
|
||||
- course-catalog-injected-popup ([#98](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/98)) ([89d03f4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/89d03f4244d324915cadadaf75a827221525d43f))
|
||||
- CourseStatus Component implemented ([#83](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/83)) ([58d7df4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/58d7df499ca9a8dfd4dc3aa47bc9d76837f21bf7))
|
||||
- CourseStatus Component implemented ([#83](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/83)) ([fa1d737](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/fa1d7374bcafbe19673dc31f33b3f8075e45f37a))
|
||||
- create empty Popup story ([ad83ba4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ad83ba4cdc67f86096d0cbc2cb6659aa9be98a0b))
|
||||
- Create icon helper ([#77](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/77)) ([ccea0f4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ccea0f4bd1e8d66dc0717d92c9e970040d9fbe9e))
|
||||
- Create icon helper ([#77](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/77)) ([1b51d65](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/1b51d65c89a4b544f4f2a9c60106e14300f9a3b0))
|
||||
- Derek vinson/calendar header ([#94](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/94)) ([12d09b5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/12d09b54cbf840993d17323b96cf274ec24c67ce))
|
||||
- Derek/disable updating ([#239](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/239)) ([0ab83ef](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0ab83efd47d75fb6c278994c7dc8a6d22b4b6b83))
|
||||
- Derek/export png ([#95](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/95)) ([58c2b46](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/58c2b4634a9d065ca156d27c4af69a504b7c2d2d))
|
||||
- DialogProvider component ([#198](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/198)) ([d1b921a](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d1b921a5b000693d7f3dabaf84d8b9580c361941))
|
||||
- dividers in calendar bottom bar ([#120](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/120)) ([eba5d9f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/eba5d9f508576d2648bd2501e5d5aaff32592566))
|
||||
- **docs:** add extra acknowledgements ([e2cbfa3](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e2cbfa3f48cb0b10a43134cf9784a44b13aa542d))
|
||||
- drag only on vertical axis ([34a6449](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/34a644952995d4aedd18d11d327bd8589f5b2610))
|
||||
- early iteration of non-virtual list ([677aa62](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/677aa624d7fb2bbb922aaf8806abe979c3399e4e))
|
||||
- enable TS strict mode ([#168](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/168)) ([efed1c0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/efed1c0edbf93987c551952a03c9c4b3c461d819))
|
||||
- experimental toggle for icons, left off for now ([#237](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/237)) ([f34dd95](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/f34dd95d7765322b6cecb2e817a46e0549d929a5))
|
||||
- fall 2023 grades ([#226](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/226)) ([863d980](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/863d980b2d1c706b1fc648f1a0de2a31515e339e))
|
||||
- finally fix grid JSX.Element generation ([e49fc29](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e49fc295ba40a37f9bf59686adbd8b4f523cd9c6))
|
||||
- finish ScheduleTotalHoursAndCourses ([12f680d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/12f680d7e9aef826281c8f769825408dc6f9f9de))
|
||||
- fix icons on PopupMain and convert to tailwind ([#108](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/108)) ([87799d8](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/87799d8f02aa5986025982f768639afdb1b0267a))
|
||||
- fix save as button dividers ([#153](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/153)) ([7986549](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7986549fdd40834a85bedbfd0862cdb21c9ae657))
|
||||
- grades by professor ([#225](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/225)) ([78d749a](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/78d749a8a96f63c32663b464e5b663f932c28ed0))
|
||||
- html2canvas -> htmlToImage and fix derick's bugs ([bda0282](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bda02826b14b96fd65a6f24218950e510c5aa3d8))
|
||||
- icon added successfully ([3b588c2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/3b588c20394523ddf90456e1ca58546e5b820c74))
|
||||
- implement Chip component ([23e881f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/23e881f14cc80100d8f8deef6a25daaa72d83f07))
|
||||
- implement flatten course schedule helper function ([e54f488](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e54f488b170d5a9af646c1ac82e8b33579fef61d))
|
||||
- implemented ConflictsWithWarning ([93f3a30](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/93f3a307b4acce6cc55fc411f1ea2d7576006b00))
|
||||
- implemented InfoCard ([21b6430](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/21b643000d884442e025534802be6bb1f6ba68e2))
|
||||
- Initial Splash Text Commit ([#208](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/208)) ([0534f60](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0534f607a51f1dd0905f6f45f08519cba65ea05a))
|
||||
- limit schedules to 10 ([#272](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/272)) ([290b841](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/290b8415170bca631e5d71b08d88db80b02163f0))
|
||||
- link to your registered courses ([#228](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/228)) ([f83e012](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/f83e012d62e268d95dce19a30e54e34f3779142b))
|
||||
- list reordering ([#154](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/154)) ([038ebaa](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/038ebaa2689c14bc9906afb542ef62c21cb13177))
|
||||
- listed versioning for beta builds ([#192](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/192)) ([0c42979](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0c429794230a91c4fd949715b7eea210ac18fee3))
|
||||
- lowercase instructor! ([#268](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/268)) ([9ec05ef](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9ec05ef764b2451c05e2d084d92074fb1e984268))
|
||||
- made List more extensible ([cd34601](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/cd34601379d5cd2b53d967afbc954178834823b9))
|
||||
- match calendar designs & add functionality ([#176](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/176)) ([8027c3d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8027c3d1bf228053acbad3e232b030c9ddbaca6a))
|
||||
- migrate styles to TailwindCSS ([7e2f5ea](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7e2f5eaed7d782bc886545f7241e0891d55cfae0))
|
||||
- migration update showing ([#293](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/293)) ([aede681](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/aede681d4bb5b01723eb9d68527f4e64991adfab))
|
||||
- missed one chip toggle there ([#245](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/245)) ([5ca24da](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5ca24dab82509d34cf43af4359940476159d052f))
|
||||
- newer grades and parameterized queries ([#238](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/238)) ([75ad416](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/75ad4167b6639bbcf58e2dc6dafc80ed4656a899))
|
||||
- one single exclamation mark did all that ([#235](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/235)) ([be87e41](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/be87e4181430e0df41683be5c9d0a1ce992b2c61))
|
||||
- open an injected course page on course block click in popup main ([#146](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/146)) ([2709484](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/27094846f70feb8d83c8d464ab0fa8dcd99b3e71))
|
||||
- pad unique ids to 5 digits ([#170](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/170)) ([b4ad687](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b4ad6870bfc88f68bdd7f87fe1d94c23d0a02b95))
|
||||
- parallelize initializeDB.ts promises ([9f1dcc6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9f1dcc667df9857e3d0469fe7112a9f985aad490))
|
||||
- popout icon for ccip in calendar ([#221](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/221)) ([6812d68](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6812d685d08902f7b3d9d2e5293350c8170c6f06))
|
||||
- popup schedule select ([#126](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/126)) ([7f2a589](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7f2a5893d4dd82a0f077dca6dfd28e79f929766f))
|
||||
- PopupCourseBlock Component ([#79](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/79)) ([9accd17](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9accd17bd418f8c9334e80b0f7e38673092ba208))
|
||||
- PopupCourseBlock Component ([#79](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/79)) ([f045b40](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/f045b400a56569f7353eb00ecd0d081b68660fc5))
|
||||
- proper injected styles ([#164](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/164)) ([e919e96](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e919e96c53bc9bec7db3d09693d1ff0684b56e87))
|
||||
- readme animation ([#281](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/281)) ([da6d86c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/da6d86c785f92f70f52ef0b4cab3dc14e0bd9790))
|
||||
- refactor all components in common ([e544312](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e5443122b422266a126de497b475020e298b2f1c))
|
||||
- refactor all components in injected ([0c44849](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0c44849e15333c3a5cbdf8972eb31ada0e830b0a))
|
||||
- refactor calendar ([28f1924](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/28f192472b7f244253b6a0b5339491b9740880f7))
|
||||
- Refactor database initialization code ([5e98f45](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5e98f45210346f0e7bb69af5cf25e336c76416ca))
|
||||
- release notes ([#283](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/283)) ([bd17e33](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bd17e3353792df70ca2e33c8b7a32b5fb9acc7d2))
|
||||
- report issue popup ([#261](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/261)) ([65ff6bf](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/65ff6bfbbfc6b696621628e448865155d0405f7f))
|
||||
- rerouted directory to syllabus when click on professor name ([#211](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/211)) ([8959e0d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8959e0d9f7fdd82125f391f1ec51aa390ba9450d))
|
||||
- sam's jokes ([#278](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/278)) ([895cd31](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/895cd31d8eb40e7e99fc0969b543e95bd070f6b7))
|
||||
- save as PNG functionality ([ad18fbd](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ad18fbd16235be0c75121b527867fac708670300))
|
||||
- schedule list item action menu ([#230](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/230)) ([15fc369](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/15fc3699cfda3ad56e5a262bd37dca166e2685dd))
|
||||
- screenshot whole page, hide certain elements, screenshot fixed size ([#180](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/180)) ([7d4c5d7](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7d4c5d7be8b266b5f3caf2135c4f3fecb96d75be))
|
||||
- settings page ([#260](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/260)) ([7a5c3a2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7a5c3a2e62c3a726735f9fe921f9e08d4092d0f9))
|
||||
- show async courses in the bottom bar ([#204](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/204)) ([227de53](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/227de53e8453804f0791f269dae64ec388136390))
|
||||
- some small changes for colors and font ([#201](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/201)) ([bae1da4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bae1da43d3373e12d593682e9fb69e19557b5749))
|
||||
- splash text additions before v2 release ([#296](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/296)) ([e774f31](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e774f316e3f92c03e79274a55f1f729178c9a14a))
|
||||
- splash text has arrived! ([#246](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/246)) ([9971435](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9971435716dec11e954dc26c14bf7d1505563d0d))
|
||||
- Storybook for Vite ([#52](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/52)) ([9cc299c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9cc299ced6132644a5c2375b95a8a16a3482601b))
|
||||
- swe title updates ([#310](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/310)) ([4629626](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4629626a31934c491758ddca874e5018d9c22e57))
|
||||
- switch button ([#229](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/229)) ([abae7a5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/abae7a5c22ab944262d66ae897298b008a03c5f1))
|
||||
- temporary removal of waitlist etc ([#236](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/236)) ([d424ccc](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d424ccce49ba05305b24cb61aeb4ffeada5bab33))
|
||||
- UnoCSS (TailwindCSS) (Storybook only) ([#61](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/61)) ([85c7f78](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/85c7f7817c58f13f6a4e8bec13a45f6412ad87db))
|
||||
- UnoCSS (TailwindCSS) (Storybook only) ([#61](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/61)) ([6521a4b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6521a4b2a90af06e182c779f2fc7cb1a3b1e55c1))
|
||||
- unplugin-icons ([#62](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/62)) ([2d67b12](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2d67b1218ff852c59901b77909615c5f54794a67))
|
||||
- unplugin-icons ([#62](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/62)) ([945e09b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/945e09b49e4ffa9bd4b02442f6567bb99831923e))
|
||||
- update admin titles ([4cf8c3f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4cf8c3f9645cd8fda07244413cdc406105b12fec))
|
||||
- update badge count when schedule changes ([#150](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/150)) ([a5e9e3c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a5e9e3c2145d61d8cc5788eb50fa19718e6d13bf))
|
||||
- update Button to v2 design ([863521f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/863521fb3bb268131bd3f369064ae10a442b4fbc))
|
||||
- update dialog component to headlessui ([#159](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/159)) ([442be8c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/442be8cbee06ec403467b100d8d5300dd4a7ea72))
|
||||
- update with TailwindCSS ([f3a8a7d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/f3a8a7db560a6b28f96a2e976c8ed33af97fe77a))
|
||||
- updated calendar page and recruitment banner links ([#219](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/219)) ([a2303ee](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a2303ee35f10aba5fa7ff5e307d0092a39f18830))
|
||||
- updated divider component ([#99](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/99)) ([8ab60c9](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8ab60c9f018143df594e7bfd0d321666289cc28c))
|
||||
- updated Text component to latest design specification ([#70](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/70)) ([8b8433d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8b8433deaf8fe66f917fd66fb56cb0a748bc6e6e))
|
||||
- updated Text component to latest design specification ([#70](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/70)) ([bb727f7](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bb727f70bea5a4c2ff5dba7e9b4428c0c84de7b2))
|
||||
- updating joke styling and updating jokes array ([#277](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/277)) ([0da27e2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0da27e2c46e9fc32efb80808715053c1f9f5165a))
|
||||
- use display: grid for calendarGrid ([b535a6e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b535a6eb32ac1091c8b5309ca3cfb6bab0b62526))
|
||||
- use downloadBlob util ([#186](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/186)) ([2dfb10e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2dfb10e57b51a08fcde2dc6638a81b5ec9bbc7ab))
|
||||
- use filter() instead of pop() ([063349d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/063349d96d0ad008fa0dc1f0a8c7a27cf3f108ce))
|
||||
- use React-icons ([8df9ea5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8df9ea55a9f0bcb04c90314cb311bba9d1ebf2e3))
|
||||
- UTRP v2 migration ([#292](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/292)) ([d22237d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d22237d561ddf6a2f8ed699ca4c11088a8b408e8))
|
||||
- working PNG and CAL downloads ([#119](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/119)) ([d9ee23c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d9ee23c5bbe27240a0e2849aae2eae81d7960bb5))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- add margin-top: -10px to p ([18406b0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/18406b0c94604b1b524de03bf97e78cffe7a5bbf))
|
||||
- added room number to course popup ([#231](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/231)) ([9eaff24](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9eaff24cbb6d78d7f28521a07c32630a90a96f82))
|
||||
- align timeBlock div ([4dc8957](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4dc8957c459d0ee57e40c67fa61194ace832327e))
|
||||
- async course display size ([#181](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/181)) ([949bbb0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/949bbb0835bfb55a81f0800850aa084b4ed1bfc4))
|
||||
- broken close bracket ([b34aacb](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b34aacb06772af6f83d1f7bafe828ba43a10db85))
|
||||
- broken file ([92462cf](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/92462cf0dffa222d75965885ee0e63181bce9890))
|
||||
- bugs ([2a01506](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2a0150600f26af5b7394f878e13c4d19611f3f64))
|
||||
- build ([60ab140](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/60ab140c556ec56eca78b1f4e280dfb6e9dbb538))
|
||||
- build errors and merge in Casey's branch (driodiwb) ([39947b3](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/39947b3694f42f9d5ad2f21d044585aa3d12c407))
|
||||
- build errors and restructure Calendar page ([c6a48dd](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/c6a48dd3f6025fe531c18e12b8730683d63f67ed))
|
||||
- calendar course cell colors ([74be880](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/74be880f9d2dc340ba48416429711fb28915baa2))
|
||||
- calendar storybook issue ([#125](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/125)) ([0c5bec8](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0c5bec8002022cb4702f3879f43fcd32b9fa6b5a))
|
||||
- Calendar View/Scaling Issues ([#144](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/144)) ([4c61ebd](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4c61ebd3fc93240903f1224f9dd3057a23d30046))
|
||||
- CalendarCourseCell spacing ([13c69ef](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/13c69ef862f0214dfa6c9368fc3463519aa8f2f0))
|
||||
- change Chromatic action to build current branch, not base branch ([#100](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/100)) ([e73c9fe](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e73c9fe417169e73c395cde1aeb841eee8add579))
|
||||
- change material icons to material symbols ([#71](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/71)) ([52e34cb](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/52e34cb830ff1f12659703776bd3d9a24815ee00))
|
||||
- chromatic build ([11303da](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/11303daebcc1e4de340463e6266082dc0920582d))
|
||||
- chromatic builds on PRs ([#140](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/140)) ([78a6939](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/78a69399292767a1f8eac66e4c86cbcb8aad019d))
|
||||
- chromatic storybook - CourseCatalogInjectedPopup ([#106](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/106)) ([ced2997](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ced29975b20a2a5beac51ea875627a2b1042e5c2))
|
||||
- clean up [#173](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/173) ([#174](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/174)) ([afa634f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/afa634f0853dc16cf313c129434d49bf132388d0))
|
||||
- cleanup imports ([#112](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/112)) ([b17c3fa](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b17c3fae6d9a8fdb2f6de84dc28d00bc31e7bcc8))
|
||||
- conflict row bug ([#130](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/130)) ([a8ea3bc](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a8ea3bc683f88fc33191a8cacf649be83f6758f8))
|
||||
- ConflictsWithWarning ([1599e48](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/1599e48d75d4877b3515c943120d83b59d084a55))
|
||||
- correct parsing of noon ([#155](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/155)) ([91f62e1](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/91f62e1943633eaef7dc8d437e6a7397456a9123))
|
||||
- db with proper insertion order ([8e79d6a](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8e79d6a6a87210e0a30584c0c9ceb60bd3a33665))
|
||||
- delete storybook timestamp file ([f93a98e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/f93a98e46ae76e9b1474c8143bfc3f5076e8b91f))
|
||||
- dialog movement ([#227](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/227)) ([bc5d68c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bc5d68ce18f818c285ef7f5eda03f499365ee2e8))
|
||||
- disabled [object Object] hover message on dialog popups ([#284](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/284)) ([dcc1d81](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/dcc1d81a74922ba27ad2a6859f978f3f20136178))
|
||||
- divider usage in HeaderAndActions ([#113](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/113)) ([84e8320](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/84e8320e8fce5961877a5745e89419a722055ed5))
|
||||
- doesn't autoload on pages that don't have pages to load ([#270](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/270)) ([88eeb62](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/88eeb620aec52d6e4172cc908843339412ade90b))
|
||||
- don't crash on cultural diversity flag ([#196](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/196)) ([5b1e451](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5b1e4513e29203a54ed9f4913c227d6c45d2e67a))
|
||||
- eslint and remove React-beautiful-dnd ([6af805b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6af805ba3a7b04b7e5705d3d597b5ace686d61e0))
|
||||
- extra space ([0f43796](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0f43796cd864acd012fc1242cbdf34844df62406))
|
||||
- Fix popup drag hitbox ([#216](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/216)) ([7a40008](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7a40008c1e3846f09ac4eeadc4a50f810ffc0d90))
|
||||
- fixed bug where activeSchedule doesn't update correctly ([#158](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/158)) ([a409090](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a409090b904686b76a5db52af980e81108b97082))
|
||||
- fixed bug with course cells after 12 PM extending past midnight ([#122](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/122)) ([f22a3cd](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/f22a3cd7c0df138ca6ecd4b4a4efaa7ce845c3dd))
|
||||
- fixed issues involving course meeting objects not being recognized as course meeting objects ([#132](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/132)) ([3406e9a](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/3406e9a0e2644218fc06cbd0b05046df5339264c))
|
||||
- Fixed typescript error ([#161](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/161)) ([df7a7c6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/df7a7c65d695cb5c5a825ac9f5ecc48438735b65))
|
||||
- grade dist when no instructor ([#269](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/269)) ([6a363ae](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6a363aeb5ccb1c11e15db317b34c4f04a3c9aafc))
|
||||
- grid JSX.Element generation ([b691bf3](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b691bf3231d5002a631687f26c7409e311237662))
|
||||
- icon library resolution ([#74](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/74)) ([bb3b313](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bb3b313fd272bba12f99270c912e046df5358fed))
|
||||
- idk why that comment was there ([#177](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/177)) ([c5fc621](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/c5fc6219e1540e1fb889d8bedc7296190103a56b))
|
||||
- import error ([152bc45](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/152bc4577685605ae95f39d9e670b5449e32e0e9))
|
||||
- improper list data propagation ([#240](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/240)) ([149fda3](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/149fda3b721dcc3d8ffa6c2398c28e39dcb62aaa))
|
||||
- improve dialog handling and error management in list items ([#257](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/257)) ([1942508](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/1942508d8d11f51b62806894ae13162b6404753b))
|
||||
- injection not working from som/elie commit ([#145](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/145)) ([591687e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/591687eee8731b3649a5f8c1357a416eb2992a93))
|
||||
- list component fixed ([#162](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/162)) ([5714ed1](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5714ed16d72c5442f58366ac51f40cc32ea406ab))
|
||||
- made list draggable only by handle ([cbb190b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/cbb190bf4b3ad694c87b650489e1fca5aa6b7826))
|
||||
- margins on list component ([cc71389](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/cc7138949cf77d1bcf1520633d669e939540575e))
|
||||
- non-determinstic options page generation ([#137](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/137)) ([d700110](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d70011016a45f5de62b06839a31c634637024f4a))
|
||||
- non-virtual dnd ([837fddf](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/837fddf8048350b0b25a58bbd16c1957fbad6edb))
|
||||
- old icon removed in .tsx ([4d387e8](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4d387e8063314c7c6d7452b746728e2cf85a1787))
|
||||
- only show button hover effects when not disabled ([8e3aa7e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8e3aa7ef3380836fd36a12698b1f6982e110c0f7))
|
||||
- openTabFromContentScript TS issue ([3a48859](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/3a48859dddc0e85649ff6a949e9afd22c962825d))
|
||||
- options page ([#131](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/131)) ([dc100b5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/dc100b5d3ac1db5f1dc011ea2536c286f56fa55b))
|
||||
- reactivity ([#188](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/188)) ([4f4f34e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4f4f34e281e8aabcdbf8b06652d05bca3554b11f))
|
||||
- README.md ([a30fecf](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a30fecf8ec76ecae03be9bb4d813803b9ffdb490))
|
||||
- README.md ([b27b21b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b27b21bbfcc8fbdc130c3fa85776479451745b7b))
|
||||
- refactor AST parsing for custom ESLint rule ([62f0851](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/62f0851406bf82d3d0481fed40462bdd30ffcf1b))
|
||||
- remote react-window fully (from List component) ([#114](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/114)) ([5f1c023](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5f1c0231e483bc571ef944bbc0b950ab20d31028))
|
||||
- remove course name from the syllabi lookup ([#200](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/200)) ([bcb5a8c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bcb5a8c469c4b5c1909880ab6789d67d04487ed2))
|
||||
- remove extra spacing ([#121](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/121)) ([6ba8b68](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6ba8b68654dc182bd2a084f93b0f4f7d5c132862))
|
||||
- remove white space after duplicate schedule regex matching ([#286](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/286)) ([d73615e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d73615e28129ee7d78604c6b295ec572b11be46e))
|
||||
- rename to course block and fix line height for styling ([b602b0b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b602b0b895416a26214e934f9650a8ec0a10711e))
|
||||
- revert "chore: add default story" ([aef8c3d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/aef8c3d9873c6df79f6a54ab4c669ee1849b5ea5))
|
||||
- revert "rename to course block and fix line height for styling" ([0273a23](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0273a239139c0f3c0ad773ed8ab1cd4ad8d22fa3))
|
||||
- revert CalendarGrid and CalendarGridCell back to SCSS from Tailwind ([56306ab](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/56306ab9440baf5ca1158f95d788de7ba42d73d8))
|
||||
- revert last commit ([27d945f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/27d945f57c76a1c2f31fee9f98c324fab6183bb6))
|
||||
- Schedule Switching Bugs ([#138](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/138)) ([c51e688](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/c51e6881d186e8bef056e483e2bca95585d5eac6))
|
||||
- support classes with no location ([#242](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/242)) ([3ff06e0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/3ff06e043b112bc232c9c173e72b2ea99609cf7a))
|
||||
- theme colors ([2f537b4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2f537b4f3e3946241f2e287d9ae075e5a2aa7087))
|
||||
- type issues by using correct import ([#111](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/111)) ([19fe070](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/19fe070491be9e2d40f12918561f6f6ac5921237))
|
||||
- **ui:** unhid settings button ([#288](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/288)) ([db1eac3](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/db1eac33a2786a3db87f0e6b3679233a85ab923f))
|
||||
- undefined color case ([9aa78a0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9aa78a02a5d4f68daa062e4892875cf5f568306c))
|
||||
- unocss theme color namings ([79d7832](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/79d7832d0987b8a7c9650b984f72ffd702682423))
|
||||
- update alignment ([7eb3113](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7eb3113adaa0917c0275576c3366c387bfe77440))
|
||||
- update daysOfWeek with new DAY_MAP keys ([ecdaadb](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ecdaadb83eb21b144d07d573613fd8971f0a5ce2))
|
||||
- visual overflow bug when editing schedule name ([#251](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/251)) ([e8d2c2e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e8d2c2e1429072fcc1173ee770f06b07e9ddda20))
|
||||
- vitest path alias bug ([ee37897](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ee37897df4f65cd2d285e2ad628be43aebb21ea1))
|
||||
- add margin-top: -10px to p ([18406b0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/18406b0c94604b1b524de03bf97e78cffe7a5bbf))
|
||||
- added room number to course popup ([#231](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/231)) ([9eaff24](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9eaff24cbb6d78d7f28521a07c32630a90a96f82))
|
||||
- align timeBlock div ([4dc8957](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4dc8957c459d0ee57e40c67fa61194ace832327e))
|
||||
- async course display size ([#181](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/181)) ([949bbb0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/949bbb0835bfb55a81f0800850aa084b4ed1bfc4))
|
||||
- broken close bracket ([b34aacb](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b34aacb06772af6f83d1f7bafe828ba43a10db85))
|
||||
- broken file ([92462cf](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/92462cf0dffa222d75965885ee0e63181bce9890))
|
||||
- bugs ([2a01506](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2a0150600f26af5b7394f878e13c4d19611f3f64))
|
||||
- build ([60ab140](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/60ab140c556ec56eca78b1f4e280dfb6e9dbb538))
|
||||
- build errors and merge in Casey's branch (driodiwb) ([39947b3](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/39947b3694f42f9d5ad2f21d044585aa3d12c407))
|
||||
- build errors and restructure Calendar page ([c6a48dd](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/c6a48dd3f6025fe531c18e12b8730683d63f67ed))
|
||||
- calendar course cell colors ([74be880](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/74be880f9d2dc340ba48416429711fb28915baa2))
|
||||
- calendar storybook issue ([#125](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/125)) ([0c5bec8](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0c5bec8002022cb4702f3879f43fcd32b9fa6b5a))
|
||||
- Calendar View/Scaling Issues ([#144](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/144)) ([4c61ebd](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4c61ebd3fc93240903f1224f9dd3057a23d30046))
|
||||
- CalendarCourseCell spacing ([13c69ef](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/13c69ef862f0214dfa6c9368fc3463519aa8f2f0))
|
||||
- change Chromatic action to build current branch, not base branch ([#100](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/100)) ([e73c9fe](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e73c9fe417169e73c395cde1aeb841eee8add579))
|
||||
- change material icons to material symbols ([#71](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/71)) ([52e34cb](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/52e34cb830ff1f12659703776bd3d9a24815ee00))
|
||||
- chromatic build ([11303da](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/11303daebcc1e4de340463e6266082dc0920582d))
|
||||
- chromatic builds on PRs ([#140](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/140)) ([78a6939](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/78a69399292767a1f8eac66e4c86cbcb8aad019d))
|
||||
- chromatic storybook - CourseCatalogInjectedPopup ([#106](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/106)) ([ced2997](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ced29975b20a2a5beac51ea875627a2b1042e5c2))
|
||||
- clean up [#173](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/173) ([#174](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/174)) ([afa634f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/afa634f0853dc16cf313c129434d49bf132388d0))
|
||||
- cleanup imports ([#112](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/112)) ([b17c3fa](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b17c3fae6d9a8fdb2f6de84dc28d00bc31e7bcc8))
|
||||
- conflict row bug ([#130](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/130)) ([a8ea3bc](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a8ea3bc683f88fc33191a8cacf649be83f6758f8))
|
||||
- ConflictsWithWarning ([1599e48](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/1599e48d75d4877b3515c943120d83b59d084a55))
|
||||
- correct parsing of noon ([#155](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/155)) ([91f62e1](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/91f62e1943633eaef7dc8d437e6a7397456a9123))
|
||||
- db with proper insertion order ([8e79d6a](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8e79d6a6a87210e0a30584c0c9ceb60bd3a33665))
|
||||
- delete storybook timestamp file ([f93a98e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/f93a98e46ae76e9b1474c8143bfc3f5076e8b91f))
|
||||
- dialog movement ([#227](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/227)) ([bc5d68c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bc5d68ce18f818c285ef7f5eda03f499365ee2e8))
|
||||
- disabled [object Object] hover message on dialog popups ([#284](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/284)) ([dcc1d81](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/dcc1d81a74922ba27ad2a6859f978f3f20136178))
|
||||
- divider usage in HeaderAndActions ([#113](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/113)) ([84e8320](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/84e8320e8fce5961877a5745e89419a722055ed5))
|
||||
- doesn't autoload on pages that don't have pages to load ([#270](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/270)) ([88eeb62](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/88eeb620aec52d6e4172cc908843339412ade90b))
|
||||
- don't crash on cultural diversity flag ([#196](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/196)) ([5b1e451](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5b1e4513e29203a54ed9f4913c227d6c45d2e67a))
|
||||
- eslint and remove React-beautiful-dnd ([6af805b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6af805ba3a7b04b7e5705d3d597b5ace686d61e0))
|
||||
- extra space ([0f43796](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0f43796cd864acd012fc1242cbdf34844df62406))
|
||||
- Fix popup drag hitbox ([#216](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/216)) ([7a40008](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7a40008c1e3846f09ac4eeadc4a50f810ffc0d90))
|
||||
- fixed bug where activeSchedule doesn't update correctly ([#158](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/158)) ([a409090](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a409090b904686b76a5db52af980e81108b97082))
|
||||
- fixed bug with course cells after 12 PM extending past midnight ([#122](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/122)) ([f22a3cd](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/f22a3cd7c0df138ca6ecd4b4a4efaa7ce845c3dd))
|
||||
- fixed issues involving course meeting objects not being recognized as course meeting objects ([#132](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/132)) ([3406e9a](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/3406e9a0e2644218fc06cbd0b05046df5339264c))
|
||||
- Fixed typescript error ([#161](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/161)) ([df7a7c6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/df7a7c65d695cb5c5a825ac9f5ecc48438735b65))
|
||||
- grade dist when no instructor ([#269](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/269)) ([6a363ae](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6a363aeb5ccb1c11e15db317b34c4f04a3c9aafc))
|
||||
- grid JSX.Element generation ([b691bf3](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b691bf3231d5002a631687f26c7409e311237662))
|
||||
- icon library resolution ([#74](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/74)) ([bb3b313](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bb3b313fd272bba12f99270c912e046df5358fed))
|
||||
- idk why that comment was there ([#177](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/177)) ([c5fc621](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/c5fc6219e1540e1fb889d8bedc7296190103a56b))
|
||||
- import error ([152bc45](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/152bc4577685605ae95f39d9e670b5449e32e0e9))
|
||||
- improper list data propagation ([#240](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/240)) ([149fda3](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/149fda3b721dcc3d8ffa6c2398c28e39dcb62aaa))
|
||||
- improve dialog handling and error management in list items ([#257](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/257)) ([1942508](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/1942508d8d11f51b62806894ae13162b6404753b))
|
||||
- injection not working from som/elie commit ([#145](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/145)) ([591687e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/591687eee8731b3649a5f8c1357a416eb2992a93))
|
||||
- list component fixed ([#162](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/162)) ([5714ed1](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5714ed16d72c5442f58366ac51f40cc32ea406ab))
|
||||
- made list draggable only by handle ([cbb190b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/cbb190bf4b3ad694c87b650489e1fca5aa6b7826))
|
||||
- margins on list component ([cc71389](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/cc7138949cf77d1bcf1520633d669e939540575e))
|
||||
- non-determinstic options page generation ([#137](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/137)) ([d700110](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d70011016a45f5de62b06839a31c634637024f4a))
|
||||
- non-virtual dnd ([837fddf](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/837fddf8048350b0b25a58bbd16c1957fbad6edb))
|
||||
- old icon removed in .tsx ([4d387e8](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4d387e8063314c7c6d7452b746728e2cf85a1787))
|
||||
- only show button hover effects when not disabled ([8e3aa7e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8e3aa7ef3380836fd36a12698b1f6982e110c0f7))
|
||||
- openTabFromContentScript TS issue ([3a48859](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/3a48859dddc0e85649ff6a949e9afd22c962825d))
|
||||
- options page ([#131](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/131)) ([dc100b5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/dc100b5d3ac1db5f1dc011ea2536c286f56fa55b))
|
||||
- reactivity ([#188](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/188)) ([4f4f34e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4f4f34e281e8aabcdbf8b06652d05bca3554b11f))
|
||||
- README.md ([a30fecf](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a30fecf8ec76ecae03be9bb4d813803b9ffdb490))
|
||||
- README.md ([b27b21b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b27b21bbfcc8fbdc130c3fa85776479451745b7b))
|
||||
- refactor AST parsing for custom ESLint rule ([62f0851](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/62f0851406bf82d3d0481fed40462bdd30ffcf1b))
|
||||
- remote react-window fully (from List component) ([#114](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/114)) ([5f1c023](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5f1c0231e483bc571ef944bbc0b950ab20d31028))
|
||||
- remove course name from the syllabi lookup ([#200](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/200)) ([bcb5a8c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/bcb5a8c469c4b5c1909880ab6789d67d04487ed2))
|
||||
- remove extra spacing ([#121](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/121)) ([6ba8b68](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6ba8b68654dc182bd2a084f93b0f4f7d5c132862))
|
||||
- remove white space after duplicate schedule regex matching ([#286](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/286)) ([d73615e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d73615e28129ee7d78604c6b295ec572b11be46e))
|
||||
- rename to course block and fix line height for styling ([b602b0b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b602b0b895416a26214e934f9650a8ec0a10711e))
|
||||
- revert "chore: add default story" ([aef8c3d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/aef8c3d9873c6df79f6a54ab4c669ee1849b5ea5))
|
||||
- revert "rename to course block and fix line height for styling" ([0273a23](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0273a239139c0f3c0ad773ed8ab1cd4ad8d22fa3))
|
||||
- revert CalendarGrid and CalendarGridCell back to SCSS from Tailwind ([56306ab](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/56306ab9440baf5ca1158f95d788de7ba42d73d8))
|
||||
- revert last commit ([27d945f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/27d945f57c76a1c2f31fee9f98c324fab6183bb6))
|
||||
- Schedule Switching Bugs ([#138](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/138)) ([c51e688](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/c51e6881d186e8bef056e483e2bca95585d5eac6))
|
||||
- support classes with no location ([#242](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/242)) ([3ff06e0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/3ff06e043b112bc232c9c173e72b2ea99609cf7a))
|
||||
- theme colors ([2f537b4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2f537b4f3e3946241f2e287d9ae075e5a2aa7087))
|
||||
- type issues by using correct import ([#111](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/111)) ([19fe070](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/19fe070491be9e2d40f12918561f6f6ac5921237))
|
||||
- **ui:** unhid settings button ([#288](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/288)) ([db1eac3](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/db1eac33a2786a3db87f0e6b3679233a85ab923f))
|
||||
- undefined color case ([9aa78a0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/9aa78a02a5d4f68daa062e4892875cf5f568306c))
|
||||
- unocss theme color namings ([79d7832](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/79d7832d0987b8a7c9650b984f72ffd702682423))
|
||||
- update alignment ([7eb3113](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/7eb3113adaa0917c0275576c3366c387bfe77440))
|
||||
- update daysOfWeek with new DAY_MAP keys ([ecdaadb](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ecdaadb83eb21b144d07d573613fd8971f0a5ce2))
|
||||
- visual overflow bug when editing schedule name ([#251](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/251)) ([e8d2c2e](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/e8d2c2e1429072fcc1173ee770f06b07e9ddda20))
|
||||
- vitest path alias bug ([ee37897](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/ee37897df4f65cd2d285e2ad628be43aebb21ea1))
|
||||
|
||||
### Reverts
|
||||
|
||||
- Revert "individual bug fix" ([b4e8c75](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b4e8c7589e53f1064d70703459cc6d66fae1b04c))
|
||||
- color palette for calendar ([#118](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/118)) ([51bbd65](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/51bbd6590bf284ce54644b014466c8b2c73b8925))
|
||||
- Revert "individual bug fix" ([b4e8c75](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b4e8c7589e53f1064d70703459cc6d66fae1b04c))
|
||||
- color palette for calendar ([#118](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/118)) ([51bbd65](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/51bbd6590bf284ce54644b014466c8b2c73b8925))
|
||||
|
||||
89
DOCKER_DEV_SETUP.md
Normal file
89
DOCKER_DEV_SETUP.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Docker Dev Setup
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker installed on your machine
|
||||
- Git (to clone the repository)
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```
|
||||
git clone https://github.com/Longhorn-Developers/UT-Registration-Plus.git
|
||||
cd UT-Registration-Plus
|
||||
```
|
||||
|
||||
2. Build the Docker image:
|
||||
|
||||
```
|
||||
docker build -t ut-registration-plus .
|
||||
```
|
||||
|
||||
## Using Docker for Different Modes
|
||||
|
||||
The Docker setup supports three modes of operation: build, zip, and dev. You can choose the mode either by passing it as a command or by setting the `BUILD_MODE` environment variable.
|
||||
|
||||
### Build Mode (Default)
|
||||
|
||||
This mode builds the extension and places the output in the `dist` folder.
|
||||
|
||||
```
|
||||
docker run -it --rm -v $(pwd)/dist:/extension/dist ut-registration-plus build
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
docker run -it --rm -v $(pwd)/dist:/extension/dist -e BUILD_MODE=build ut-registration-plus
|
||||
```
|
||||
|
||||
### Zip Mode
|
||||
|
||||
This mode builds the extension and creates a zipped package in the `package` folder.
|
||||
|
||||
```
|
||||
docker run -it --rm -v $(pwd)/package:/extension/package ut-registration-plus zip
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
docker run -it --rm -v $(pwd)/package:/extension/package -e BUILD_MODE=zip ut-registration-plus
|
||||
```
|
||||
|
||||
### Development Mode with Hot Module Replacement (HMR)
|
||||
|
||||
This mode runs the extension in development mode with HMR support.
|
||||
|
||||
_Note_: This currently doesn't work.
|
||||
|
||||
```
|
||||
docker run -it --rm -v $(pwd)/dist:/extension/dist -v $(pwd)/dist:/extension/dist -p 5173:5173 ut-registration-plus dev
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
docker run -it --rm -v $(pwd)/dist:/extension/dist -p 5173:5173 -e BUILD_MODE=dev ut-registration-plus
|
||||
```
|
||||
|
||||
## Accessing the Built Extension
|
||||
|
||||
- For build mode, the extension files will be in the `dist` directory on your host machine.
|
||||
- For zip mode, the zipped extension will be in the `package` directory on your host machine.
|
||||
- For dev mode, the extension will be continuously built in the `dist` directory, and you can load it as an unpacked extension in Chrome.
|
||||
|
||||
## Docker Development Workflow
|
||||
|
||||
When working on the extension:
|
||||
|
||||
1. Run the container in dev mode
|
||||
2. Make changes to your source code
|
||||
3. The extension will automatically rebuild thanks to HMR
|
||||
4. Reload the extension in Chrome to see your changes
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- If you encounter permission issues with the output directories, ensure that the directories exist on your host machine and have the correct permissions.
|
||||
- For any other issues, please check the Docker logs or open an issue in the GitHub repository.
|
||||
47
Dockerfile
Normal file
47
Dockerfile
Normal file
@@ -0,0 +1,47 @@
|
||||
# Stage 1: Base image with Node.js and pnpm
|
||||
FROM node:20.9.0-alpine AS base
|
||||
|
||||
# Install pnpm
|
||||
RUN npm install -g pnpm
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package.json, pnpm-lock.yaml, and .nvmrc
|
||||
COPY package.json pnpm-lock.yaml .nvmrc ./
|
||||
|
||||
# Copy patches directory if it exists
|
||||
COPY patches ./patches
|
||||
|
||||
# Install dependencies, including applying patches
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Copy the rest of the source code
|
||||
COPY . .
|
||||
|
||||
# Stage 2: Final stage
|
||||
FROM base AS final
|
||||
|
||||
# Install zip utility and bash
|
||||
RUN apk add --no-cache zip bash
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /extension
|
||||
|
||||
# Copy all files from base
|
||||
COPY --from=base /app ./
|
||||
|
||||
# Copy the entrypoint script
|
||||
COPY docker-entrypoint.sh /usr/local/bin/
|
||||
|
||||
# Make the entrypoint script executable
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
# Expose port for HMR
|
||||
EXPOSE 5173
|
||||
|
||||
# Set the entrypoint to our new script
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
|
||||
# Set the default command (which can be overridden)
|
||||
CMD ["build"]
|
||||
24
README.md
24
README.md
@@ -6,7 +6,7 @@
|
||||

|
||||

|
||||
|
||||
**UT Registration Plus (UTRP)** streamlines the process of registering for classes at UT Austin by reducing the chaos of juggling multiple tabs like Rate My Professor, Google Sheets, and the UT Course Schedule. With UTRP, you can simplify class selection and schedule management. We've all been there. 20 tabs of Rate My Professor, Google Spreadsheet, and the UT Course Schedule open and you still don't know what classes to take. UT Registration Plus (UTRP), tries to streamline most of the unnecessary steps and headaches of registering for classes at UT Austin.
|
||||
**UT Registration Plus (UTRP)** streamlines the process of registering for classes at UT Austin by reducing the chaos of juggling multiple tabs like Rate My Professor, Google Sheets, and the UT Course Schedule. With UTRP, you can simplify class selection and schedule management. We've all been there. 20 tabs of Rate My Professor, Google Spreadsheet, and the UT Course Schedule open and you still don't know what classes to take.
|
||||
|
||||
## Demo
|
||||
|
||||
@@ -58,7 +58,9 @@ Contributions are welcome and encouraged! To get started:
|
||||
|
||||
For significant changes, it’s recommended to open an issue first to discuss the proposed updates.
|
||||
|
||||
## Development: Getting Started
|
||||
## Development
|
||||
|
||||
### Getting Started
|
||||
|
||||
1. Clone this repository: `git clone https://github.com/Longhorn-Developers/UT-Registration-Plus.git`
|
||||
2. **Node Version**: This project requires the Node.js version specified in `.nvmrc`. Use [nvm](https://github.com/nvm-sh/nvm) to install and manage the correct version:
|
||||
@@ -101,6 +103,10 @@ Use `BETA=true pnpm build` to generate a beta build.
|
||||
|
||||
</details>
|
||||
|
||||
### Docker
|
||||
|
||||
This project includes a Dockerfile that allows you to build, zip, or run the extension in development mode using Docker. Refer to [Docker Dev Setup](./DOCKER_DEV_SETUP.md) to get started.
|
||||
|
||||
## Loading the Extension Manually
|
||||
|
||||
To load the extension manually in Chrome:
|
||||
@@ -194,3 +200,17 @@ Special thanks to the developers and contributors behind these amazing tools and
|
||||
- [Figma Designs](https://www.figma.com/design/8tsCay2FRqctrdcZ3r9Ahw/UTRP) by @IsaDavRod
|
||||
- [Longhorn Developers](https://github.com/Longhorn-Developers) - established in 2024
|
||||
- The UTRP devs, beta testers, and all supporters of the project!
|
||||
|
||||
## Activity
|
||||
|
||||

|
||||
|
||||
## Star History
|
||||
|
||||
<a href="https://star-history.com/#Longhorn-Developers/UT-Registration-Plus&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Longhorn-Developers/UT-Registration-Plus&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Longhorn-Developers/UT-Registration-Plus&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Longhorn-Developers/UT-Registration-Plus&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
46
docker-entrypoint.sh
Normal file
46
docker-entrypoint.sh
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Define supported modes
|
||||
SUPPORTED_MODES=("build" "zip" "dev")
|
||||
|
||||
# Function to display usage information
|
||||
usage() {
|
||||
echo "Usage: $0 [build|zip|dev]"
|
||||
echo " build: Build the extension"
|
||||
echo " zip: Build and zip the extension"
|
||||
echo " dev: Run in development mode with HMR"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if BUILD_MODE is set, otherwise use the first argument
|
||||
if [ -n "${BUILD_MODE:-}" ]; then
|
||||
mode="$BUILD_MODE"
|
||||
elif [ $# -eq 1 ]; then
|
||||
mode="$1"
|
||||
else
|
||||
usage
|
||||
fi
|
||||
|
||||
# Validate the mode
|
||||
if [[ ! " ${SUPPORTED_MODES[*]} " =~ " ${mode} " ]]; then
|
||||
echo "Error: Invalid mode '${mode}'" >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
# Execute the appropriate command based on the mode
|
||||
case "$mode" in
|
||||
build)
|
||||
echo "Building extension..."
|
||||
exec pnpm run build
|
||||
;;
|
||||
zip)
|
||||
echo "Building and zipping extension..."
|
||||
exec pnpm run zip
|
||||
;;
|
||||
dev)
|
||||
echo "Running in development mode with HMR..."
|
||||
exec pnpm run dev
|
||||
;;
|
||||
esac
|
||||
107
gulpfile.js
107
gulpfile.js
@@ -1,48 +1,93 @@
|
||||
// (Thanks go to https://github.com/pnd280/complexity/blob/alpha/gulpfile.js)
|
||||
|
||||
import cp from 'child_process';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs';
|
||||
import gulp from 'gulp';
|
||||
import gulpZip from 'gulp-zip';
|
||||
import { createRequire } from 'module';
|
||||
import { dest, series, src } from 'gulp';
|
||||
import { exec } from 'gulp-execa';
|
||||
import zip from 'gulp-zip';
|
||||
import path from 'path';
|
||||
|
||||
const DIST_DIR = 'dist';
|
||||
const PACKAGE_DIR = 'package';
|
||||
const DATABASE_DIR = path.join(DIST_DIR, 'database');
|
||||
|
||||
// Custom log functions
|
||||
const log = message => console.log(chalk.blue(`[${new Date().toTimeString().split(' ')[0]}]`), chalk.white(message));
|
||||
const logWarn = message =>
|
||||
console.warn(
|
||||
chalk.blue(`[${new Date().toTimeString().split(' ')[0]}]`),
|
||||
chalk.yellow(' [WARN]'),
|
||||
chalk.white(message)
|
||||
);
|
||||
const logError = message =>
|
||||
console.error(
|
||||
chalk.blue(`[${new Date().toTimeString().split(' ')[0]}]`),
|
||||
chalk.red(' [ERROR]'),
|
||||
chalk.white(message)
|
||||
);
|
||||
|
||||
// Remove extra database folder
|
||||
function removeExtraDatabaseDir(cb) {
|
||||
fs.rmSync(DATABASE_DIR, { recursive: true, force: true });
|
||||
log('Extra database directory removed.');
|
||||
|
||||
cb();
|
||||
}
|
||||
|
||||
// Instrument with Sentry
|
||||
// Make sure sentry is configured https://docs.sentry.io/platforms/javascript/sourcemaps/uploading/typescript/#2-configure-sentry-cli
|
||||
function instrumentWithSentry() {
|
||||
return cp.exec('sentry-cli sourcemaps inject dist/ && sentry-cli sourcemaps upload dist/');
|
||||
async function instrumentWithSentry(cb) {
|
||||
await exec(`sentry-cli sourcemaps inject ${DIST_DIR}`);
|
||||
await exec(`sentry-cli sourcemaps upload ${DIST_DIR}`);
|
||||
log('Sentry instrumentation completed.');
|
||||
|
||||
cb();
|
||||
}
|
||||
|
||||
// Zip the dist folder
|
||||
function zipDist() {
|
||||
const require = createRequire(import.meta.url);
|
||||
const manifest = require('./package.json');
|
||||
const zipFileName = `${manifest.name.replaceAll(' ', '-')}-${manifest.version}.zip`;
|
||||
const packageInfo = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
|
||||
const zipFileName = `${packageInfo.name.replace(/ /g, '-')}-${packageInfo.version}.zip`;
|
||||
|
||||
return gulp
|
||||
.src('dist/**', {
|
||||
encoding: false,
|
||||
})
|
||||
.pipe(gulpZip(zipFileName))
|
||||
.pipe(gulp.dest('package'));
|
||||
return src(`${DIST_DIR}/**`, {
|
||||
base: DIST_DIR,
|
||||
encoding: false, // Disable encoding to handle binary files correctly
|
||||
})
|
||||
.pipe(zip(zipFileName))
|
||||
.pipe(dest(PACKAGE_DIR))
|
||||
.on('end', () => log(`Zip file created: ${path.join(PACKAGE_DIR, zipFileName)}`));
|
||||
}
|
||||
|
||||
const zip = gulp.series(instrumentWithSentry, zipDist);
|
||||
|
||||
// Temp fix for CSP on Chrome 130
|
||||
// Manually remove them because there is no option to disable use_dynamic_url on @crxjs/vite-plugin
|
||||
function forceDisableUseDynamicUrl(done) {
|
||||
const require = createRequire(import.meta.url);
|
||||
const manifest = require('./dist/manifest.json');
|
||||
// Force disable use_dynamic_url in manifest.json
|
||||
function forceDisableUseDynamicUrl(cb) {
|
||||
const manifestPath = path.join(DIST_DIR, 'manifest.json');
|
||||
|
||||
manifest.web_accessible_resources.forEach(resource => {
|
||||
delete resource.use_dynamic_url;
|
||||
});
|
||||
|
||||
if (!fs.existsSync('./dist/manifest.json')) {
|
||||
return done();
|
||||
if (!fs.existsSync(manifestPath)) {
|
||||
logWarn('manifest.json not found. Skipping modification.');
|
||||
return cb();
|
||||
}
|
||||
|
||||
fs.writeFileSync('./dist/manifest.json', JSON.stringify(manifest, null, 2));
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
||||
let modified = false;
|
||||
|
||||
done();
|
||||
manifest.web_accessible_resources.forEach(resource => {
|
||||
if (resource.use_dynamic_url) {
|
||||
delete resource.use_dynamic_url;
|
||||
modified = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (modified) {
|
||||
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
||||
log('use_dynamic_url removed from manifest.json');
|
||||
} else {
|
||||
log('No use_dynamic_url found in manifest.json. No changes made.');
|
||||
}
|
||||
|
||||
cb();
|
||||
}
|
||||
|
||||
export { forceDisableUseDynamicUrl, zip };
|
||||
// Main build task
|
||||
const zipProdBuild = series(removeExtraDatabaseDir, instrumentWithSentry, zipDist);
|
||||
|
||||
export { forceDisableUseDynamicUrl, zipProdBuild };
|
||||
|
||||
92
package.json
92
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ut-registration-plus",
|
||||
"displayName": "UT Registration Plus",
|
||||
"version": "2.0.2",
|
||||
"version": "2.1.0",
|
||||
"description": "UT Registration Plus is a Chrome extension that allows students to easily register for classes.",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/Longhorn-Developers/UT-Registration-Plus",
|
||||
@@ -10,7 +10,7 @@
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"build:watch": "NODE_ENV='development' vite build --mode development -w",
|
||||
"zip": "PROD=true pnpm build && pnpm gulp zip",
|
||||
"zip": "PROD=true pnpm build && pnpm gulp zipProdBuild",
|
||||
"prettier": "prettier src --check",
|
||||
"prettier:fix": "prettier src --write",
|
||||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives",
|
||||
@@ -27,12 +27,16 @@
|
||||
"prepare": "husky"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^2.1.10",
|
||||
"@hello-pangea/dnd": "^17.0.0",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/modifiers": "^9.0.0",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@headlessui/react": "^2.2.0",
|
||||
"@octokit/rest": "^21.0.2",
|
||||
"@sentry/react": "^8.34.0",
|
||||
"@unocss/vite": "^0.63.4",
|
||||
"@vitejs/plugin-react": "^4.3.2",
|
||||
"@phosphor-icons/react": "^2.1.7",
|
||||
"@sentry/react": "^8.38.0",
|
||||
"@unocss/vite": "^0.63.6",
|
||||
"@vitejs/plugin-react": "^4.3.3",
|
||||
"chrome-extension-toolkit": "^0.0.54",
|
||||
"clsx": "^2.1.1",
|
||||
"conventional-changelog": "^6.0.0",
|
||||
@@ -41,14 +45,14 @@
|
||||
"html-to-image": "^1.11.11",
|
||||
"husky": "^9.1.6",
|
||||
"kc-dabr-wasm": "^0.1.2",
|
||||
"nanoid": "^5.0.7",
|
||||
"nanoid": "^5.0.8",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-loading-skeleton": "^3.5.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"sass": "^1.79.5",
|
||||
"sass": "^1.81.0",
|
||||
"simple-git": "^3.27.0",
|
||||
"sql.js": "1.11.0"
|
||||
},
|
||||
@@ -59,27 +63,29 @@
|
||||
"@commitlint/types": "^19.5.0",
|
||||
"@crxjs/vite-plugin": "2.0.0-beta.21",
|
||||
"@iconify-json/bi": "^1.2.1",
|
||||
"@iconify-json/iconoir": "^1.2.2",
|
||||
"@iconify-json/material-symbols": "^1.2.4",
|
||||
"@iconify-json/ri": "^1.2.1",
|
||||
"@iconify-json/ic": "^1.2.1",
|
||||
"@iconify-json/iconoir": "^1.2.4",
|
||||
"@iconify-json/material-symbols": "^1.2.6",
|
||||
"@iconify-json/ri": "^1.2.3",
|
||||
"@iconify-json/streamline": "^1.2.1",
|
||||
"@semantic-release/exec": "^6.0.3",
|
||||
"@sentry/types": "^8.34.0",
|
||||
"@storybook/addon-designs": "^8.0.3",
|
||||
"@storybook/addon-essentials": "^8.3.5",
|
||||
"@storybook/addon-links": "^8.3.5",
|
||||
"@storybook/blocks": "^8.3.5",
|
||||
"@storybook/react": "^8.3.5",
|
||||
"@storybook/react-vite": "^8.3.5",
|
||||
"@storybook/test": "^8.3.5",
|
||||
"@sentry/types": "^8.38.0",
|
||||
"@storybook/addon-designs": "^8.0.4",
|
||||
"@storybook/addon-essentials": "^8.4.4",
|
||||
"@storybook/addon-links": "^8.4.4",
|
||||
"@storybook/blocks": "^8.4.4",
|
||||
"@storybook/react": "^8.4.4",
|
||||
"@storybook/react-vite": "^8.4.4",
|
||||
"@storybook/test": "^8.4.4",
|
||||
"@svgr/core": "^8.1.0",
|
||||
"@svgr/plugin-jsx": "^8.1.0",
|
||||
"@types/chrome": "^0.0.273",
|
||||
"@types/conventional-changelog": "^3.1.5",
|
||||
"@types/gulp": "^4.0.17",
|
||||
"@types/gulp-zip": "^4.0.4",
|
||||
"@types/node": "^22.7.5",
|
||||
"@types/node": "^22.9.0",
|
||||
"@types/prompts": "^2.4.9",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/semantic-release": "^20.0.6",
|
||||
@@ -87,18 +93,18 @@
|
||||
"@types/sql.js": "^1.4.9",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"@unocss/eslint-config": "^0.63.4",
|
||||
"@unocss/postcss": "^0.63.4",
|
||||
"@unocss/preset-uno": "^0.63.4",
|
||||
"@unocss/preset-web-fonts": "^0.63.4",
|
||||
"@unocss/reset": "^0.63.4",
|
||||
"@unocss/transformer-directives": "^0.63.4",
|
||||
"@unocss/transformer-variant-group": "^0.63.4",
|
||||
"@unocss/eslint-config": "^0.63.6",
|
||||
"@unocss/postcss": "^0.63.6",
|
||||
"@unocss/preset-uno": "^0.63.6",
|
||||
"@unocss/preset-web-fonts": "^0.63.6",
|
||||
"@unocss/reset": "^0.63.6",
|
||||
"@unocss/transformer-directives": "^0.63.6",
|
||||
"@unocss/transformer-variant-group": "^0.63.6",
|
||||
"@vitejs/plugin-react-swc": "^3.7.1",
|
||||
"@vitest/coverage-v8": "^2.1.2",
|
||||
"@vitest/ui": "^2.1.2",
|
||||
"@vitest/coverage-v8": "^2.1.5",
|
||||
"@vitest/ui": "^2.1.5",
|
||||
"chalk": "^5.3.0",
|
||||
"chromatic": "^11.12.5",
|
||||
"chromatic": "^11.18.1",
|
||||
"cssnano": "^7.0.6",
|
||||
"cssnano-preset-advanced": "^7.0.6",
|
||||
"dotenv": "^16.4.5",
|
||||
@@ -111,29 +117,31 @@
|
||||
"eslint-import-resolver-typescript": "^3.6.3",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-import-essentials": "^0.2.1",
|
||||
"eslint-plugin-jsdoc": "^50.3.2",
|
||||
"eslint-plugin-jsdoc": "^50.5.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-react": "^7.37.1",
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-react-prefer-function-component": "^3.3.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.12",
|
||||
"eslint-plugin-react-refresh": "^0.4.14",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"eslint-plugin-storybook": "^0.9.0",
|
||||
"eslint-plugin-tsdoc": "^0.3.0",
|
||||
"gulp": "^5.0.0",
|
||||
"gulp-execa": "^7.0.1",
|
||||
"gulp-zip": "^6.0.0",
|
||||
"path": "^0.12.7",
|
||||
"postcss": "^8.4.47",
|
||||
"postcss": "^8.4.49",
|
||||
"prettier": "^3.3.3",
|
||||
"react-dev-utils": "^12.0.1",
|
||||
"semantic-release": "^24.1.2",
|
||||
"storybook": "^8.3.5",
|
||||
"semantic-release": "^24.2.0",
|
||||
"storybook": "^8.4.4",
|
||||
"typescript": "^5.6.3",
|
||||
"unocss": "^0.63.4",
|
||||
"unocss": "^0.63.6",
|
||||
"unocss-preset-primitives": "0.0.2-beta.1",
|
||||
"unplugin-icons": "^0.19.3",
|
||||
"vite": "^5.4.8",
|
||||
"vite": "^5.4.11",
|
||||
"vite-plugin-inspect": "^0.8.7",
|
||||
"vitest": "^2.1.2"
|
||||
"vitest": "^2.1.5"
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
|
||||
7294
pnpm-lock.yaml
generated
7294
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
BIN
public/fonts/roboto-mono.woff2
Normal file
BIN
public/fonts/roboto-mono.woff2
Normal file
Binary file not shown.
@@ -8,8 +8,8 @@ const manifest = chrome.runtime.getManifest();
|
||||
/**
|
||||
* Handles editing the storage for a specific area.
|
||||
*
|
||||
* @param {string} areaName - The name of the storage area.
|
||||
* @returns {Function} - A function that accepts changes and sets them in the storage.
|
||||
* @param areaName - The name of the storage area.
|
||||
* @returns A function that accepts changes and sets them in the storage.
|
||||
*/
|
||||
const handleEditStorage = (areaName: 'local' | 'sync' | 'session') => (changes: Record<string, unknown>) => {
|
||||
chrome.storage[areaName].set(changes);
|
||||
|
||||
@@ -18,11 +18,13 @@ const nameSuffix = isBeta ? ' (beta)' : mode === 'development' ? ' (dev)' : '';
|
||||
|
||||
const HOST_PERMISSIONS: string[] = [
|
||||
'*://*.utdirect.utexas.edu/apps/registrar/course_schedule/*',
|
||||
'*://*.utdirect.utexas.edu/registration/classlist/*',
|
||||
'*://*.utexas.collegescheduler.com/*',
|
||||
'*://*.catalog.utexas.edu/ribbit/',
|
||||
'*://*.registrar.utexas.edu/schedules/*',
|
||||
'*://*.login.utexas.edu/login/*',
|
||||
'https://utexas.bluera.com/*',
|
||||
'*://my.utexas.edu/student/student/*',
|
||||
];
|
||||
|
||||
const manifest = defineManifest(async () => ({
|
||||
|
||||
486
src/pages/404/Page404.tsx
Normal file
486
src/pages/404/Page404.tsx
Normal file
@@ -0,0 +1,486 @@
|
||||
// @ts-nocheck
|
||||
/* eslint-disable */
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
/**
|
||||
* Page404 component
|
||||
*
|
||||
* @returns The Page404 component
|
||||
*/
|
||||
export default function Page404(): JSX.Element {
|
||||
const canvasRef = useRef(null);
|
||||
const ASPECT_RATIO = 848 / 480;
|
||||
|
||||
useEffect(() => {
|
||||
// Credit: Notch
|
||||
function _0x58e5() {
|
||||
let _0x33cb15 = [
|
||||
'putImageData',
|
||||
'sin',
|
||||
'getContext',
|
||||
'random',
|
||||
'sqrt',
|
||||
'3498792PzxeQW',
|
||||
'2JZNlgj',
|
||||
'1199520qBJBWq',
|
||||
'8OebCGy',
|
||||
'156395VBdeaj',
|
||||
'79193hyrbkc',
|
||||
'10iqsPJh',
|
||||
'now',
|
||||
'data',
|
||||
'80uLyuyX',
|
||||
'cos',
|
||||
'1843331yKjgfa',
|
||||
'createImageData',
|
||||
'2266182vPDGCM',
|
||||
'current',
|
||||
'49758oxlYFR',
|
||||
];
|
||||
_0x58e5 = function () {
|
||||
return _0x33cb15;
|
||||
};
|
||||
return _0x58e5();
|
||||
}
|
||||
let _0xdd3699 = _0x9cb4;
|
||||
(function (_0x5cc1ea, _0x44f991) {
|
||||
let _0x3b1280 = _0x9cb4;
|
||||
let _0x500290 = _0x5cc1ea();
|
||||
while ([]) {
|
||||
try {
|
||||
let _0xea56e3 =
|
||||
(-parseInt(_0x3b1280(0x8b)) / (0x2344 + 0x63 * 0x59 + -0x45ae)) *
|
||||
(parseInt(_0x3b1280(0x87)) / (0x2ec * -0x3 + -0x9 * 0x3ed + 0x2c1b)) +
|
||||
(parseInt(_0x3b1280(0x95)) / (0x4 * -0x8b7 + -0x16 * -0x109 + 0x1 * 0xc19)) *
|
||||
(-parseInt(_0x3b1280(0x8f)) / (0x17 * -0x8c + 0x1 * 0x1f0f + 0x1d * -0xa3)) +
|
||||
-parseInt(_0x3b1280(0x8a)) / (-0x113 * 0x21 + -0x1 * 0xc20 + 0x2f98) +
|
||||
-parseInt(_0x3b1280(0x88)) / (-0x125c + 0xd * 0x12d + 0x1 * 0x319) +
|
||||
parseInt(_0x3b1280(0x91)) / (0x389 * -0x2 + 0x1 * 0xb9c + -0x483) +
|
||||
(-parseInt(_0x3b1280(0x89)) / (0x4e7 + -0xcf * -0x1 + -0x2d7 * 0x2)) *
|
||||
(-parseInt(_0x3b1280(0x93)) / (-0x1859 + 0x1366 + 0x4fc)) +
|
||||
(parseInt(_0x3b1280(0x8c)) / (-0x1 * -0x22b2 + -0x438 * -0x5 + -0x1be * 0x20)) *
|
||||
(parseInt(_0x3b1280(0x86)) / (0x29 * 0x29 + 0x1d09 * 0x1 + -0x238f));
|
||||
if (_0xea56e3 === _0x44f991) break;
|
||||
else _0x500290.push(_0x500290.shift());
|
||||
} catch (_0x5847bb) {
|
||||
_0x500290.push(_0x500290.shift());
|
||||
}
|
||||
}
|
||||
})(_0x58e5, 0x3c063 + 0x13c * -0x305 + 0x2e43c);
|
||||
const _0x5dce41 = canvasRef[_0xdd3699(0x94)];
|
||||
const _0x16f39e = _0x5dce41[_0xdd3699(0x83)]('2d');
|
||||
let _0x228047;
|
||||
const _0x124180 = 0x2 * -0x7f7 + -0x70 * 0x1 + -0x602 * -0x3;
|
||||
const _0x591e4f = 0xba7 * 0x1 + 0x1ebc + -0x2973;
|
||||
let _0x39dbea = new Array(
|
||||
(0x94f + -0x215 * -0x1 + -0xb24) *
|
||||
(-0x1 * -0x1dda + -0x23e + -0x1b5c) *
|
||||
(0xb9c + 0x1 * 0x1c57 + -0x27b3 * 0x1)
|
||||
);
|
||||
let _0x391e96 = new Array(
|
||||
(-0x1e8f + -0x1 * -0x20f9 + -0x25a) *
|
||||
(-0xce6 + 0x2a * 0x86 + -0x906) *
|
||||
(0x1d57 * -0x1 + -0x1170 + 0x2eca) *
|
||||
(0x1c2 + -0x11a9 + 0x1 * 0xff7)
|
||||
);
|
||||
function _0x3ebd0f() {
|
||||
let _0x53ab34 = _0xdd3699;
|
||||
for (
|
||||
var _0x599786 = 0x1e5b + 0x7a * -0x3a + -0x2b6;
|
||||
_0x599786 < -0x1 * 0x593 + 0x9 * -0x15 + 0x660;
|
||||
_0x599786++
|
||||
) {
|
||||
let _0x5dd0b8 =
|
||||
0x1043 +
|
||||
0x401 +
|
||||
-0x1345 -
|
||||
((Math[_0x53ab34(0x84)]() * (-0xb10 + -0x173e + 0x22ae)) |
|
||||
(0x1 * -0xf5b + -0x83 * -0xd + -0x8b4 * -0x1));
|
||||
for (
|
||||
var _0xebe028 = -0x1827 * -0x1 + 0x385 + 0x142 * -0x16;
|
||||
_0xebe028 < (-0x77c + 0x11d * 0x1b + -0x1683) * (0x1a * 0x162 + 0x13 * -0xc3 + -0x1578);
|
||||
_0xebe028++
|
||||
) {
|
||||
for (
|
||||
var _0x4bc490 = 0x2c * 0x55 + -0xf1d + -0x3 * -0x2b;
|
||||
_0x4bc490 < 0x3 * -0x9a5 + -0x1ee8 + 0x5 * 0xbfb;
|
||||
_0x4bc490++
|
||||
) {
|
||||
let _0x36dcfc = -0xd60e5f * 0x1 + -0x3f232d + 0x1ab9dd6;
|
||||
if (_0x599786 == -0x517 * -0x5 + -0x1b9b + -0x8b * -0x4)
|
||||
_0x36dcfc = -0xcc5387 * 0x1 + -0xa89ad4 + -0xa6 * -0x303bf;
|
||||
(_0x599786 != -0x9 * -0x4f + 0x53 * -0x22 + 0x843 ||
|
||||
((Math[_0x53ab34(0x84)]() * (-0x11de + -0x1 * -0xbf1 + 0x5f0)) |
|
||||
(0xf60 + -0x704 + -0x85c)) ==
|
||||
0xb0b * -0x2 + -0x139b + 0x29b1) &&
|
||||
(_0x5dd0b8 =
|
||||
0x1 * 0x6d4 +
|
||||
-0x9 * -0x1f2 +
|
||||
-0x19 * 0xef -
|
||||
((Math[_0x53ab34(0x84)]() * (-0x5 * 0x79d + -0x233 * -0xb + 0xe40)) |
|
||||
(-0x4e4 + -0x165a + 0x1b3e)));
|
||||
if (
|
||||
_0x599786 == 0xe17 + 0x2a1 + -0x10b7 &&
|
||||
_0xebe028 <
|
||||
(((_0x4bc490 * _0x4bc490 * (-0x25c0 + -0x24 * 0x76 + -0x5 * -0xadf) +
|
||||
_0x4bc490 * (0x2569 + -0x396 + 0x10c1 * -0x2)) >>
|
||||
(-0x6d * 0x5 + 0x3b9 * 0x9 + 0x646 * -0x5)) &
|
||||
(-0x249a + 0xec3 + 0x15da)) +
|
||||
(-0x56 * 0x29 + -0x11 * -0x219 + -0x15d1)
|
||||
)
|
||||
_0x36dcfc = -0x1 * -0x3d6586 + 0x8bbbc5 + -0x5e770b;
|
||||
else
|
||||
_0x599786 == 0x9e + 0x29 * -0xa2 + 0x1955 &&
|
||||
_0xebe028 <
|
||||
(((_0x4bc490 * _0x4bc490 * (0x647 * -0x5 + -0x2359 + 0x42bf) +
|
||||
_0x4bc490 * (-0xf * 0xb8 + -0x220c + 0x2d25)) >>
|
||||
(0x13 * -0xc4 + -0x15fc + -0x3 * -0xc2e)) &
|
||||
(-0x2483 + 0x13a9 + 0x59f * 0x3)) +
|
||||
(0xca3 + -0x480 + -0x810) &&
|
||||
(_0x5dd0b8 =
|
||||
(_0x5dd0b8 * (-0xf2a * 0x1 + 0x4ab + 0xa81 * 0x1)) /
|
||||
(-0x1 * 0x1e5d + -0x1f7a + -0x261 * -0x1a));
|
||||
if (_0x599786 == 0xdad + 0xbe5 + 0xd * -0x1f7) {
|
||||
_0x36dcfc = 0x30e9f6 * -0x2 + -0x3c841f + 0x105aa3c;
|
||||
if (
|
||||
_0x4bc490 > -0x1af + -0x2 * -0xf94 + -0x5 * 0x5e5 &&
|
||||
_0x4bc490 < 0x1ed5 + 0x3c4 * 0x3 + -0x2a12 &&
|
||||
((_0xebe028 > 0x164 + -0x1720 + 0x56f * 0x4 &&
|
||||
_0xebe028 < 0x1514 + -0x127d * 0x1 + -0x288) ||
|
||||
(_0xebe028 > 0x1b * 0x147 + -0x1415 * -0x1 + 0x1b39 * -0x2 &&
|
||||
_0xebe028 < 0x2208 + -0x2408 + 0x22f * 0x1))
|
||||
) {
|
||||
_0x36dcfc = -0x1a7143 + 0x16bbd0e + -0x94b369;
|
||||
let _0xf0bb95 = _0x4bc490 - (-0x20 * -0xce + -0x1222 * -0x1 + -0x2bdb);
|
||||
var _0x436c14 =
|
||||
(_0xebe028 & (0x1f03 + -0x1506 + -0x3e * 0x29)) -
|
||||
(-0x1e7 + 0xef9 * 0x2 + -0x28c * 0xb);
|
||||
if (_0xf0bb95 < -0x10 * 0xe0 + -0x207d + 0x3 * 0xf7f)
|
||||
_0xf0bb95 = -0x8f * 0x2b + -0xd18 + 0x251e * 0x1 - _0xf0bb95;
|
||||
if (_0x436c14 < 0x1f64 + -0x18d * -0x3 + -0x240b * 0x1)
|
||||
_0x436c14 = -0x755 * 0x4 + -0xafb + 0x81 * 0x50 - _0x436c14;
|
||||
if (_0x436c14 > _0xf0bb95) _0xf0bb95 = _0x436c14;
|
||||
_0x5dd0b8 =
|
||||
0x3b5 * 0x5 +
|
||||
-0x1 * 0x1db7 +
|
||||
0xbf2 -
|
||||
((Math.random() * (-0xc45 + 0x257 * -0xb + 0x2622)) |
|
||||
(-0xf29 * -0x1 + -0x948 + 0x1 * -0x5e1)) +
|
||||
(_0xf0bb95 % (-0x1 * -0x13e1 + 0xa4e * -0x3 + 0x1c * 0x65)) *
|
||||
(-0x939 + -0x1d87 + 0x26e0);
|
||||
} else
|
||||
((Math[_0x53ab34(0x84)]() * (-0x405 * 0x6 + -0x122a + 0x2a4a)) |
|
||||
(0x7f7 + -0x1 * 0xc4f + -0x1 * -0x458)) ==
|
||||
0x1 * -0x26b + -0x131 * 0xd + 0x2 * 0x8f4 &&
|
||||
(_0x5dd0b8 =
|
||||
(_0x5dd0b8 *
|
||||
(0x102c +
|
||||
-0x2f9 * 0x4 +
|
||||
-0x3b2 -
|
||||
(_0x4bc490 & (0x18a7 * -0x1 + 0x236e + 0x563 * -0x2)) *
|
||||
(0x89 * 0x45 + 0xb38 + -0x2fc1))) /
|
||||
(-0x1 * 0x156 + -0x4d * 0x65 + 0x201b * 0x1));
|
||||
}
|
||||
_0x599786 == -0x2 * -0x1087 + -0x3 * 0xcb3 + 0x9 * 0x90 &&
|
||||
((_0x36dcfc = -0x100d2 * -0x9f + -0x659425 + 0x7b4bcc),
|
||||
((_0x4bc490 +
|
||||
(_0xebe028 >> (-0x12 * -0x205 + 0x1ca + -0x1 * 0x2622)) *
|
||||
(-0xe67 + -0x1c85 * 0x1 + -0x30 * -0xe5)) %
|
||||
(0x15c1 * -0x1 + 0x73f + 0xe8a * 0x1) ==
|
||||
-0x1729 + -0x1 * 0x247 + 0x1970 ||
|
||||
_0xebe028 % (0x14e9 * -0x1 + 0x21e1 + -0x1 * 0xcf4) ==
|
||||
0x641 * 0x1 + -0x16fd + 0x4 * 0x42f) &&
|
||||
(_0x36dcfc = 0x112d086 + -0x442565 + -0x47edf * 0x4));
|
||||
_0x599786 == 0x6 * -0x2bb + -0x24 + 0x9d * 0x1b &&
|
||||
(_0x36dcfc = -0x197 * 0x4ae9 + -0x22af5 * 0x1 + 0xb98463);
|
||||
let _0xba7812 = _0x5dd0b8;
|
||||
if (_0xebe028 >= -0x4 * -0x52 + 0x25d + -0x385)
|
||||
_0xba7812 /= -0x1 * -0x497 + 0x1a3f + -0x7b5 * 0x4;
|
||||
_0x599786 == -0x1d35 * -0x1 + -0x2445 + 0x718 &&
|
||||
((_0x36dcfc = -0x981b * 0xc4 + -0x6c2f7 * -0x16 + -0x11 * -0x2db19),
|
||||
((Math.random() * (-0x1 * 0x181a + -0x1700 + 0x2f1c)) | (-0x266 + 0x3 * 0x8b4 + -0x17b6)) ==
|
||||
0xcc2 + -0xbc6 + -0xe * 0x12 &&
|
||||
((_0x36dcfc = 0x2589 + -0xd * 0x27f + 0x3e * -0x15),
|
||||
(_0xba7812 = 0x10ef + -0x68a + -0x966)));
|
||||
let _0x1206cd =
|
||||
(((((_0x36dcfc >> (0x2 * 0xfb3 + -0x118a + -0xdcc)) & (-0x8fc + -0x1a19 + 0x1 * 0x2414)) *
|
||||
_0xba7812) /
|
||||
(0xa6 * -0x25 + 0x112c + 0x7d1)) <<
|
||||
(0x140c + -0xac0 + -0x1 * 0x93c)) |
|
||||
(((((_0x36dcfc >> (-0x11 * -0x3e + 0x157d + -0x1993 * 0x1)) &
|
||||
(-0x9 * 0x421 + -0x2102 + 0x472a * 0x1)) *
|
||||
_0xba7812) /
|
||||
(0x107b * -0x1 + -0x16fc + 0x2876)) <<
|
||||
(-0x20e8 + -0x5e * -0x53 + 0x276)) |
|
||||
(((_0x36dcfc & (0x581 * 0x3 + 0x1 * 0x977 + -0x18fb)) * _0xba7812) /
|
||||
(-0xaa0 + 0x211 + 0x2 * 0x4c7));
|
||||
_0x391e96[
|
||||
_0x4bc490 +
|
||||
_0xebe028 * (0x7 * -0x415 + 0x1b6e * -0x1 + 0x1f * 0x1cf) +
|
||||
_0x599786 * (0x504 + -0x2075 + -0x97b * -0x3) * (0x25 * -0xa9 + 0x171d + -0x3 * -0x71)
|
||||
] = _0x1206cd;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (
|
||||
var _0x4bc490 = -0x825 * -0x3 + 0x71a + -0x1b * 0x12b;
|
||||
_0x4bc490 < 0x17f0 + -0x15a3 + -0x20d;
|
||||
_0x4bc490++
|
||||
) {
|
||||
for (
|
||||
var _0xebe028 = 0x53a + 0x7fe + -0x24 * 0x5e;
|
||||
_0xebe028 < 0x1 * 0x1445 + 0x6d * 0x2 + -0xd * 0x19b;
|
||||
_0xebe028++
|
||||
) {
|
||||
for (
|
||||
let _0x2eee05 = 0x69 * 0x39 + -0x202 * 0xb + -0x14b;
|
||||
_0x2eee05 < 0x97 * 0x27 + -0x261 + -0x1 * 0x1460;
|
||||
_0x2eee05++
|
||||
) {
|
||||
var _0x599786 =
|
||||
(_0x2eee05 << (-0x1 * -0x235e + 0x121 + -0x2473)) |
|
||||
(_0xebe028 << (-0x7db + -0x80 * 0x23 + 0x1961)) |
|
||||
_0x4bc490;
|
||||
var _0x436c14 =
|
||||
(_0xebe028 - (0xec6 + 0x6 * 0x199 + -0xbc * 0x21 + 0.5)) *
|
||||
(0x1fca + 0x7 * 0xf9 + 0x2699 * -0x1 + 0.4);
|
||||
let _0x198036 =
|
||||
(_0x2eee05 - (0x1 * 0xecb + -0x24b8 + 0x160d + 0.5)) * (0xc * 0xda + -0x825 + -0x213 + 0.4);
|
||||
_0x39dbea[_0x599786] =
|
||||
(Math.random() * (0x1134 + -0x131 * -0x9 + -0x1bdd)) | (0x160c + -0x7 * 0x6b + -0x131f);
|
||||
if (
|
||||
Math[_0x53ab34(0x84)]() >
|
||||
Math[_0x53ab34(0x85)](Math.sqrt(_0x436c14 * _0x436c14 + _0x198036 * _0x198036)) -
|
||||
(0x15a9 + -0xe8f * -0x2 + 0x32c7 * -0x1 + 0.8)
|
||||
)
|
||||
_0x39dbea[_0x599786] = 0x1a62 + -0xaf * -0x1 + 0x1 * -0x1b11;
|
||||
}
|
||||
}
|
||||
}
|
||||
_0x228047 = _0x16f39e[_0x53ab34(0x92)](_0x124180, _0x591e4f);
|
||||
for (
|
||||
var _0x599786 = 0x26b7 + -0x1b66 * 0x1 + -0x1 * 0xb51;
|
||||
_0x599786 < _0x124180 * _0x591e4f;
|
||||
_0x599786++
|
||||
) {
|
||||
_0x228047[_0x53ab34(0x8e)][
|
||||
_0x599786 * (-0x4 * -0x4c0 + 0x228a + -0x3586) + (-0x856 + -0x22 * 0x37 + 0x1 * 0xfa7)
|
||||
] = -0x4e5 + 0x196a + -0x126 * 0x11;
|
||||
}
|
||||
setInterval(_0x5629d1, (0x26b5 + 0x1 * 0xf9b + 0x1934 * -0x2) / (0x1 * 0xca1 + -0xbb6 + -0x87));
|
||||
}
|
||||
function _0x5629d1() {
|
||||
let _0x13c635 = _0xdd3699;
|
||||
_0x5b7f43(),
|
||||
_0x16f39e[_0x13c635(0x81)](
|
||||
_0x228047,
|
||||
0x9c + -0x1 * 0x23ab + 0x230f * 0x1,
|
||||
-0x1c26 + 0x7bf + -0x6cd * -0x3
|
||||
);
|
||||
}
|
||||
let _0x93f360 = 0x64 * 0x49 + 0x1e1e + -0x3aa2;
|
||||
function _0x5b7f43() {
|
||||
let _0x4626de = _0xdd3699;
|
||||
let _0x400eca =
|
||||
Math[_0x4626de(0x82)](
|
||||
((Date.now() % (-0x1f15 * -0x1 + 0x1 * -0x44b9 + -0x265a * -0x2)) /
|
||||
(-0x396f + 0x1 * -0x995 + 0x6a14)) *
|
||||
Math.PI *
|
||||
(0x2429 + 0x2360 + -0x4787)
|
||||
) *
|
||||
(-0x106 * -0x1d + -0x3e * -0xa0 + -0x446e + 0.4) +
|
||||
Math.PI / (0x1b21 + -0x1 * -0xa9 + 0x379 * -0x8);
|
||||
let _0xaac757 =
|
||||
Math[_0x4626de(0x90)](
|
||||
((Date[_0x4626de(0x8d)]() % (0xd * -0x351 + -0x3994 + 0x13f7 * 0x7)) /
|
||||
(0x1ceb + -0x9ef * 0x1 + 0x1414)) *
|
||||
Math.PI *
|
||||
(0x1420 + 0x12b1 + -0x26cf)
|
||||
) *
|
||||
(-0x1 * -0x211a + -0x18ff + -0x81b * 0x1 + 0.4);
|
||||
let _0x159021 = Math[_0x4626de(0x90)](_0xaac757);
|
||||
let _0x11f9a2 = Math[_0x4626de(0x82)](_0xaac757);
|
||||
let _0x1ac9ca = Math[_0x4626de(0x90)](_0x400eca);
|
||||
let _0x49fc16 = Math[_0x4626de(0x82)](_0x400eca);
|
||||
let _0x116a45 =
|
||||
0x1b4f +
|
||||
-0x1167 +
|
||||
-0x9c8 +
|
||||
0.5 +
|
||||
((Date[_0x4626de(0x8d)]() % (-0x1 * -0xfaa + 0x169 * 0x1f + -0x7 * 0x2e7)) /
|
||||
(-0x2831 + 0x1 * 0x171 + 0x4dd0)) *
|
||||
(-0x911 + -0x26e2 + -0x1b * -0x1c9);
|
||||
let _0x46282c = 0x1 * 0x238 + 0x252d + -0x2745 + 0.5;
|
||||
let _0x1680c5 = -0x14ad + -0x187 * 0x15 + -0x30 * -0x11a + 0.5;
|
||||
_0x93f360++;
|
||||
for (let _0x132623 = -0x174b + 0x19 * -0xb7 + 0x292a; _0x132623 < _0x124180; _0x132623++) {
|
||||
let _0x1859c2 = (_0x132623 - _0x124180 / (-0x3e5 * 0x8 + -0x1e7b + -0x3da5 * -0x1)) / _0x591e4f;
|
||||
for (let _0x1a573d = 0x2440 + -0x262f + -0x1ef * -0x1; _0x1a573d < _0x591e4f; _0x1a573d++) {
|
||||
let _0x58642d = (_0x1a573d - _0x591e4f / (0x14f4 + 0x24b * 0x11 + -0x3bed)) / _0x591e4f;
|
||||
let _0x3109fa = -0x7ca + 0x5 * -0x503 + 0x349 * 0xa;
|
||||
let _0x1df399 = _0x3109fa * _0x159021 + _0x58642d * _0x11f9a2;
|
||||
let _0x8e77ec = _0x58642d * _0x159021 - _0x3109fa * _0x11f9a2;
|
||||
let _0x5d0446 = _0x1859c2 * _0x1ac9ca + _0x1df399 * _0x49fc16;
|
||||
let _0x14ed58 = _0x1df399 * _0x1ac9ca - _0x1859c2 * _0x49fc16;
|
||||
let _0x13f1b0 = 0x1af9 + -0x3 * 0xc1b + 0x34 * 0x2e;
|
||||
let _0x2062a9 = -0x6c * -0x21 + -0x13bb * 0x1 + 0x43 * 0x1a;
|
||||
let _0x5c387a = 0x1124 + 0x862 * 0x2 + 0x9b * -0x38;
|
||||
let _0x267dd3 = -0x986 + 0x5 * 0x1f5 + 0x5 * -0x7;
|
||||
for (
|
||||
let _0x3e178a = -0xf01 + 0xd * -0x59 + 0x93 * 0x22;
|
||||
_0x3e178a < -0x16f6 + -0x204b * 0x1 + -0x36 * -0x106;
|
||||
_0x3e178a++
|
||||
) {
|
||||
let _0x828a0c = _0x5d0446;
|
||||
if (_0x3e178a == 0x411 * -0x7 + 0x2 * 0x80f + 0x11 * 0xba) _0x828a0c = _0x8e77ec;
|
||||
if (_0x3e178a == -0x1e6d + -0xdb1 * -0x1 + 0x85f * 0x2) _0x828a0c = _0x14ed58;
|
||||
let _0x57383c =
|
||||
(0x18ab + 0x183f + -0x1 * 0x30e9) /
|
||||
(_0x828a0c < 0x25 * -0xe3 + -0x2424 + 0x44f3 ? -_0x828a0c : _0x828a0c);
|
||||
let _0x4b089b = _0x5d0446 * _0x57383c;
|
||||
let _0x5b3085 = _0x8e77ec * _0x57383c;
|
||||
let _0x1eaaad = _0x14ed58 * _0x57383c;
|
||||
let _0x2a568a = _0x116a45 - (_0x116a45 | (-0xcdb + 0x1447 + -0x13 * 0x64));
|
||||
if (_0x3e178a == 0x1 * -0x1048 + -0x7ea + -0x4d7 * -0x5)
|
||||
_0x2a568a = _0x46282c - (_0x46282c | (-0x1db5 + 0x1 * -0x35 + -0x223 * -0xe));
|
||||
if (_0x3e178a == 0x107e + 0x2c7 + -0x1343 * 0x1)
|
||||
_0x2a568a = _0x1680c5 - (_0x1680c5 | (0x234d + -0x2368 + -0x3 * -0x9));
|
||||
if (_0x828a0c > 0xe00 + 0xc9b * -0x1 + -0x165)
|
||||
_0x2a568a = 0xb * -0x117 + 0x1 * -0x1e0b + 0x2a09 - _0x2a568a;
|
||||
let _0x38c463 = _0x57383c * _0x2a568a;
|
||||
let _0x227002 = _0x116a45 + _0x4b089b * _0x2a568a;
|
||||
let _0x2aec99 = _0x46282c + _0x5b3085 * _0x2a568a;
|
||||
let _0x32116b = _0x1680c5 + _0x1eaaad * _0x2a568a;
|
||||
if (_0x828a0c < 0x180b + 0x19e3 * 0x1 + 0x391 * -0xe) {
|
||||
if (_0x3e178a == 0x1313 * -0x2 + 0x1 * 0x2605 + -0x1 * -0x21) _0x227002--;
|
||||
if (_0x3e178a == 0x3d * 0x15 + 0x114d + 0x164d * -0x1) _0x2aec99--;
|
||||
if (_0x3e178a == -0x1c1 * -0x8 + -0x60c + -0x3fd * 0x2) _0x32116b--;
|
||||
}
|
||||
while (_0x38c463 < _0x267dd3) {
|
||||
let _0x3db6a4 =
|
||||
_0x39dbea[
|
||||
((_0x32116b & (-0x3 * 0x1f3 + 0x1ef2 + -0x18da * 0x1)) <<
|
||||
(-0x8d3 * 0x2 + -0x1f7 * 0x9 + 0x2361)) |
|
||||
((_0x2aec99 & (-0x1a92 + 0x6b8 + 0x1419)) << (-0xe71 * 0x1 + -0x788 + 0x15ff)) |
|
||||
(_0x227002 & (0x1897 + 0x2134 + -0x398c))
|
||||
];
|
||||
if (_0x3db6a4 > -0x18f6 + 0x513 * 0x2 + 0xed0) {
|
||||
let _0x546fb5 =
|
||||
((_0x227002 + _0x32116b) * (-0x861 + -0x16bd + 0x1f2e)) &
|
||||
(0xd1c + -0x94 * -0x8 + -0x11ad);
|
||||
let _0x1667c5 =
|
||||
((_0x2aec99 * (-0x16f5 * -0x1 + -0x47 * -0x2f + 0x2a * -0xdb)) &
|
||||
(-0x2469 + 0x156a * -0x1 + 0x39e2)) +
|
||||
(-0x1 * 0x13f8 + 0x6df + -0x1 * -0xd29);
|
||||
if (_0x3e178a == 0x1 * -0x1a87 + 0x1fdd + -0x555 * 0x1) {
|
||||
(_0x546fb5 =
|
||||
(_0x227002 * (-0x10d7 + 0x1 * 0x15ad + -0x2 * 0x263)) &
|
||||
(-0x25ca * 0x1 + 0x8 * 0x278 + -0x1 * -0x1219)),
|
||||
(_0x1667c5 =
|
||||
(_0x32116b * (0x4d3 + 0x1c09 * -0x1 + 0x3 * 0x7c2)) &
|
||||
(-0xf06 * 0x2 + -0x144f * -0x1 + -0x344 * -0x3));
|
||||
if (_0x5b3085 < -0xa * 0xed + -0xd19 + 0x1 * 0x165b)
|
||||
_0x1667c5 += -0xd48 + 0xf6c + 0xc * -0x2b;
|
||||
}
|
||||
let _0x5206c3 =
|
||||
_0x391e96[
|
||||
_0x546fb5 +
|
||||
_0x1667c5 * (0x1c90 + 0x2 * -0x1297 + 0x8ae) +
|
||||
_0x3db6a4 *
|
||||
(0x208 * -0x13 + -0x2550 + 0x1 * 0x4ce8) *
|
||||
(0xbc5 + -0x7b3 * -0x2 + -0x58 * 0x4f)
|
||||
];
|
||||
_0x5206c3 > 0x65a * -0x4 + 0xf9a + -0x5 * -0x1f6 &&
|
||||
((_0x13f1b0 = _0x5206c3),
|
||||
(_0x5c387a =
|
||||
0x181c +
|
||||
0x1449 +
|
||||
-0x2b66 -
|
||||
(((_0x38c463 / (-0x15 * -0x59 + -0x3 * -0x3c9 + 0x944 * -0x2)) *
|
||||
(0x9cd * 0x1 + -0x15ce + 0x100 * 0xd)) |
|
||||
(-0xc * -0x2a0 + 0x1 * -0x23bf + 0x43f))),
|
||||
(_0x2062a9 =
|
||||
((0x7a0 + 0x3f * 0x51 + 0x8 * -0x352) *
|
||||
(-0xf60 +
|
||||
0x21e9 +
|
||||
-0xa * 0x1c1 -
|
||||
((_0x3e178a + (0x1439 + -0x1049 + -0x1 * 0x3ee)) %
|
||||
(-0xbd7 + -0x1 * -0x7d5 + 0x405)) *
|
||||
(0x214 * -0x7 + 0x17 * 0x7 + 0xe1d * 0x1))) /
|
||||
(-0x2709 + -0x6 * -0x312 + -0x39a * -0x6)),
|
||||
(_0x267dd3 = _0x38c463));
|
||||
}
|
||||
(_0x227002 += _0x4b089b),
|
||||
(_0x2aec99 += _0x5b3085),
|
||||
(_0x32116b += _0x1eaaad),
|
||||
(_0x38c463 += _0x57383c);
|
||||
}
|
||||
}
|
||||
let _0x5cba48 =
|
||||
(((_0x13f1b0 >> (0x8bb + 0x407 * -0x2 + -0x9d * 0x1)) & (-0xc6 + 0xb43 + -0x97e)) *
|
||||
_0x2062a9 *
|
||||
_0x5c387a) /
|
||||
((-0x1df7 * -0x1 + -0x48b * 0x1 + -0x186d) * (-0x5 * -0x585 + -0x1497 + -0x603));
|
||||
let _0x48b547 =
|
||||
(((_0x13f1b0 >> (-0xa42 + -0x760 * 0x2 + 0x5 * 0x502)) &
|
||||
(0xb31 + 0x1 * -0x538 + 0x31 * -0x1a)) *
|
||||
_0x2062a9 *
|
||||
_0x5c387a) /
|
||||
((-0x1cff * -0x1 + -0xecd + -0xd33) * (-0x1 * 0x4d5 + 0x2440 + -0x2c * 0xb1));
|
||||
let _0xdf8389 =
|
||||
((_0x13f1b0 & (-0x247a + -0x4 * -0x9c2 + -0x39 * 0x7)) * _0x2062a9 * _0x5c387a) /
|
||||
((0x1d5 * 0xa + -0x250a + -0x31 * -0x67) * (-0x7 * 0x25f + -0xae7 + 0x1c7f * 0x1));
|
||||
(_0x228047[_0x4626de(0x8e)][
|
||||
(_0x132623 + _0x1a573d * _0x124180) * (0x29 * -0xa9 + -0x94 * -0x2b + -0x239 * -0x1) +
|
||||
(0x55d * 0x2 + 0xeed * 0x1 + -0xc7 * 0x21)
|
||||
] = _0x5cba48),
|
||||
(_0x228047[_0x4626de(0x8e)][
|
||||
(_0x132623 + _0x1a573d * _0x124180) * (0x9 * -0x205 + -0xfb7 * 0x1 + -0x1c * -0x136) +
|
||||
(-0x1 * -0x1e79 + 0x10cd + -0x2f45)
|
||||
] = _0x48b547),
|
||||
(_0x228047[_0x4626de(0x8e)][
|
||||
(_0x132623 + _0x1a573d * _0x124180) * (0x1e2a + -0x21df + -0x1 * -0x3b9) +
|
||||
(0x1e79 + 0x860 * -0x2 + 0x1 * -0xdb7)
|
||||
] = _0xdf8389);
|
||||
}
|
||||
}
|
||||
}
|
||||
function _0x9cb4(_0x473675, _0x42315f) {
|
||||
let _0x1c3f44 = _0x58e5();
|
||||
return (
|
||||
(_0x9cb4 = function (_0x2f27e0, _0x487e61) {
|
||||
_0x2f27e0 -= -0x6c7 * -0x2 + 0x1 * -0x1b75 + -0x4 * -0x39a;
|
||||
let _0x44ff4c = _0x1c3f44[_0x2f27e0];
|
||||
return _0x44ff4c;
|
||||
}),
|
||||
_0x9cb4(_0x473675, _0x42315f)
|
||||
);
|
||||
}
|
||||
_0x3ebd0f();
|
||||
|
||||
console.log('404 page loaded');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className='relative h-screen w-screen flex items-center justify-center overflow-hidden bg-white'>
|
||||
{/* Canvas Container */}
|
||||
<div className='relative h-full w-full'>
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className='absolute left-1/2 top-1/2 h-full w-full object-contain -translate-x-1/2 -translate-y-1/2'
|
||||
style={{
|
||||
maxWidth: '100vw',
|
||||
maxHeight: '100vh',
|
||||
aspectRatio: ASPECT_RATIO,
|
||||
imageRendering: 'crisp-edges',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 404 Text Overlay */}
|
||||
<div className='absolute left-1/2 top-1/2 z-10 -translate-x-1/2 -translate-y-1/2'>
|
||||
<h1 className='text-8xl text-white font-bold'>404 Page not found</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
27
src/pages/404/index.html
Normal file
27
src/pages/404/index.html
Normal file
@@ -0,0 +1,27 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<title>404</title>
|
||||
</head>
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script src="./index.tsx" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
8
src/pages/404/index.tsx
Normal file
8
src/pages/404/index.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import 'uno.css';
|
||||
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import Page404 from './Page404';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(<Page404 />);
|
||||
@@ -2,6 +2,8 @@ import type { TabWithId } from '@background/util/openNewTab';
|
||||
import openNewTab from '@background/util/openNewTab';
|
||||
import { tabs } from '@shared/messages';
|
||||
import type { CalendarBackgroundMessages } from '@shared/messages/CalendarMessages';
|
||||
import { OptionsStore } from '@shared/storage/OptionsStore';
|
||||
import { CRX_PAGES } from '@shared/types/CRXPages';
|
||||
import type { MessageHandler } from 'chrome-extension-toolkit';
|
||||
|
||||
const getAllTabInfos = async () => {
|
||||
@@ -21,13 +23,13 @@ const getAllTabInfos = async () => {
|
||||
const calendarBackgroundHandler: MessageHandler<CalendarBackgroundMessages> = {
|
||||
async switchToCalendarTab({ data, sendResponse }) {
|
||||
const { uniqueId } = data;
|
||||
const calendarUrl = chrome.runtime.getURL(`calendar.html`);
|
||||
const calendarUrl = chrome.runtime.getURL(CRX_PAGES.CALENDAR);
|
||||
|
||||
const allTabs = await getAllTabInfos();
|
||||
|
||||
const openCalendarTabInfo = allTabs.find(tab => tab.url?.startsWith(calendarUrl));
|
||||
|
||||
if (openCalendarTabInfo !== undefined) {
|
||||
if (openCalendarTabInfo !== undefined && !(await OptionsStore.get('alwaysOpenCalendarInNewTab'))) {
|
||||
const tabid = openCalendarTabInfo.tab.id;
|
||||
|
||||
await chrome.tabs.update(tabid, { active: true });
|
||||
|
||||
@@ -2,16 +2,18 @@ import addCourse from '@pages/background/lib/addCourse';
|
||||
import clearCourses from '@pages/background/lib/clearCourses';
|
||||
import createSchedule from '@pages/background/lib/createSchedule';
|
||||
import deleteSchedule from '@pages/background/lib/deleteSchedule';
|
||||
import exportSchedule from '@pages/background/lib/exportSchedule';
|
||||
import removeCourse from '@pages/background/lib/removeCourse';
|
||||
import renameSchedule from '@pages/background/lib/renameSchedule';
|
||||
import switchSchedule from '@pages/background/lib/switchSchedule';
|
||||
import type { UserScheduleMessages } from '@shared/messages/UserScheduleMessages';
|
||||
import { Course } from '@shared/types/Course';
|
||||
import { validateLoginStatus } from '@shared/util/checkLoginStatus';
|
||||
import type { MessageHandler } from 'chrome-extension-toolkit';
|
||||
|
||||
const userScheduleHandler: MessageHandler<UserScheduleMessages> = {
|
||||
addCourse({ data, sendResponse }) {
|
||||
addCourse(data.scheduleId, new Course(data.course)).then(sendResponse);
|
||||
addCourse(data.scheduleId, new Course(data.course), data.hasColor ?? false).then(sendResponse);
|
||||
},
|
||||
removeCourse({ data, sendResponse }) {
|
||||
removeCourse(data.scheduleId, new Course(data.course)).then(sendResponse);
|
||||
@@ -31,6 +33,21 @@ const userScheduleHandler: MessageHandler<UserScheduleMessages> = {
|
||||
renameSchedule({ data, sendResponse }) {
|
||||
renameSchedule(data.scheduleId, data.newName).then(sendResponse);
|
||||
},
|
||||
// proxy so we can add courses
|
||||
addCourseByURL({ data: { url, method, body, response }, sendResponse }) {
|
||||
fetch(url, {
|
||||
method,
|
||||
body,
|
||||
})
|
||||
.then(res => (response === 'json' ? res.json() : res.text()))
|
||||
.then(sendResponse);
|
||||
},
|
||||
validateLoginStatus({ data, sendResponse }) {
|
||||
validateLoginStatus(data.url).then(sendResponse);
|
||||
},
|
||||
exportSchedule({ data, sendResponse }) {
|
||||
exportSchedule(data.scheduleId).then(sendResponse);
|
||||
},
|
||||
};
|
||||
|
||||
export default userScheduleHandler;
|
||||
|
||||
@@ -4,21 +4,26 @@ import { getUnusedColor } from '@shared/util/colors';
|
||||
|
||||
/**
|
||||
* Adds a course to a user's schedule.
|
||||
*
|
||||
* @param scheduleId - The id of the schedule to add the course to.
|
||||
* @param course - The course to add.
|
||||
* @param hasColor - If the course block already has colors manually set
|
||||
* @returns A promise that resolves to void.
|
||||
* @throws An error if the schedule is not found.
|
||||
*/
|
||||
export default async function addCourse(scheduleId: string, course: Course): Promise<void> {
|
||||
export default async function addCourse(scheduleId: string, course: Course, hasColor = false): Promise<void> {
|
||||
const schedules = await UserScheduleStore.get('schedules');
|
||||
const activeSchedule = schedules.find(s => s.id === scheduleId);
|
||||
if (!activeSchedule) {
|
||||
throw new Error('Schedule not found');
|
||||
}
|
||||
|
||||
course.colors = getUnusedColor(activeSchedule, course);
|
||||
if (!hasColor) {
|
||||
course.colors = getUnusedColor(activeSchedule, course);
|
||||
}
|
||||
|
||||
activeSchedule.courses.push(course);
|
||||
activeSchedule.updatedAt = Date.now();
|
||||
|
||||
await UserScheduleStore.set('schedules', schedules);
|
||||
console.log(`Course added: ${course.courseName} (ID: ${course.uniqueId})`);
|
||||
}
|
||||
|
||||
65
src/pages/background/lib/addCourseByURL.ts
Normal file
65
src/pages/background/lib/addCourseByURL.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import addCourse from '@pages/background/lib/addCourse';
|
||||
import { background } from '@shared/messages';
|
||||
import type { UserSchedule } from '@shared/types/UserSchedule';
|
||||
import { CourseCatalogScraper } from '@views/lib/CourseCatalogScraper';
|
||||
import getCourseTableRows from '@views/lib/getCourseTableRows';
|
||||
import { SiteSupport } from '@views/lib/getSiteSupport';
|
||||
|
||||
/**
|
||||
* Adds a course to the active schedule by fetching course details from a provided URL.
|
||||
* If no URL is provided, prompts the user to enter one.
|
||||
* Sriram and Elie made this
|
||||
*
|
||||
* @param activeSchedule - The user's active schedule to which the course will be added.
|
||||
* @param link - The URL from which to fetch the course details. If not provided, a prompt will ask for it.
|
||||
*
|
||||
* @returns A promise that resolves when the course has been added or the operation is cancelled.
|
||||
*
|
||||
* @throws an error if there is an issue with scraping the course details.
|
||||
*/
|
||||
export async function addCourseByURL(activeSchedule: UserSchedule, link?: string): Promise<void> {
|
||||
// todo: Use a proper modal instead of a prompt
|
||||
// eslint-disable-next-line no-param-reassign, no-alert
|
||||
if (!link) link = prompt('Enter course link') || undefined;
|
||||
|
||||
// Exit if the user cancels the prompt
|
||||
if (!link) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let htmlText: string;
|
||||
try {
|
||||
htmlText = await background.addCourseByURL({
|
||||
url: link,
|
||||
method: 'GET',
|
||||
response: 'text',
|
||||
});
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-alert
|
||||
alert(`Failed to fetch url '${link}'`);
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = new DOMParser().parseFromString(htmlText, 'text/html');
|
||||
|
||||
const scraper = new CourseCatalogScraper(SiteSupport.COURSE_CATALOG_DETAILS, doc, link);
|
||||
const tableRows = getCourseTableRows(doc);
|
||||
const scrapedCourses = scraper.scrape(tableRows, false);
|
||||
|
||||
if (scrapedCourses.length !== 1) return;
|
||||
const description = scraper.getDescription(doc);
|
||||
const row = scrapedCourses[0]!;
|
||||
const course = row.course!;
|
||||
course.description = description;
|
||||
|
||||
if (activeSchedule.courses.every(c => c.uniqueId !== course.uniqueId)) {
|
||||
console.log('Adding course');
|
||||
await addCourse(activeSchedule.id, course);
|
||||
} else {
|
||||
console.log('Course already exists');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error scraping course:', error);
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,9 @@ import type { Serialized } from 'chrome-extension-toolkit';
|
||||
|
||||
/**
|
||||
* Creates a new schedule with the given name
|
||||
* @param scheduleName the name of the schedule to create
|
||||
* @returns undefined if successful, otherwise an error message
|
||||
*
|
||||
* @param scheduleName - The name of the schedule to create
|
||||
* @returns Undefined if successful, otherwise an error message
|
||||
*/
|
||||
export default async function createSchedule(scheduleName: string) {
|
||||
const schedules = await UserScheduleStore.get('schedules');
|
||||
|
||||
@@ -18,20 +18,18 @@ export default async function deleteSchedule(scheduleId: string): Promise<string
|
||||
if (scheduleIndex === -1) {
|
||||
throw new Error(`Schedule ${scheduleId} does not exist`);
|
||||
}
|
||||
if (scheduleIndex === activeIndex) {
|
||||
throw new Error(`Cannot delete active schedule`);
|
||||
}
|
||||
|
||||
if (scheduleIndex < activeIndex) {
|
||||
await UserScheduleStore.set('activeIndex', activeIndex - 1);
|
||||
}
|
||||
|
||||
schedules.splice(scheduleIndex, 1);
|
||||
await UserScheduleStore.set('schedules', schedules);
|
||||
|
||||
if (activeIndex >= schedules.length) {
|
||||
await UserScheduleStore.set('activeIndex', schedules.length - 1);
|
||||
let newActiveIndex = activeIndex;
|
||||
if (scheduleIndex < activeIndex) {
|
||||
newActiveIndex = activeIndex - 1;
|
||||
} else if (activeIndex >= schedules.length) {
|
||||
newActiveIndex = schedules.length - 1;
|
||||
}
|
||||
await UserScheduleStore.set('activeIndex', newActiveIndex);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@ import handleDuplicate from './handleDuplicate';
|
||||
|
||||
/**
|
||||
* Creates a new schedule with the given name
|
||||
* @param scheduleName the name of the schedule to create
|
||||
* @returns undefined if successful, otherwise an error message
|
||||
*
|
||||
* @param scheduleName - The name of the schedule to create
|
||||
* @returns Undefined if successful, otherwise an error message
|
||||
*/
|
||||
export default async function duplicateSchedule(scheduleId: string): Promise<string | undefined> {
|
||||
const schedules = await UserScheduleStore.get('schedules');
|
||||
|
||||
24
src/pages/background/lib/exportSchedule.ts
Normal file
24
src/pages/background/lib/exportSchedule.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
||||
|
||||
/**
|
||||
* Exports the provided schedule to a portable JSON
|
||||
*
|
||||
* @param scheduleId - The Id matching the to-be-exported schedule
|
||||
* @returns JSON format of the provided schedule ID, empty if one was not found
|
||||
*/
|
||||
export default async function exportSchedule(scheduleId: string): Promise<string | undefined> {
|
||||
try {
|
||||
const storageData = await UserScheduleStore.get('schedules');
|
||||
const selectedSchedule = storageData.find(s => s.id === scheduleId);
|
||||
|
||||
if (!selectedSchedule) {
|
||||
console.warn(`Schedule ${scheduleId} does not exist`);
|
||||
return JSON.stringify({});
|
||||
}
|
||||
|
||||
console.log(selectedSchedule);
|
||||
return JSON.stringify(selectedSchedule, null, 2);
|
||||
} catch (error) {
|
||||
console.error('Error getting storage data:', error);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,10 @@ import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
||||
/**
|
||||
* Duplicates a new schedule with the given name.
|
||||
* Assumes that each schedule has a unique name.
|
||||
* @param scheduleName the name of the schedule to handle duplication for
|
||||
* @param schedules the list of UserSchedules to find existing names
|
||||
* @returns the new name for the schedule, of the form `{baseName}({index})`
|
||||
*
|
||||
* @param scheduleName - The name of the schedule to handle duplication for
|
||||
* @param schedules - The list of UserSchedules to find existing names
|
||||
* @returns The new name for the schedule, of the form `{baseName}({index})`
|
||||
*/
|
||||
export default async function handleDuplicate(scheduleName: string): Promise<string> {
|
||||
const schedules = await UserScheduleStore.get('schedules');
|
||||
|
||||
34
src/pages/background/lib/importSchedule.ts
Normal file
34
src/pages/background/lib/importSchedule.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Course } from '@shared/types/Course';
|
||||
import type { UserSchedule } from '@shared/types/UserSchedule';
|
||||
import type { Serialized } from 'chrome-extension-toolkit';
|
||||
|
||||
import addCourse from './addCourse';
|
||||
import createSchedule from './createSchedule';
|
||||
import switchSchedule from './switchSchedule';
|
||||
|
||||
function isValidSchedule(data: unknown): data is Serialized<UserSchedule> {
|
||||
if (typeof data !== 'object' || data === null) return false;
|
||||
const schedule = data as Record<string, unknown>;
|
||||
return typeof schedule.id === 'string' && typeof schedule.name === 'string' && Array.isArray(schedule.courses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a user schedule from portable file, creating a new schedule for it
|
||||
|
||||
* @param scheduleData - Data to be parsed back into a course schedule
|
||||
*/
|
||||
export default async function importSchedule(scheduleData: unknown): Promise<void> {
|
||||
if (isValidSchedule(scheduleData)) {
|
||||
const newScheduleId = await createSchedule(scheduleData.name);
|
||||
await switchSchedule(newScheduleId);
|
||||
|
||||
for (const c of scheduleData.courses) {
|
||||
const course = new Course(c);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await addCourse(newScheduleId, course, true);
|
||||
}
|
||||
console.log('Course schedule successfully parsed!');
|
||||
} else {
|
||||
console.error('No schedule data provided for import');
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,27 @@
|
||||
import CourseCatalogMain from '@views/components/CourseCatalogMain';
|
||||
import InjectedButton from '@views/components/injected/AddAllButton';
|
||||
import getSiteSupport, { SiteSupport } from '@views/lib/getSiteSupport';
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
const support = getSiteSupport(window.location.href);
|
||||
|
||||
if (support === SiteSupport.COURSE_CATALOG_DETAILS || support === SiteSupport.COURSE_CATALOG_LIST) {
|
||||
const renderComponent = (Component: React.ComponentType) => {
|
||||
const container = document.createElement('div');
|
||||
container.id = 'extension-root';
|
||||
document.body.appendChild(container);
|
||||
|
||||
createRoot(container).render(
|
||||
<React.StrictMode>
|
||||
<CourseCatalogMain support={support} />
|
||||
<Component />
|
||||
</React.StrictMode>
|
||||
);
|
||||
};
|
||||
|
||||
if (support === SiteSupport.COURSE_CATALOG_DETAILS || support === SiteSupport.COURSE_CATALOG_LIST) {
|
||||
renderComponent(() => <CourseCatalogMain support={support} />);
|
||||
}
|
||||
|
||||
if (support === SiteSupport.MY_UT) {
|
||||
renderComponent(InjectedButton);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 400px;
|
||||
width: 430px;
|
||||
height: 540px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@ interface CESMessage {
|
||||
/**
|
||||
* Opens the CES page for the specified instructor
|
||||
*
|
||||
* @param data first and last name of the instructor
|
||||
* @param data - First and last name of the instructor
|
||||
*/
|
||||
openCESPage: (data: { instructorFirstName: string; instructorLastName: string }) => chrome.tabs.Tab;
|
||||
}
|
||||
|
||||
@@ -4,17 +4,22 @@
|
||||
export default interface TabManagementMessages {
|
||||
/**
|
||||
* Opens a new tab with the given URL
|
||||
* @param data The URL to open
|
||||
*
|
||||
* @param data - The URL to open
|
||||
*/
|
||||
openNewTab: (data: { url: string }) => chrome.tabs.Tab;
|
||||
|
||||
/**
|
||||
* Gets the ID of the current tab (the tab that sent the message)
|
||||
*
|
||||
* @returns The ID of the current tab
|
||||
*/
|
||||
getTabId: () => number;
|
||||
|
||||
/**
|
||||
* Removes the tab with the given ID
|
||||
* @param data The ID of the tab to remove
|
||||
*
|
||||
* @param data - The ID of the tab to remove
|
||||
* @returns The ID of the tab that was removed
|
||||
*/
|
||||
removeTab: (data: { tabId: number }) => void;
|
||||
|
||||
@@ -6,42 +6,77 @@ import type { Course } from '@shared/types/Course';
|
||||
export interface UserScheduleMessages {
|
||||
/**
|
||||
* Add a course to a schedule
|
||||
* @param data the schedule id and course to add
|
||||
*
|
||||
* @param data - The schedule id and course to add
|
||||
*/
|
||||
addCourse: (data: { scheduleId: string; course: Course }) => void;
|
||||
addCourse: (data: { scheduleId: string; course: Course; hasColor?: boolean }) => void;
|
||||
|
||||
/**
|
||||
* Adds a course by URL
|
||||
*
|
||||
* @param data - The URL of the course to add
|
||||
* @returns Response of the requested course URL
|
||||
*/
|
||||
addCourseByURL: (data: { url: string; method: string; body?: string; response: 'json' | 'text' }) => string;
|
||||
|
||||
/**
|
||||
* Remove a course from a schedule
|
||||
* @param data the schedule id and course to remove
|
||||
*
|
||||
* @param data - The schedule id and course to remove
|
||||
*/
|
||||
removeCourse: (data: { scheduleId: string; course: Course }) => void;
|
||||
|
||||
/**
|
||||
* Clears all courses from a schedule
|
||||
* @param data the id of the schedule to clear
|
||||
*
|
||||
* @param data - The id of the schedule to clear
|
||||
*/
|
||||
clearCourses: (data: { scheduleId: string }) => void;
|
||||
|
||||
/**
|
||||
* Switches the active schedule to the one specified
|
||||
* @param data the id of the schedule to switch to
|
||||
*
|
||||
* @param data - The id of the schedule to switch to
|
||||
*/
|
||||
switchSchedule: (data: { scheduleId: string }) => void;
|
||||
|
||||
/**
|
||||
* Creates a new schedule with the specified name
|
||||
* @param data the name of the schedule to create
|
||||
* @returns undefined if successful, otherwise an error message
|
||||
*
|
||||
* @param data - The name of the schedule to create
|
||||
* @returns Undefined if successful, otherwise an error message
|
||||
*/
|
||||
createSchedule: (data: { scheduleName: string }) => string | undefined;
|
||||
|
||||
/**
|
||||
* Deletes a schedule with the specified name
|
||||
* @param data the id of the schedule to delete
|
||||
* @returns undefined if successful, otherwise an error message
|
||||
*
|
||||
* @param data - The id of the schedule to delete
|
||||
* @returns Undefined if successful, otherwise an error message
|
||||
*/
|
||||
deleteSchedule: (data: { scheduleId: string }) => string | undefined;
|
||||
|
||||
/**
|
||||
* Renames a schedule with the specified name
|
||||
* @param data the id of the schedule to rename and the new name
|
||||
* @returns undefined if successful, otherwise an error message
|
||||
*
|
||||
* @param data - The id of the schedule to rename and the new name
|
||||
* @returns Undefined if successful, otherwise an error message
|
||||
*/
|
||||
renameSchedule: (data: { scheduleId: string; newName: string }) => string | undefined;
|
||||
|
||||
/**
|
||||
* Checks the login status by making a request to the provided URL.
|
||||
*
|
||||
* @param data - The URL to check the login status against.
|
||||
* @returns true if user was already logged into the provided URL, false otherwise
|
||||
*/
|
||||
validateLoginStatus: (data: { url: string }) => boolean;
|
||||
|
||||
/**
|
||||
* Exports the current schedule to a JSON file for backing up and sharing
|
||||
*
|
||||
* @param data - Id of schedule that will be exported
|
||||
* @returns
|
||||
*/
|
||||
exportSchedule: (data: { scheduleId: string }) => string | undefined;
|
||||
}
|
||||
|
||||
15
src/shared/storage/CacheStore.ts
Normal file
15
src/shared/storage/CacheStore.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { CachedData } from '@shared/types/CachedData';
|
||||
import { createLocalStore, debugStore } from 'chrome-extension-toolkit';
|
||||
|
||||
interface ICacheStore {
|
||||
github: Record<string, CachedData<unknown>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A store that is used for storing cached data such as GitHub contributors
|
||||
*/
|
||||
export const CacheStore = createLocalStore<ICacheStore>({
|
||||
github: {},
|
||||
});
|
||||
|
||||
debugStore({ cacheStore: CacheStore });
|
||||
@@ -7,9 +7,6 @@ export interface IOptionsStore {
|
||||
/** whether we should enable course status chips (indicator for waitlisted, cancelled, and closed courses) */
|
||||
enableCourseStatusChips: boolean;
|
||||
|
||||
/** whether we should enable course's time and location in the extension's popup */
|
||||
enableTimeAndLocationInPopup: boolean;
|
||||
|
||||
/** whether we should automatically highlight conflicts on the course schedule page (adds a red strikethrough to courses that have conflicting times) */
|
||||
enableHighlightConflicts: boolean;
|
||||
|
||||
@@ -18,27 +15,31 @@ export interface IOptionsStore {
|
||||
|
||||
/** whether we should automatically refresh the data for the waitlist, course status, and other info with the latest data from UT's site */
|
||||
enableDataRefreshing: boolean;
|
||||
|
||||
/** whether we should open the calendar in a new tab; default is to focus an existing calendar tab */
|
||||
alwaysOpenCalendarInNewTab: boolean;
|
||||
}
|
||||
|
||||
export const OptionsStore = createSyncStore<IOptionsStore>({
|
||||
enableCourseStatusChips: false,
|
||||
enableTimeAndLocationInPopup: false,
|
||||
enableHighlightConflicts: true,
|
||||
enableScrollToLoad: true,
|
||||
enableDataRefreshing: true,
|
||||
enableDataRefreshing: false,
|
||||
alwaysOpenCalendarInNewTab: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* Initializes the settings by retrieving the values from the OptionsStore.
|
||||
* @returns {Promise<IOptionsStore>} A promise that resolves to an object satisfying the IOptionsStore interface.
|
||||
*
|
||||
* @returns A promise that resolves to an object satisfying the IOptionsStore interface.
|
||||
*/
|
||||
export const initSettings = async () =>
|
||||
({
|
||||
enableCourseStatusChips: await OptionsStore.get('enableCourseStatusChips'),
|
||||
enableTimeAndLocationInPopup: await OptionsStore.get('enableTimeAndLocationInPopup'),
|
||||
enableHighlightConflicts: await OptionsStore.get('enableHighlightConflicts'),
|
||||
enableScrollToLoad: await OptionsStore.get('enableScrollToLoad'),
|
||||
enableDataRefreshing: await OptionsStore.get('enableDataRefreshing'),
|
||||
alwaysOpenCalendarInNewTab: await OptionsStore.get('alwaysOpenCalendarInNewTab'),
|
||||
}) satisfies IOptionsStore;
|
||||
|
||||
// Clothing retailer right
|
||||
|
||||
7
src/shared/types/CachedData.ts
Normal file
7
src/shared/types/CachedData.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Represents cached data with its fetch timestamp
|
||||
*/
|
||||
export type CachedData<T> = {
|
||||
data: T;
|
||||
dataFetched: number;
|
||||
};
|
||||
@@ -25,3 +25,8 @@ export type sRGB = [r: number, g: number, b: number];
|
||||
* Represents a Lab color value.
|
||||
*/
|
||||
export type Lab = [l: number, a: number, b: number];
|
||||
|
||||
/**
|
||||
* Represents a HSL color value.
|
||||
*/
|
||||
export type HSL = [h: number, s: number, l: number];
|
||||
|
||||
@@ -95,8 +95,9 @@ export class Course {
|
||||
|
||||
/**
|
||||
* Gets a list of all the conflicts between this course and another course (i.e. if they have a meeting at the same time)
|
||||
* @param other another course to compare this course to
|
||||
* @returns a list of all the conflicts between this course and the other course as a tuple of the two conflicting meetings
|
||||
*
|
||||
* @param other - Another course to compare this course to
|
||||
* @returns A list of all the conflicts between this course and the other course as a tuple of the two conflicting meetings
|
||||
*/
|
||||
getConflicts(other: Course): [CourseMeeting, CourseMeeting][] {
|
||||
const conflicts: [CourseMeeting, CourseMeeting][] = [];
|
||||
|
||||
@@ -48,13 +48,13 @@ export class CourseMeeting {
|
||||
|
||||
/**
|
||||
* Whether or not this meeting conflicts with another meeting
|
||||
* MWF 10:00 am - 11:00 am conflicts with a F 10:00 am - 10:30 am
|
||||
* @param meeting the meeting to check for conflicts with
|
||||
* @returns true if the given meeting conflicts with this meeting, false otherwise
|
||||
*
|
||||
* @remarks MWF 10:00 am - 11:00 am conflicts with a F 10:00 am - 10:30 am
|
||||
* @param meeting - The meeting to check for conflicts with
|
||||
* @returns True if the given meeting conflicts with this meeting, false otherwise
|
||||
*/
|
||||
isConflicting(meeting: CourseMeeting): boolean {
|
||||
isConflicting({ days: otherDays, startTime: otherStartTime, endTime: otherEndTime }: CourseMeeting): boolean {
|
||||
const { days, startTime, endTime } = this;
|
||||
const { days: otherDays, startTime: otherStartTime, endTime: otherEndTime } = meeting;
|
||||
|
||||
const hasDayConflict = days.some(day => otherDays.includes(day));
|
||||
const hasTimeConflict = startTime < otherEndTime && endTime > otherStartTime;
|
||||
@@ -64,17 +64,17 @@ export class CourseMeeting {
|
||||
|
||||
/**
|
||||
* Return the string representation of the days of the week that this meeting is taught
|
||||
* @param options options for the string representation
|
||||
* @returns string representation of the days of the week that this meeting is taught
|
||||
*
|
||||
* @param options - Options for the string representation
|
||||
* @returns String representation of the days of the week that this meeting is taught
|
||||
*/
|
||||
getDaysString(options: DaysStringOptions): string {
|
||||
let { format, separator } = options;
|
||||
getDaysString({ format, separator }: DaysStringOptions): string {
|
||||
let { days } = this;
|
||||
|
||||
if (format === 'short') {
|
||||
days = Object.keys(DAY_MAP).filter(day => days.includes(DAY_MAP[day as keyof typeof DAY_MAP])) as Day[];
|
||||
}
|
||||
if (separator === 'none') {
|
||||
if (!separator) {
|
||||
return days.join('');
|
||||
}
|
||||
const listFormat = new Intl.ListFormat('en-US', {
|
||||
@@ -86,10 +86,11 @@ export class CourseMeeting {
|
||||
|
||||
/**
|
||||
* Return the string representation of the time range for the course
|
||||
* @param options options for the string representation
|
||||
* @returns string representation of the time range for the course
|
||||
*
|
||||
* @param options - Options for the string representation
|
||||
* @returns String representation of the time range for the course
|
||||
*/
|
||||
getTimeString(options: TimeStringOptions): string {
|
||||
getTimeString({ separator = '-' }: TimeStringOptions): string {
|
||||
const { startTime, endTime } = this;
|
||||
const startHour = Math.floor(startTime / 60);
|
||||
const startMinute = startTime % 60;
|
||||
@@ -108,7 +109,7 @@ export class CourseMeeting {
|
||||
}
|
||||
|
||||
startTimeString += startMinute === 0 ? ':00' : `:${startMinute}`;
|
||||
startTimeString += startHour >= 12 ? ' p.m.' : ' a.m.';
|
||||
startTimeString += startHour >= 12 ? 'pm' : 'am';
|
||||
|
||||
if (endHour === 0) {
|
||||
endTimeString = '12';
|
||||
@@ -117,15 +118,11 @@ export class CourseMeeting {
|
||||
} else {
|
||||
endTimeString = `${endHour}`;
|
||||
}
|
||||
|
||||
endTimeString += endMinute === 0 ? ':00' : `:${endMinute}`;
|
||||
endTimeString += endHour >= 12 ? ' p.m.' : ' a.m.';
|
||||
endTimeString += endHour >= 12 ? 'pm' : 'am';
|
||||
|
||||
if (options.capitalize) {
|
||||
startTimeString = startTimeString.toUpperCase();
|
||||
endTimeString = endTimeString.toUpperCase();
|
||||
}
|
||||
|
||||
return `${startTimeString} ${options.separator} ${endTimeString}`;
|
||||
return `${startTimeString} ${separator} ${endTimeString}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,8 +132,6 @@ export class CourseMeeting {
|
||||
type TimeStringOptions = {
|
||||
/** the separator between the start and end times */
|
||||
separator: string;
|
||||
/** capitalizes the AM/PM */
|
||||
capitalize?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -156,5 +151,5 @@ type DaysStringOptions = {
|
||||
*
|
||||
* 'narrow' = `Monday Wednesday Friday`
|
||||
*/
|
||||
separator: Intl.ListFormatStyle | 'none';
|
||||
separator?: Intl.ListFormatStyle;
|
||||
};
|
||||
|
||||
@@ -20,9 +20,10 @@ export class CourseSchedule {
|
||||
|
||||
/**
|
||||
* Given a string representation of the meeting information for a class, parse it into a CourseMeeting object
|
||||
* @param dayLine a string representation of the days of the week that the course is taught: MWF, TR, etc.
|
||||
* @param timeLine a string representation of a time-range that the course is taught: 10:00 am - 11:00 am, 1:00 pm - 2:00 pm, etc.
|
||||
* @param locLine a string representation of the location that the course is taught in: JGB 2.302, etc.
|
||||
*
|
||||
* @param dayLine - A string representation of the days of the week that the course is taught: MWF, TR, etc.
|
||||
* @param timeLine - A string representation of a time-range that the course is taught: 10:00 am - 11:00 am, 1:00 pm - 2:00 pm, etc.
|
||||
* @param locLine - A string representation of the location that the course is taught in: JGB 2.302, etc.
|
||||
* @returns CourseMeeting object representing the meeting information
|
||||
*/
|
||||
static parse(dayLine: string, timeLine: string, locLine: string): CourseMeeting {
|
||||
|
||||
@@ -16,57 +16,44 @@ export default class Instructor {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to the instructor's directory page on the UT Directory website
|
||||
* @returns a URL string to the instructor's directory page
|
||||
*/
|
||||
getDirectoryUrl(): string {
|
||||
const name = this.toString({
|
||||
format: 'full_name',
|
||||
case: 'capitalize',
|
||||
});
|
||||
|
||||
const url = new URL('https://directory.utexas.edu/index.php');
|
||||
url.searchParams.set('q', name);
|
||||
url.searchParams.set('scope', 'faculty/staff');
|
||||
url.searchParams.set('submit', 'Search');
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string representation of the instructor
|
||||
* @param options the options for how to format the instructor string
|
||||
* @returns a string representation of the instructor
|
||||
*
|
||||
* @param options - The options for how to format the instructor string
|
||||
* @returns A string representation of the instructor
|
||||
*/
|
||||
toString(options: InstructorFormatOptions): string {
|
||||
const { firstName, lastName, fullName } = this;
|
||||
const { format, case: caseType } = options;
|
||||
const { format } = options;
|
||||
|
||||
const process = (str: string) => {
|
||||
if (caseType === 'lowercase') {
|
||||
return str.toLowerCase();
|
||||
}
|
||||
if (caseType === 'uppercase') {
|
||||
return str.toUpperCase();
|
||||
}
|
||||
return capitalize(str);
|
||||
};
|
||||
switch (format) {
|
||||
case 'first_last':
|
||||
if (firstName && lastName) {
|
||||
return `${capitalize(firstName)} ${capitalize(lastName)}`;
|
||||
}
|
||||
|
||||
if (format === 'abbr' && firstName && lastName && firstName[0]) {
|
||||
return `${process(firstName[0])}. ${process(lastName)}`;
|
||||
}
|
||||
if (format === 'full_name' && fullName) {
|
||||
return process(fullName);
|
||||
}
|
||||
if (format === 'first_last' && firstName && lastName) {
|
||||
return `${process(firstName)} ${process(lastName)}`;
|
||||
}
|
||||
if (format === 'last' && lastName) {
|
||||
return process(lastName);
|
||||
}
|
||||
if (lastName) {
|
||||
return capitalize(lastName);
|
||||
}
|
||||
|
||||
throw new Error(`Invalid Instructor String format: ${format}`);
|
||||
if (fullName) {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
return '';
|
||||
case 'last':
|
||||
if (lastName) {
|
||||
return capitalize(lastName);
|
||||
}
|
||||
|
||||
if (fullName) {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
return '';
|
||||
default:
|
||||
throw new Error(`Invalid Instructor String format: ${format}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,9 +62,5 @@ export default class Instructor {
|
||||
*/
|
||||
type InstructorFormatOptions = {
|
||||
/** How do you want the names of the professors formatted */
|
||||
format: 'abbr' | 'first_last' | 'last' | 'full_name';
|
||||
/**
|
||||
* What the case of the string should be
|
||||
*/
|
||||
case: 'capitalize' | 'lowercase' | 'uppercase';
|
||||
format: 'first_last' | 'last';
|
||||
};
|
||||
|
||||
26
src/shared/types/Spacing.ts
Normal file
26
src/shared/types/Spacing.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* An object representing various spacing values used throughout the application.
|
||||
* Each key corresponds to a specific spacing size based on the 4px grid system.
|
||||
*/
|
||||
export const spacing = {
|
||||
'spacing-1': '0.125rem',
|
||||
'spacing-2': '0.25rem',
|
||||
'spacing-3': '0.5rem',
|
||||
'spacing-4': '0.75rem',
|
||||
'spacing-5': '1rem',
|
||||
'spacing-6': '1.25rem',
|
||||
'spacing-7': '1.5rem',
|
||||
'spacing-8': '2rem',
|
||||
} as const;
|
||||
|
||||
type SpacingKey = keyof typeof spacing;
|
||||
|
||||
/**
|
||||
* Converts a spacing value from rem to pixels
|
||||
* @param key - The spacing key to convert
|
||||
* @returns The spacing value in pixels
|
||||
*/
|
||||
export function getSpacingInPx(key: SpacingKey): number {
|
||||
const remValue = parseFloat(spacing[key]);
|
||||
return remValue * 16; // 1rem = 16px
|
||||
}
|
||||
@@ -14,11 +14,17 @@ export const colors = {
|
||||
blue: '#005F86',
|
||||
gray: '#9CADB7',
|
||||
offwhite: '#D6D2C4',
|
||||
concrete: '#95A5A6',
|
||||
},
|
||||
theme: {
|
||||
red: '#D10000',
|
||||
black: '#1A2024',
|
||||
offwhite: '#D6D2C4',
|
||||
black1: '#333F4850',
|
||||
black2: '#333F4820',
|
||||
staticwhite: '#FFFFFF',
|
||||
staticblack: '#1A2024',
|
||||
majorgridline: '#D1D5DB',
|
||||
minorgridline: '#F3F4F6',
|
||||
},
|
||||
} as const satisfies Record<string, Record<string, string>>;
|
||||
|
||||
@@ -37,7 +43,7 @@ export const extendedColors = {
|
||||
d: '#DC2626',
|
||||
dminus: '#B91C1C',
|
||||
f: '#B91C1C',
|
||||
other: '#6B7280',
|
||||
other: '#D6D3D1',
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { Serialized } from 'chrome-extension-toolkit';
|
||||
import { theme } from 'unocss/preset-mini';
|
||||
|
||||
import type { HexColor, Lab, RGB, sRGB } from '../types/Color';
|
||||
import type { HexColor, HSL, Lab, RGB, sRGB } from '../types/Color';
|
||||
import { isHexColor } from '../types/Color';
|
||||
import type { Course } from '../types/Course';
|
||||
import type { CourseColors, TWColorway, TWIndex } from '../types/ThemeColors';
|
||||
import { colorwayIndexes } from '../types/ThemeColors';
|
||||
import { colors, colorwayIndexes } from '../types/ThemeColors';
|
||||
import type { UserSchedule } from '../types/UserSchedule';
|
||||
|
||||
/**
|
||||
@@ -26,6 +26,19 @@ export function hexToRGB(hex: HexColor): RGB | undefined {
|
||||
return [parseInt(result[1]!, 16), parseInt(result[2]!, 16), parseInt(result[3]!, 16)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given string is a valid hex color.
|
||||
*
|
||||
* A valid hex color is a string that starts with a '#' followed by either
|
||||
* 3 or 6 hexadecimal characters (0-9, A-F, a-f).
|
||||
*
|
||||
* @param hex - The hex color string to validate.
|
||||
* @returns True if the string is a valid hex color, false otherwise.
|
||||
*/
|
||||
export function isValidHexColor(hex: string): boolean {
|
||||
return /^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(hex);
|
||||
}
|
||||
|
||||
export const useableColorways = Object.keys(theme.colors)
|
||||
// check that the color is a colorway (is an object)
|
||||
.filter(color => typeof theme.colors[color as keyof typeof theme.colors] === 'object')
|
||||
@@ -33,7 +46,9 @@ export const useableColorways = Object.keys(theme.colors)
|
||||
|
||||
/**
|
||||
* Generate a Tailwind classname for the font color based on the background color
|
||||
* @param bgColor the hex color of the background
|
||||
*
|
||||
* @param bgColor - The hex color of the background
|
||||
* @returns The Tailwind classname for the font color
|
||||
*/
|
||||
export function pickFontColor(bgColor: HexColor): 'text-white' | 'text-black' | 'text-theme-black' {
|
||||
const coefficients = [0.2126729, 0.7151522, 0.072175] as const;
|
||||
@@ -54,9 +69,20 @@ export function pickFontColor(bgColor: HexColor): 'text-white' | 'text-black' |
|
||||
return Ys < 0.365 ? 'text-black' : 'text-theme-black';
|
||||
}
|
||||
|
||||
// Mapping of Tailwind CSS class names to their corresponding hex values
|
||||
export const tailwindColorMap: Record<string, HexColor> = {
|
||||
'text-white': '#FFFFFF',
|
||||
'text-black': '#000000',
|
||||
'text-theme-black': colors.theme.black,
|
||||
};
|
||||
|
||||
/**
|
||||
* Get primary and secondary colors from a Tailwind colorway
|
||||
* @param colorway the Tailwind colorway ex. "emerald"
|
||||
*
|
||||
* @param colorway - The Tailwind colorway ex. "emerald"
|
||||
* @param index - The index of the primary color in the colorway
|
||||
* @param offset - The offset to get the secondary color
|
||||
* @returns The primary and secondary colors
|
||||
*/
|
||||
export function getCourseColors(colorway: TWColorway, index?: number, offset: number = 300): CourseColors {
|
||||
if (index === undefined) {
|
||||
@@ -76,10 +102,15 @@ export function getCourseColors(colorway: TWColorway, index?: number, offset: nu
|
||||
* @param color - The hexadecimal color value.
|
||||
* @returns The Tailwind colorway.
|
||||
*/
|
||||
export function getColorwayFromColor(color: HexColor): TWColorway {
|
||||
export function getColorwayFromColor(color: HexColor): {
|
||||
colorway: TWColorway;
|
||||
index: TWIndex;
|
||||
} {
|
||||
for (const colorway of useableColorways) {
|
||||
if (Object.values(theme.colors[colorway]).includes(color)) {
|
||||
return colorway as TWColorway;
|
||||
const colorValues = Object.values(theme.colors[colorway]);
|
||||
const index = colorValues.indexOf(color);
|
||||
if (index !== -1) {
|
||||
return { colorway: colorway as TWColorway, index: (index * 100) as TWIndex };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,10 +146,159 @@ export function getColorwayFromColor(color: HexColor): TWColorway {
|
||||
return getColorwayFromColor(closestColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a hexadecimal color value to HSL (Hue, Saturation, Lightness) format
|
||||
*
|
||||
* @param hex - The hexadecimal color string
|
||||
* @returns An array of [hue (0-360), saturation (0-100), lightness (0-100)]
|
||||
* @throws If the hex color cannot be converted to RGB
|
||||
*/
|
||||
export const hexToHSL = (hex: HexColor): HSL => {
|
||||
const rgb = hexToRGB(hex);
|
||||
|
||||
if (!rgb) {
|
||||
throw new Error('hexToRGB returned undefined');
|
||||
}
|
||||
|
||||
// Convert RGB to decimals
|
||||
const r = rgb[0] / 255;
|
||||
const g = rgb[1] / 255;
|
||||
const b = rgb[2] / 255;
|
||||
|
||||
// Find min/max/delta
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
const delta = max - min;
|
||||
|
||||
// Calculate HSL values
|
||||
let h = 0;
|
||||
let s = 0;
|
||||
let l = (max + min) / 2;
|
||||
|
||||
if (delta !== 0) {
|
||||
// Calculate saturation
|
||||
s = delta / (1 - Math.abs(2 * l - 1));
|
||||
|
||||
// Calculate hue
|
||||
if (max === r) {
|
||||
h = ((g - b) / delta) % 6;
|
||||
} else if (max === g) {
|
||||
h = (b - r) / delta + 2;
|
||||
} else {
|
||||
h = (r - g) / delta + 4;
|
||||
}
|
||||
h *= 60;
|
||||
}
|
||||
|
||||
// Normalize values
|
||||
h = Math.round(h < 0 ? h + 360 : h);
|
||||
s = Math.round(s * 100);
|
||||
l = Math.round(l * 100);
|
||||
|
||||
return [h, s, l];
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts an HSL color value to RGB format.
|
||||
*
|
||||
* @param hsl - The HSL color value
|
||||
* @returns An RGB color value
|
||||
*/
|
||||
function hslToRGB([hue, saturation, lightness]: HSL): RGB {
|
||||
// Convert percentages to decimals
|
||||
const s = saturation / 100;
|
||||
const l = lightness / 100;
|
||||
|
||||
// Calculate intermediate values
|
||||
const c = (1 - Math.abs(2 * l - 1)) * s;
|
||||
const x = c * (1 - Math.abs(((hue / 60) % 2) - 1));
|
||||
const m = l - c / 2;
|
||||
|
||||
let r = 0;
|
||||
let g = 0;
|
||||
let b = 0;
|
||||
|
||||
// Determine RGB values based on hue
|
||||
if (hue >= 0 && hue < 60) {
|
||||
[r, g, b] = [c, x, 0];
|
||||
} else if (hue >= 60 && hue < 120) {
|
||||
[r, g, b] = [x, c, 0];
|
||||
} else if (hue >= 120 && hue < 180) {
|
||||
[r, g, b] = [0, c, x];
|
||||
} else if (hue >= 180 && hue < 240) {
|
||||
[r, g, b] = [0, x, c];
|
||||
} else if (hue >= 240 && hue < 300) {
|
||||
[r, g, b] = [x, 0, c];
|
||||
} else {
|
||||
[r, g, b] = [c, 0, x];
|
||||
}
|
||||
|
||||
// Convert to 0-255 range and round
|
||||
return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a darker shade of the given hex color by reducing the lightness in HSL color space.
|
||||
*
|
||||
* @param color - The hexadecimal color value to darken.
|
||||
* @param offset - The percentage to reduce the lightness by (default is 20).
|
||||
* @returns The darker shade of the given hex color.
|
||||
* @throws If the provided color is not a valid hex color.
|
||||
*/
|
||||
export function getDarkerShade(color: HexColor, offset: number = 20): HexColor {
|
||||
const rgb = hexToRGB(color);
|
||||
if (!rgb) {
|
||||
throw new Error('color: Invalid hex.');
|
||||
}
|
||||
|
||||
// Convert to HSL
|
||||
const [h, s, l] = hexToHSL(color);
|
||||
|
||||
// Reduce lightness by offset percentage, ensuring it doesn't go below 0
|
||||
const newL = Math.max(0, l - offset);
|
||||
|
||||
// Convert back to RGB
|
||||
const newRGB = hslToRGB([h, s, newL]);
|
||||
|
||||
// Convert to hex
|
||||
return `#${newRGB.map(c => Math.round(c).toString(16).padStart(2, '0')).join('')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a lighter shade of the given hex color by increasing the lightness in HSL color space.
|
||||
*
|
||||
* @param color - The hexadecimal color value to lighten.
|
||||
* @param offset - The percentage to increase the lightness by (default is 20).
|
||||
* @returns The lighter shade of the given hex color.
|
||||
* @throws If the provided color is not a valid hex color.
|
||||
*/
|
||||
export function getLighterShade(color: HexColor, offset: number = 20): HexColor {
|
||||
const rgb = hexToRGB(color);
|
||||
if (!rgb) {
|
||||
throw new Error('color: Invalid hex.');
|
||||
}
|
||||
|
||||
// Convert to HSL
|
||||
const [h, s, l] = hexToHSL(color);
|
||||
|
||||
// Increase lightness by offset percentage, ensuring it doesn't go above 100
|
||||
const newL = Math.min(100, l + offset);
|
||||
|
||||
// Convert back to RGB
|
||||
const newRGB = hslToRGB([h, s, newL]);
|
||||
|
||||
// Convert to hex
|
||||
return `#${newRGB.map(c => Math.round(c).toString(16).padStart(2, '0')).join('')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next unused color in a tailwind colorway for a given schedule
|
||||
* @param schedule the schedule which the course is in
|
||||
* @param course the course to get the color for
|
||||
*
|
||||
* @param schedule - The schedule which the course is in
|
||||
* @param course - The course to get the color for
|
||||
* @param index - The index of the primary color in the colorway
|
||||
* @param offset - The offset to get the secondary color
|
||||
* @returns The primary and secondary colors
|
||||
*/
|
||||
export function getUnusedColor(
|
||||
schedule: Serialized<UserSchedule>,
|
||||
@@ -143,17 +323,28 @@ export function getUnusedColor(
|
||||
|
||||
const scheduleCourses = schedule.courses.map(c => ({
|
||||
...c,
|
||||
colorway: getColorwayFromColor(c.colors.primaryColor),
|
||||
theme: (() => {
|
||||
try {
|
||||
return getColorwayFromColor(c.colors.primaryColor);
|
||||
} catch (error) {
|
||||
// Default to emerald colorway with index 500
|
||||
return {
|
||||
colorway: 'emerald' as TWColorway,
|
||||
index: 500 as TWIndex,
|
||||
};
|
||||
}
|
||||
})(),
|
||||
}));
|
||||
const usedColorways = new Set(scheduleCourses.map(c => c.colorway));
|
||||
|
||||
const usedColorways = new Set(scheduleCourses.map(c => c.theme.colorway));
|
||||
const availableColorways = new Set(useableColorways.filter(c => !usedColorways.has(c)));
|
||||
|
||||
if (availableColorways.size > 0) {
|
||||
let sameDepartment = scheduleCourses.filter(c => c.department === course.department);
|
||||
|
||||
sameDepartment.sort((a, b) => {
|
||||
const aIndex = useableColorways.indexOf(a.colorway);
|
||||
const bIndex = useableColorways.indexOf(b.colorway);
|
||||
const aIndex = useableColorways.indexOf(a.theme.colorway);
|
||||
const bIndex = useableColorways.indexOf(b.theme.colorway);
|
||||
|
||||
return aIndex - bIndex;
|
||||
});
|
||||
@@ -162,8 +353,8 @@ export function getUnusedColor(
|
||||
// check to see if any adjacent colorways are available
|
||||
const centerCourse = sameDepartment[Math.floor(Math.random() * sameDepartment.length)]!;
|
||||
|
||||
let nextColorway = getNextColorway(centerCourse.colorway);
|
||||
let prevColorway = getPreviousColorway(centerCourse.colorway);
|
||||
let nextColorway = getNextColorway(centerCourse.theme.colorway);
|
||||
let prevColorway = getPreviousColorway(centerCourse.theme.colorway);
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
@@ -221,8 +412,9 @@ function rgbToSrgb(rgb: RGB): sRGB {
|
||||
|
||||
/**
|
||||
* Convert an RGB color to the OKLab color space
|
||||
* @param rgb the RGB color
|
||||
* @returns the color in the OKLab color space
|
||||
*
|
||||
* @param rgb - The RGB color
|
||||
* @returns The color in the OKLab color space
|
||||
*/
|
||||
function srgbToOKlab([r, g, b]: sRGB): Lab {
|
||||
let l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
||||
|
||||
@@ -3,9 +3,10 @@ import MIMEType from '../types/MIMEType';
|
||||
|
||||
/**
|
||||
* Downloads a blob by creating a temporary URL and triggering a download.
|
||||
* @param blobPart The blob data to be downloaded.
|
||||
* @param type The MIME type of the blob.
|
||||
* @param fileName The name of the file to be downloaded.
|
||||
*
|
||||
* @param blobPart - The blob data to be downloaded.
|
||||
* @param type - The MIME type of the blob.
|
||||
* @param fileName - The name of the file to be downloaded.
|
||||
* @returns A promise that resolves when the download is successful, or rejects with an error if the download fails.
|
||||
*/
|
||||
export function downloadBlob(blobPart: BlobPart, type: MIMETypeKey, fileName: string): Promise<void> {
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
import { ClockUser, LockKey, Prohibit } from '@phosphor-icons/react';
|
||||
import type { StatusType } from '@shared/types/Course';
|
||||
import { Status } from '@shared/types/Course';
|
||||
import type { SVGProps } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import ClosedIcon from '~icons/material-symbols/lock';
|
||||
import WaitlistIcon from '~icons/material-symbols/timelapse';
|
||||
import CancelledIcon from '~icons/material-symbols/warning';
|
||||
interface StatusIconProps extends SVGProps<SVGSVGElement> {
|
||||
status: StatusType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Icon component based on status
|
||||
* @param props.status status
|
||||
* @returns the icon component
|
||||
*
|
||||
* @param props - The props for the StatusIcon component
|
||||
* @returns The rendered icon component
|
||||
*/
|
||||
export function StatusIcon(props: SVGProps<SVGSVGElement> & { status: StatusType }): JSX.Element | null {
|
||||
export function StatusIcon(props: StatusIconProps): JSX.Element | null {
|
||||
const { status, ...rest } = props;
|
||||
|
||||
switch (status) {
|
||||
case Status.WAITLISTED:
|
||||
return <WaitlistIcon {...rest} />;
|
||||
return <ClockUser weight='fill' {...rest} />;
|
||||
case Status.CLOSED:
|
||||
return <ClosedIcon {...rest} />;
|
||||
return <LockKey weight='fill' {...rest} />;
|
||||
case Status.CANCELLED:
|
||||
return <CancelledIcon {...rest} />;
|
||||
return <Prohibit weight='fill' {...rest} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { customAlphabet } from 'nanoid';
|
||||
/**
|
||||
* Generate secure URL-friendly unique ID.
|
||||
*
|
||||
* @param size Size of the ID. The default size is 12.
|
||||
* @param size - Size of the ID. The default size is 12.
|
||||
* @returns A random string.
|
||||
*/
|
||||
export const generateRandomId = customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 12);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Given a string, returns a string with the first letter capitalized.
|
||||
* @input The string to capitalize.
|
||||
*
|
||||
* @param input - The string to capitalize.
|
||||
*/
|
||||
export function capitalize(input: string): string {
|
||||
let capitalized = '';
|
||||
@@ -25,8 +26,9 @@ export function capitalize(input: string): string {
|
||||
|
||||
/**
|
||||
* Given a string, returns a string with the first letter capitalized.
|
||||
* @param input capitalize the first letter of this string
|
||||
* @returns the string with the first letter capitalized
|
||||
*
|
||||
* @param input - Capitalize the first letter of this string
|
||||
* @returns The string with the first letter capitalized
|
||||
*/
|
||||
export function capitalizeFirstLetter(input: string): string {
|
||||
return input.charAt(0).toUpperCase() + input.slice(1).toLowerCase();
|
||||
@@ -34,8 +36,9 @@ 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.
|
||||
* @param input The string to ellipsify.
|
||||
* @param length The length of the string to return.
|
||||
*
|
||||
* @param input - The string to ellipsify.
|
||||
* @param length - The length of the string to return.
|
||||
* @returns The ellipsified string.
|
||||
*/
|
||||
export const ellipsify = (input: string, chars: number): string => {
|
||||
|
||||
112
src/shared/util/tests/colors.test.ts
Normal file
112
src/shared/util/tests/colors.test.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { getLighterShade, hexToHSL, isValidHexColor } from '@shared/util/colors';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe('hexToHSL', () => {
|
||||
it('should convert pure red to HSL', () => {
|
||||
const result = hexToHSL('#FF0000');
|
||||
expect(result).toEqual([0, 100, 50]);
|
||||
});
|
||||
|
||||
it('should convert pure green to HSL', () => {
|
||||
const result = hexToHSL('#00FF00');
|
||||
expect(result).toEqual([120, 100, 50]);
|
||||
});
|
||||
|
||||
it('should convert pure blue to HSL', () => {
|
||||
const result = hexToHSL('#0000FF');
|
||||
expect(result).toEqual([240, 100, 50]);
|
||||
});
|
||||
|
||||
it('should convert white to HSL', () => {
|
||||
const result = hexToHSL('#FFFFFF');
|
||||
expect(result).toEqual([0, 0, 100]);
|
||||
});
|
||||
|
||||
it('should convert black to HSL', () => {
|
||||
const result = hexToHSL('#000000');
|
||||
expect(result).toEqual([0, 0, 0]);
|
||||
});
|
||||
|
||||
it('should convert UT burnt orange to HSL', () => {
|
||||
const result = hexToHSL('#BF5700');
|
||||
expect(result).toEqual([27, 100, 37]);
|
||||
});
|
||||
|
||||
it('should convert gray to HSL', () => {
|
||||
const result = hexToHSL('#808080');
|
||||
expect(result).toEqual([0, 0, 50]);
|
||||
});
|
||||
|
||||
it('should throw error for invalid hex color', () => {
|
||||
expect(() => hexToHSL('#GGGGGG')).toThrow('hexToRGB returned undefined');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidHexColor', () => {
|
||||
it('should validate 6-digit hex colors with hash', () => {
|
||||
expect(isValidHexColor('#000000')).toBe(true);
|
||||
expect(isValidHexColor('#FFFFFF')).toBe(true);
|
||||
expect(isValidHexColor('#BF5700')).toBe(true);
|
||||
expect(isValidHexColor('#D6D2C4')).toBe(true);
|
||||
});
|
||||
|
||||
it('should validate 6-digit hex colors without hash', () => {
|
||||
expect(isValidHexColor('000000')).toBe(true);
|
||||
expect(isValidHexColor('FFFFFF')).toBe(true);
|
||||
expect(isValidHexColor('BF5700')).toBe(true);
|
||||
});
|
||||
|
||||
it('should validate 3-digit hex colors with hash', () => {
|
||||
expect(isValidHexColor('#000')).toBe(true);
|
||||
expect(isValidHexColor('#FFF')).toBe(true);
|
||||
expect(isValidHexColor('#F0F')).toBe(true);
|
||||
});
|
||||
|
||||
it('should validate 3-digit hex colors without hash', () => {
|
||||
expect(isValidHexColor('000')).toBe(true);
|
||||
expect(isValidHexColor('FFF')).toBe(true);
|
||||
expect(isValidHexColor('F0F')).toBe(true);
|
||||
});
|
||||
|
||||
it('should reject invalid hex colors', () => {
|
||||
expect(isValidHexColor('#')).toBe(false);
|
||||
expect(isValidHexColor('#GGG')).toBe(false);
|
||||
expect(isValidHexColor('#GGGGGG')).toBe(false);
|
||||
expect(isValidHexColor('GGGGGG')).toBe(false);
|
||||
expect(isValidHexColor('#12345')).toBe(false);
|
||||
expect(isValidHexColor('#1234567')).toBe(false);
|
||||
expect(isValidHexColor('not a color')).toBe(false);
|
||||
expect(isValidHexColor('')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLighterShade', () => {
|
||||
it('should lighten a color by default offset (20%)', () => {
|
||||
const result = getLighterShade('#BF5700');
|
||||
expect(result).toBe('#ff8624');
|
||||
});
|
||||
|
||||
it('should lighten black correctly', () => {
|
||||
const result = getLighterShade('#000000');
|
||||
expect(result).toBe('#333333');
|
||||
});
|
||||
|
||||
it('should not exceed 100% lightness', () => {
|
||||
const result = getLighterShade('#FFFFFF', 20);
|
||||
expect(result).toBe('#ffffff');
|
||||
});
|
||||
|
||||
it('should handle custom offset values', () => {
|
||||
const result = getLighterShade('#BF5700', 40);
|
||||
expect(result).toBe('#ffbe8a');
|
||||
});
|
||||
|
||||
it('should maintain hue while increasing lightness', () => {
|
||||
const result = getLighterShade('#00FF00', 20); // Pure green
|
||||
expect(result.toLowerCase()).toBe('#66ff66');
|
||||
});
|
||||
|
||||
it('should throw error for invalid hex color', () => {
|
||||
expect(() => getLighterShade('#GGGGGG')).toThrow('color: Invalid hex.');
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,7 @@ 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.
|
||||
*/
|
||||
@@ -14,8 +15,8 @@ export const sleep = (milliseconds: number): Promise<void> => new Promise(resolv
|
||||
/**
|
||||
* Checks to see if expired by the time first stored and the time frame that it is stored for
|
||||
*
|
||||
* @param time time it was stored
|
||||
* @param threshold time frame it can be stored for
|
||||
* @return true if expired, false if the time frame is still in range
|
||||
* @param time - time it was stored
|
||||
* @param threshold - time frame it can be stored for
|
||||
* @returns true if expired, false if the time frame is still in range
|
||||
*/
|
||||
export const didExpire = (time: number, threshold: number): boolean => time + threshold <= Date.now();
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
import { CalendarDots, ChatText, FileText, ImageSquare, Minus, Plus, Smiley } from '@phosphor-icons/react';
|
||||
import { colorsFlattened } from '@shared/util/themeColors';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Button } from '@views/components/common/Button';
|
||||
import React from 'react';
|
||||
|
||||
import AddIcon from '~icons/material-symbols/add';
|
||||
import CalendarMonthIcon from '~icons/material-symbols/calendar-month';
|
||||
import DescriptionIcon from '~icons/material-symbols/description';
|
||||
import ImagePlaceholderIcon from '~icons/material-symbols/image';
|
||||
import HappyFaceIcon from '~icons/material-symbols/mood';
|
||||
import RemoveIcon from '~icons/material-symbols/remove';
|
||||
import ReviewsIcon from '~icons/material-symbols/reviews';
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
||||
const meta = {
|
||||
title: 'Components/Common/Button',
|
||||
@@ -24,7 +17,7 @@ const meta = {
|
||||
// More on argTypes: https://storybook.js.org/docs/api/argtypes
|
||||
args: {
|
||||
children: 'Button',
|
||||
icon: ImagePlaceholderIcon,
|
||||
icon: ImageSquare,
|
||||
},
|
||||
argTypes: {
|
||||
children: { control: 'text' },
|
||||
@@ -42,6 +35,62 @@ export const Default: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const Small: Story = {
|
||||
// @ts-ignore
|
||||
args: {
|
||||
children: '',
|
||||
},
|
||||
render: props => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
|
||||
<div style={{ display: 'flex', gap: '15px' }}>
|
||||
<Button {...props} variant='filled' color='ut-black' size='small'>
|
||||
Button
|
||||
</Button>
|
||||
<Button {...props} variant='outline' color='ut-black' size='small'>
|
||||
Button
|
||||
</Button>
|
||||
<Button {...props} variant='minimal' color='ut-black' size='small'>
|
||||
Button
|
||||
</Button>
|
||||
</div>
|
||||
<hr />
|
||||
<div style={{ display: 'flex', gap: '15px' }}>
|
||||
<Button {...props} icon={ImageSquare} variant='filled' color='ut-black' size='small' />
|
||||
<Button {...props} icon={ImageSquare} variant='outline' color='ut-black' size='small' />
|
||||
<Button {...props} icon={ImageSquare} variant='minimal' color='ut-black' size='small' />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const Mini: Story = {
|
||||
// @ts-ignore
|
||||
args: {
|
||||
children: '',
|
||||
},
|
||||
render: props => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
|
||||
<div style={{ display: 'flex', gap: '15px' }}>
|
||||
<Button {...props} variant='filled' color='ut-black' size='mini'>
|
||||
Button
|
||||
</Button>
|
||||
<Button {...props} variant='outline' color='ut-black' size='mini'>
|
||||
Button
|
||||
</Button>
|
||||
<Button {...props} variant='minimal' color='ut-black' size='mini'>
|
||||
Button
|
||||
</Button>
|
||||
</div>
|
||||
<hr />
|
||||
<div style={{ display: 'flex', gap: '15px' }}>
|
||||
<Button {...props} icon={ImageSquare} variant='filled' color='ut-black' size='mini' />
|
||||
<Button {...props} icon={ImageSquare} variant='outline' color='ut-black' size='mini' />
|
||||
<Button {...props} icon={ImageSquare} variant='minimal' color='ut-black' size='mini' />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
variant: 'filled',
|
||||
@@ -57,7 +106,7 @@ export const Grid: Story = {
|
||||
<div style={{ display: 'flex', gap: '15px' }}>
|
||||
<Button {...props} variant='filled' color='ut-black' />
|
||||
<Button {...props} variant='outline' color='ut-black' />
|
||||
<Button {...props} variant='single' color='ut-black' />
|
||||
<Button {...props} variant='minimal' color='ut-black' />
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
@@ -65,7 +114,7 @@ export const Grid: Story = {
|
||||
<div style={{ display: 'flex', gap: '15px' }}>
|
||||
<Button {...props} variant='filled' color='ut-black' disabled />
|
||||
<Button {...props} variant='outline' color='ut-black' disabled />
|
||||
<Button {...props} variant='single' color='ut-black' disabled />
|
||||
<Button {...props} variant='minimal' color='ut-black' disabled />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
@@ -89,12 +138,12 @@ export const PrettyColors: Story = {
|
||||
<Button {...props} variant='outline' color={color}>
|
||||
Button
|
||||
</Button>
|
||||
<Button {...props} variant='single' color={color}>
|
||||
<Button {...props} variant='minimal' color={color}>
|
||||
Button
|
||||
</Button>
|
||||
<Button {...props} variant='filled' color={color} />
|
||||
<Button {...props} variant='outline' color={color} />
|
||||
<Button {...props} variant='single' color={color} />
|
||||
<Button {...props} variant='minimal' color={color} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -106,10 +155,10 @@ export const PrettyColors: Story = {
|
||||
export const CourseButtons: Story = {
|
||||
render: props => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px', alignItems: 'center' }}>
|
||||
<Button {...props} variant='filled' color='ut-green' icon={AddIcon}>
|
||||
<Button {...props} variant='filled' color='ut-green' icon={Plus}>
|
||||
Add Course
|
||||
</Button>
|
||||
<Button {...props} variant='filled' color='theme-red' icon={RemoveIcon}>
|
||||
<Button {...props} variant='filled' color='theme-red' icon={Minus}>
|
||||
Remove Course
|
||||
</Button>
|
||||
</div>
|
||||
@@ -129,17 +178,17 @@ export const CourseCatalogActionButtons: Story = {
|
||||
},
|
||||
render: props => (
|
||||
<div style={{ display: 'flex', gap: '15px' }}>
|
||||
<Button {...props} variant='filled' color='ut-burntorange' icon={CalendarMonthIcon} />
|
||||
<Button {...props} variant='outline' color='ut-blue' icon={ReviewsIcon}>
|
||||
<Button {...props} variant='filled' color='ut-burntorange' icon={CalendarDots} />
|
||||
<Button {...props} variant='outline' color='ut-blue' icon={ChatText}>
|
||||
RateMyProf
|
||||
</Button>
|
||||
<Button {...props} variant='outline' color='ut-teal' icon={HappyFaceIcon}>
|
||||
<Button {...props} variant='outline' color='ut-teal' icon={Smiley}>
|
||||
CES
|
||||
</Button>
|
||||
<Button {...props} variant='outline' color='ut-orange' icon={DescriptionIcon}>
|
||||
<Button {...props} variant='outline' color='ut-orange' icon={FileText}>
|
||||
Past Syllabi
|
||||
</Button>
|
||||
<Button {...props} variant='filled' color='ut-green' icon={AddIcon}>
|
||||
<Button {...props} variant='filled' color='ut-green' icon={Plus}>
|
||||
Add Course
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { ArrowsVertical } from '@phosphor-icons/react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Button } from '@views/components/common/Button';
|
||||
import DialogProvider, { usePrompt } from '@views/components/common/DialogProvider/DialogProvider';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import MaterialSymbolsExpandAllRoundedIcon from '~icons/material-symbols/expand-all-rounded';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Common/DialogProvider',
|
||||
component: DialogProvider,
|
||||
@@ -46,7 +45,13 @@ const InnerComponent = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Button variant='filled' color='ut-burntorange' icon={MaterialSymbolsExpandAllRoundedIcon} onClick={myShow}>
|
||||
<Button
|
||||
variant='filled'
|
||||
color='ut-burntorange'
|
||||
icon={ArrowsVertical}
|
||||
iconProps={{ className: 'h-4 w-4' }}
|
||||
onClick={myShow}
|
||||
>
|
||||
Open Dialog
|
||||
</Button>
|
||||
);
|
||||
@@ -86,7 +91,7 @@ const FiveDialogsInnerComponent = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Button variant='filled' color='ut-burntorange' icon={MaterialSymbolsExpandAllRoundedIcon} onClick={myShow}>
|
||||
<Button variant='filled' color='ut-burntorange' icon={ArrowsVertical} onClick={myShow}>
|
||||
Open Dialogs
|
||||
</Button>
|
||||
);
|
||||
@@ -121,7 +126,7 @@ const NestedDialogsInnerComponent = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Button variant='filled' color='ut-burntorange' icon={MaterialSymbolsExpandAllRoundedIcon} onClick={myShow}>
|
||||
<Button variant='filled' color='ut-burntorange' icon={ArrowsVertical} onClick={myShow}>
|
||||
Open Next Dialog
|
||||
</Button>
|
||||
);
|
||||
@@ -161,7 +166,7 @@ const DialogWithOnCloseInnerComponent = () => {
|
||||
<h1>
|
||||
You closed the button below {timesClosed} {timesClosed === 1 ? 'time' : 'times'}
|
||||
</h1>
|
||||
<Button variant='filled' color='ut-burntorange' icon={MaterialSymbolsExpandAllRoundedIcon} onClick={myShow}>
|
||||
<Button variant='filled' color='ut-burntorange' icon={ArrowsVertical} onClick={myShow}>
|
||||
Open Dialog
|
||||
</Button>
|
||||
</>
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { CalendarDots, ChatText, FileText, Plus, Smiley } from '@phosphor-icons/react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Button } from '@views/components/common/Button';
|
||||
import Divider from '@views/components/common/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,
|
||||
@@ -59,18 +54,18 @@ export const CourseCatalogActionButtons: Story = {
|
||||
},
|
||||
render: props => (
|
||||
<div className='flex items-center gap-3.75'>
|
||||
<Button variant='filled' color='ut-burntorange' icon={CalendarMonthIcon} />
|
||||
<Button variant='filled' color='ut-burntorange' icon={CalendarDots} />
|
||||
<Divider {...props} />
|
||||
<Button variant='outline' color='ut-blue' icon={ReviewsIcon}>
|
||||
<Button variant='outline' color='ut-blue' icon={ChatText}>
|
||||
RateMyProf
|
||||
</Button>
|
||||
<Button variant='outline' color='ut-teal' icon={HappyFaceIcon}>
|
||||
<Button variant='outline' color='ut-teal' icon={Smiley}>
|
||||
CES
|
||||
</Button>
|
||||
<Button variant='outline' color='ut-orange' icon={DescriptionIcon}>
|
||||
<Button variant='outline' color='ut-orange' icon={FileText}>
|
||||
Past Syllabi
|
||||
</Button>
|
||||
<Button variant='filled' color='ut-green' icon={AddIcon}>
|
||||
<Button variant='filled' color='ut-green' icon={Plus}>
|
||||
Add Course
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -2,10 +2,10 @@ import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
||||
import { UserSchedule } from '@shared/types/UserSchedule';
|
||||
import { generateRandomId } from '@shared/util/random';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import List from '@views/components/common/List';
|
||||
import type { ScheduleDropdownProps } from '@views/components/common/ScheduleDropdown';
|
||||
import ScheduleDropdown from '@views/components/common/ScheduleDropdown';
|
||||
import ScheduleListItem from '@views/components/common/ScheduleListItem';
|
||||
import { SortableList } from '@views/components/common/SortableList';
|
||||
import useSchedules, { getActiveSchedule, switchSchedule } from '@views/hooks/useSchedules';
|
||||
import type { Serialized } from 'chrome-extension-toolkit';
|
||||
import React, { useEffect } from 'react';
|
||||
@@ -48,10 +48,10 @@ const meta: Meta<typeof ScheduleDropdown> = {
|
||||
return (
|
||||
<div className='w-80'>
|
||||
<ScheduleDropdown {...args}>
|
||||
<List
|
||||
<SortableList
|
||||
className='gap-spacing-3'
|
||||
draggables={schedules}
|
||||
itemKey={s => s.id}
|
||||
onReordered={reordered => {
|
||||
onChange={reordered => {
|
||||
const activeSchedule = getActiveSchedule();
|
||||
const activeIndex = reordered.findIndex(s => s.id === activeSchedule.id);
|
||||
|
||||
@@ -59,18 +59,10 @@ const meta: Meta<typeof ScheduleDropdown> = {
|
||||
UserScheduleStore.set('schedules', reordered);
|
||||
UserScheduleStore.set('activeIndex', activeIndex);
|
||||
}}
|
||||
gap={10}
|
||||
>
|
||||
{(schedule, handleProps) => (
|
||||
<ScheduleListItem
|
||||
schedule={schedule}
|
||||
onClick={() => {
|
||||
switchSchedule(schedule.id);
|
||||
}}
|
||||
dragHandleProps={handleProps}
|
||||
/>
|
||||
renderItem={schedule => (
|
||||
<ScheduleListItem schedule={schedule} onClick={() => switchSchedule(schedule.id)} />
|
||||
)}
|
||||
</List>
|
||||
/>
|
||||
</ScheduleDropdown>
|
||||
</div>
|
||||
);
|
||||
|
||||
159
src/stories/components/FileUpload.stories.tsx
Normal file
159
src/stories/components/FileUpload.stories.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import { ImageSquare } from '@phosphor-icons/react';
|
||||
import { colorsFlattened } from '@shared/util/themeColors';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import FileUpload from '@views/components/common/FileUpload';
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* Stole this straight from Button.stories.tsx to test the input
|
||||
*/
|
||||
const meta = {
|
||||
title: 'Components/Common/FileUpload',
|
||||
component: FileUpload,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
children: 'Upload File',
|
||||
icon: ImageSquare,
|
||||
},
|
||||
argTypes: {
|
||||
children: { control: 'text' },
|
||||
color: {
|
||||
control: 'select',
|
||||
options: Object.keys(colorsFlattened),
|
||||
},
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['filled', 'outline', 'single'],
|
||||
},
|
||||
disabled: {
|
||||
control: 'boolean',
|
||||
},
|
||||
onChange: { action: 'file selected' }, // action to show when file is selected
|
||||
},
|
||||
} satisfies Meta<typeof FileUpload>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
variant: 'filled',
|
||||
color: 'ut-black', // Default theme color
|
||||
},
|
||||
};
|
||||
|
||||
export const Small: Story = {
|
||||
// @ts-ignore
|
||||
args: {
|
||||
children: '',
|
||||
},
|
||||
render: props => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
|
||||
<div style={{ display: 'flex', gap: '15px' }}>
|
||||
<FileUpload {...props} variant='filled' color='ut-black' size='small'>
|
||||
Upload File
|
||||
</FileUpload>
|
||||
<FileUpload {...props} variant='outline' color='ut-black' size='small'>
|
||||
Upload File
|
||||
</FileUpload>
|
||||
<FileUpload {...props} variant='minimal' color='ut-black' size='small'>
|
||||
Upload File
|
||||
</FileUpload>
|
||||
</div>
|
||||
<hr />
|
||||
<div style={{ display: 'flex', gap: '15px' }}>
|
||||
<FileUpload {...props} icon={ImageSquare} variant='filled' color='ut-black' size='small' />
|
||||
<FileUpload {...props} icon={ImageSquare} variant='outline' color='ut-black' size='small' />
|
||||
<FileUpload {...props} icon={ImageSquare} variant='minimal' color='ut-black' size='small' />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const Mini: Story = {
|
||||
// @ts-ignore
|
||||
args: {
|
||||
children: '',
|
||||
},
|
||||
render: props => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
|
||||
<div style={{ display: 'flex', gap: '15px' }}>
|
||||
<FileUpload {...props} variant='filled' color='ut-black' size='mini'>
|
||||
Button
|
||||
</FileUpload>
|
||||
<FileUpload {...props} variant='outline' color='ut-black' size='mini'>
|
||||
Button
|
||||
</FileUpload>
|
||||
<FileUpload {...props} variant='minimal' color='ut-black' size='mini'>
|
||||
Button
|
||||
</FileUpload>
|
||||
</div>
|
||||
<hr />
|
||||
<div style={{ display: 'flex', gap: '15px' }}>
|
||||
<FileUpload {...props} icon={ImageSquare} variant='filled' color='ut-black' size='mini' />
|
||||
<FileUpload {...props} icon={ImageSquare} variant='outline' color='ut-black' size='mini' />
|
||||
<FileUpload {...props} icon={ImageSquare} variant='minimal' color='ut-black' size='mini' />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
variant: 'filled',
|
||||
color: 'ut-black',
|
||||
disabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
export const Grid: Story = {
|
||||
render: props => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
|
||||
<div style={{ display: 'flex', gap: '15px' }}>
|
||||
<FileUpload {...props} variant='filled' color='ut-black' />
|
||||
<FileUpload {...props} variant='outline' color='ut-black' />
|
||||
<FileUpload {...props} variant='minimal' color='ut-black' />
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div style={{ display: 'flex', gap: '15px' }}>
|
||||
<FileUpload {...props} variant='filled' color='ut-black' disabled />
|
||||
<FileUpload {...props} variant='outline' color='ut-black' disabled />
|
||||
<FileUpload {...props} variant='minimal' color='ut-black' disabled />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const PrettyColors: Story = {
|
||||
// @ts-ignore
|
||||
args: {
|
||||
children: '',
|
||||
},
|
||||
render: props => {
|
||||
const colorsNames = Object.keys(colorsFlattened) as (keyof typeof colorsFlattened)[];
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
|
||||
{colorsNames.map(color => (
|
||||
<div style={{ display: 'flex', gap: '15px' }} key={color}>
|
||||
<FileUpload {...props} variant='filled' color={color}>
|
||||
Button
|
||||
</FileUpload>
|
||||
<FileUpload {...props} variant='outline' color={color}>
|
||||
Button
|
||||
</FileUpload>
|
||||
<FileUpload {...props} variant='minimal' color={color}>
|
||||
Button
|
||||
</FileUpload>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -56,10 +56,10 @@ export const AreYouSure: StoryObj<PromptDialogProps> = {
|
||||
</Text>
|
||||
),
|
||||
children: [
|
||||
<Button key='yes' variant='single' color='ut-burntorange'>
|
||||
<Button key='yes' variant='minimal' color='ut-burntorange'>
|
||||
Yes
|
||||
</Button>,
|
||||
<Button key='no' variant='single' color='ut-black'>
|
||||
<Button key='no' variant='minimal' color='ut-black'>
|
||||
No
|
||||
</Button>,
|
||||
],
|
||||
@@ -76,7 +76,7 @@ export const YouHave10ActiveSchedules: StoryObj<PromptDialogProps> = {
|
||||
</Text>
|
||||
),
|
||||
children: [
|
||||
<Button key='yes' variant='single' color='ut-black'>
|
||||
<Button key='yes' variant='minimal' color='ut-black'>
|
||||
I understand
|
||||
</Button>,
|
||||
],
|
||||
@@ -94,10 +94,10 @@ export const WelcomeToUTRPV2: StoryObj<PromptDialogProps> = {
|
||||
</Text>
|
||||
),
|
||||
children: [
|
||||
<Button key='migrate' variant='single' color='ut-black'>
|
||||
<Button key='migrate' variant='minimal' color='ut-black'>
|
||||
Don't Migrate
|
||||
</Button>,
|
||||
<Button key='start-fresh' variant='single' color='ut-burntorange'>
|
||||
<Button key='start-fresh' variant='minimal' color='ut-burntorange'>
|
||||
Migrate
|
||||
</Button>,
|
||||
],
|
||||
|
||||
19
src/stories/components/ResourceLinks.stories.tsx
Normal file
19
src/stories/components/ResourceLinks.stories.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import ResourceLinks from '@views/components/calendar/ResourceLinks';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Common/ResourceLinks',
|
||||
component: ResourceLinks,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
} satisfies Meta<typeof ResourceLinks>;
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {},
|
||||
};
|
||||
@@ -1,16 +1,15 @@
|
||||
import type { DraggableProvidedDragHandleProps } from '@hello-pangea/dnd';
|
||||
import { Course, Status } from '@shared/types/Course';
|
||||
import { CourseMeeting } from '@shared/types/CourseMeeting';
|
||||
import Instructor from '@shared/types/Instructor';
|
||||
import { tailwindColorways } from '@shared/util/storybook';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import List from '@views/components/common/List';
|
||||
import PopupCourseBlock from '@views/components/common/PopupCourseBlock';
|
||||
import type { BaseItem } from '@views/components/common/SortableList';
|
||||
import { SortableList } from '@views/components/common/SortableList';
|
||||
import React from 'react';
|
||||
|
||||
const numberOfCourses = 5;
|
||||
|
||||
// TODO: move into utils
|
||||
/**
|
||||
* Generates an array of courses.
|
||||
*
|
||||
@@ -73,36 +72,34 @@ const generateCourses = (count: number): Course[] => {
|
||||
};
|
||||
|
||||
const exampleCourses = generateCourses(numberOfCourses);
|
||||
const generateCourseBlocks = (course: Course, dragHandleProps: DraggableProvidedDragHandleProps) => (
|
||||
<PopupCourseBlock key={course.uniqueId} course={course} colors={course.colors} dragHandleProps={dragHandleProps} />
|
||||
);
|
||||
|
||||
type CourseWithId = Course & BaseItem;
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Common/List',
|
||||
component: List,
|
||||
title: 'Components/Common/SortableList',
|
||||
component: SortableList,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
gap: { control: 'number' },
|
||||
},
|
||||
} satisfies Meta<typeof List<Course>>;
|
||||
} satisfies Meta<typeof SortableList<CourseWithId>>;
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<Meta<typeof List<Course>>>;
|
||||
type Story = StoryObj<Meta<typeof SortableList<CourseWithId>>>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
draggables: exampleCourses,
|
||||
children: generateCourseBlocks,
|
||||
itemKey: item => item.uniqueId,
|
||||
gap: 12,
|
||||
onReordered: () => {},
|
||||
draggables: exampleCourses.map(course => ({
|
||||
id: course.uniqueId,
|
||||
...course,
|
||||
getConflicts: course.getConflicts,
|
||||
})),
|
||||
onChange: () => {},
|
||||
renderItem: course => <PopupCourseBlock key={course.id} course={course} colors={course.colors} />,
|
||||
},
|
||||
render: args => (
|
||||
<div className='w-sm'>
|
||||
<List {...args} />
|
||||
<div className='h-3xl w-3xl transform-none'>
|
||||
<SortableList {...args} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -3,6 +3,7 @@ import Instructor from '@shared/types/Instructor';
|
||||
import { getCourseColors } from '@shared/util/colors';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import CalendarBottomBar from '@views/components/calendar/CalendarBottomBar';
|
||||
import type { CalendarGridCourse } from '@views/hooks/useFlattenedCourseSchedule';
|
||||
import React from 'react';
|
||||
|
||||
const exampleGovCourse: Course = new Course({
|
||||
@@ -91,9 +92,18 @@ export const Default: Story = {
|
||||
async: true,
|
||||
calendarGridPoint: { dayIndex: -1, endIndex: -1, startIndex: -1 },
|
||||
componentProps: {
|
||||
colors: getCourseColors('pink', 200),
|
||||
courseDeptAndInstr: `${exampleGovCourse.department} ${exampleGovCourse.number} – ${exampleGovCourse.instructors[0]!.lastName}`,
|
||||
status: exampleGovCourse.status,
|
||||
blockData: {
|
||||
calendarGridPoint: { dayIndex: -1, endIndex: -1, startIndex: -1 },
|
||||
componentProps: {
|
||||
courseDeptAndInstr: `${exampleGovCourse.department} ${exampleGovCourse.number} – ${exampleGovCourse.instructors[0]!.lastName}`,
|
||||
status: exampleGovCourse.status,
|
||||
blockData: {} as CalendarGridCourse,
|
||||
},
|
||||
course: exampleGovCourse,
|
||||
async: true,
|
||||
},
|
||||
},
|
||||
course: exampleGovCourse,
|
||||
},
|
||||
@@ -101,9 +111,18 @@ export const Default: Story = {
|
||||
async: true,
|
||||
calendarGridPoint: { dayIndex: -1, endIndex: -1, startIndex: -1 },
|
||||
componentProps: {
|
||||
colors: getCourseColors('slate', 500),
|
||||
courseDeptAndInstr: `${examplePsyCourse.department} ${examplePsyCourse.number} – ${examplePsyCourse.instructors[0]!.lastName}`,
|
||||
status: examplePsyCourse.status,
|
||||
blockData: {
|
||||
calendarGridPoint: { dayIndex: -1, endIndex: -1, startIndex: -1 },
|
||||
componentProps: {
|
||||
courseDeptAndInstr: `${examplePsyCourse.department} ${examplePsyCourse.number} – ${examplePsyCourse.instructors[0]!.lastName}`,
|
||||
status: examplePsyCourse.status,
|
||||
blockData: {} as CalendarGridCourse,
|
||||
},
|
||||
course: examplePsyCourse,
|
||||
async: true,
|
||||
},
|
||||
},
|
||||
course: examplePsyCourse,
|
||||
},
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Status } from '@shared/types/Course';
|
||||
import { getCourseColors } from '@shared/util/colors';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import type { CalendarCourseCellProps } from '@views/components/calendar/CalendarCourseCell';
|
||||
import CalendarCourseCell from '@views/components/calendar/CalendarCourseCell';
|
||||
import type { CalendarGridCourse } from '@views/hooks/useFlattenedCourseSchedule';
|
||||
import React from 'react';
|
||||
|
||||
import { ExampleCourse } from '../PopupCourseBlock.stories';
|
||||
@@ -19,7 +19,6 @@ const meta = {
|
||||
className: { control: { type: 'text' } },
|
||||
status: { control: { type: 'select', options: Object.values(Status) } },
|
||||
timeAndLocation: { control: { type: 'text' } },
|
||||
colors: { control: { type: 'object' } },
|
||||
},
|
||||
render: (args: CalendarCourseCellProps) => (
|
||||
<div className='w-45'>
|
||||
@@ -30,24 +29,71 @@ const meta = {
|
||||
courseDeptAndInstr: ExampleCourse.department,
|
||||
className: ExampleCourse.number,
|
||||
status: ExampleCourse.status,
|
||||
timeAndLocation: ExampleCourse.schedule.meetings[0]!.getTimeString({ separator: '-' }),
|
||||
|
||||
colors: getCourseColors('emerald', 500),
|
||||
timeAndLocation: ExampleCourse.schedule.meetings[0]!.getTimeString({ separator: '–' }),
|
||||
},
|
||||
} satisfies Meta<typeof CalendarCourseCell>;
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
courseDeptAndInstr: ExampleCourse.department,
|
||||
className: ExampleCourse.number,
|
||||
status: ExampleCourse.status,
|
||||
timeAndLocation: ExampleCourse.schedule.meetings[0]!.getTimeString({ separator: '-' }),
|
||||
blockData: {
|
||||
calendarGridPoint: {
|
||||
dayIndex: 4,
|
||||
startIndex: 10,
|
||||
endIndex: 11,
|
||||
},
|
||||
course: ExampleCourse,
|
||||
async: false,
|
||||
componentProps: {
|
||||
courseDeptAndInstr: ExampleCourse.department,
|
||||
status: ExampleCourse.status,
|
||||
timeAndLocation: ExampleCourse.schedule.meetings[0]!.getTimeString({ separator: '-' }),
|
||||
blockData: {} as CalendarGridCourse,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Variants: Story = {
|
||||
render: props => (
|
||||
<div className='grid grid-cols-2 h-40 max-w-60 w-90vw gap-x-4 gap-y-2'>
|
||||
<CalendarCourseCell {...props} colors={getCourseColors('green', 500)} />
|
||||
<CalendarCourseCell {...props} colors={getCourseColors('teal', 400)} />
|
||||
<CalendarCourseCell {...props} colors={getCourseColors('indigo', 400)} />
|
||||
<CalendarCourseCell {...props} colors={getCourseColors('red', 500)} />
|
||||
</div>
|
||||
),
|
||||
args: {
|
||||
courseDeptAndInstr: ExampleCourse.department,
|
||||
className: ExampleCourse.number,
|
||||
status: ExampleCourse.status,
|
||||
timeAndLocation: ExampleCourse.schedule.meetings[0]!.getTimeString({ separator: '-' }),
|
||||
blockData: {
|
||||
calendarGridPoint: {
|
||||
dayIndex: 4,
|
||||
startIndex: 10,
|
||||
endIndex: 11,
|
||||
},
|
||||
course: ExampleCourse,
|
||||
async: false,
|
||||
componentProps: {
|
||||
courseDeptAndInstr: ExampleCourse.department,
|
||||
status: ExampleCourse.status,
|
||||
timeAndLocation: ExampleCourse.schedule.meetings[0]!.getTimeString({ separator: '-' }),
|
||||
blockData: {
|
||||
calendarGridPoint: {
|
||||
dayIndex: 4,
|
||||
startIndex: 10,
|
||||
endIndex: 11,
|
||||
},
|
||||
componentProps: {
|
||||
courseDeptAndInstr: ExampleCourse.department,
|
||||
status: ExampleCourse.status,
|
||||
timeAndLocation: ExampleCourse.schedule.meetings[0]!.getTimeString({ separator: '-' }),
|
||||
blockData: {} as CalendarGridCourse,
|
||||
},
|
||||
course: ExampleCourse,
|
||||
async: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
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';
|
||||
import type { CalendarGridCourse } from '@views/hooks/useFlattenedCourseSchedule';
|
||||
@@ -32,7 +31,21 @@ const testData: CalendarGridCourse[] = [
|
||||
courseDeptAndInstr: 'Course 1',
|
||||
timeAndLocation: '9:00 AM - 10:00 AM, Room 101',
|
||||
status: Status.OPEN,
|
||||
colors: getCourseColors('emerald', 500),
|
||||
blockData: {
|
||||
calendarGridPoint: {
|
||||
dayIndex: 4,
|
||||
startIndex: 10,
|
||||
endIndex: 11,
|
||||
},
|
||||
componentProps: {
|
||||
courseDeptAndInstr: 'Course 1',
|
||||
timeAndLocation: '9:00 AM - 10:00 AM, Room 101',
|
||||
status: Status.OPEN,
|
||||
blockData: {} as CalendarGridCourse,
|
||||
},
|
||||
course: ExampleCourse,
|
||||
async: false,
|
||||
},
|
||||
},
|
||||
course: ExampleCourse,
|
||||
async: false,
|
||||
@@ -47,7 +60,21 @@ const testData: CalendarGridCourse[] = [
|
||||
courseDeptAndInstr: 'Course 1',
|
||||
timeAndLocation: '9:00 AM - 10:00 AM, Room 101',
|
||||
status: Status.OPEN,
|
||||
colors: getCourseColors('emerald', 500),
|
||||
blockData: {
|
||||
calendarGridPoint: {
|
||||
dayIndex: 2,
|
||||
startIndex: 5,
|
||||
endIndex: 6,
|
||||
},
|
||||
componentProps: {
|
||||
courseDeptAndInstr: 'Course 1',
|
||||
timeAndLocation: '9:00 AM - 10:00 AM, Room 101',
|
||||
status: Status.OPEN,
|
||||
blockData: {} as CalendarGridCourse,
|
||||
},
|
||||
course: ExampleCourse,
|
||||
async: false,
|
||||
},
|
||||
},
|
||||
course: ExampleCourse,
|
||||
async: false,
|
||||
@@ -62,7 +89,21 @@ const testData: CalendarGridCourse[] = [
|
||||
courseDeptAndInstr: 'Course 2',
|
||||
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
|
||||
status: Status.CLOSED,
|
||||
colors: getCourseColors('emerald', 500),
|
||||
blockData: {
|
||||
calendarGridPoint: {
|
||||
dayIndex: 1,
|
||||
startIndex: 10,
|
||||
endIndex: 12,
|
||||
},
|
||||
componentProps: {
|
||||
courseDeptAndInstr: 'Course 2',
|
||||
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
|
||||
status: Status.CLOSED,
|
||||
blockData: {} as CalendarGridCourse,
|
||||
},
|
||||
course: ExampleCourse,
|
||||
async: false,
|
||||
},
|
||||
},
|
||||
course: ExampleCourse,
|
||||
async: false,
|
||||
@@ -77,7 +118,21 @@ const testData: CalendarGridCourse[] = [
|
||||
courseDeptAndInstr: 'Course 1',
|
||||
timeAndLocation: '9:00 AM - 10:00 AM, Room 101',
|
||||
status: Status.OPEN,
|
||||
colors: getCourseColors('emerald', 500),
|
||||
blockData: {
|
||||
calendarGridPoint: {
|
||||
dayIndex: 4,
|
||||
startIndex: 10,
|
||||
endIndex: 11,
|
||||
},
|
||||
componentProps: {
|
||||
courseDeptAndInstr: 'Course 1',
|
||||
timeAndLocation: '9:00 AM - 10:00 AM, Room 101',
|
||||
status: Status.OPEN,
|
||||
blockData: {} as CalendarGridCourse,
|
||||
},
|
||||
course: ExampleCourse,
|
||||
async: false,
|
||||
},
|
||||
},
|
||||
course: ExampleCourse,
|
||||
async: false,
|
||||
@@ -92,7 +147,21 @@ const testData: CalendarGridCourse[] = [
|
||||
courseDeptAndInstr: 'Course 2',
|
||||
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
|
||||
status: Status.CLOSED,
|
||||
colors: getCourseColors('emerald', 500),
|
||||
blockData: {
|
||||
calendarGridPoint: {
|
||||
dayIndex: 1,
|
||||
startIndex: 10,
|
||||
endIndex: 12,
|
||||
},
|
||||
componentProps: {
|
||||
courseDeptAndInstr: 'Course 2',
|
||||
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
|
||||
status: Status.CLOSED,
|
||||
blockData: {} as CalendarGridCourse,
|
||||
},
|
||||
course: ExampleCourse,
|
||||
async: false,
|
||||
},
|
||||
},
|
||||
course: ExampleCourse,
|
||||
async: false,
|
||||
@@ -107,7 +176,21 @@ const testData: CalendarGridCourse[] = [
|
||||
courseDeptAndInstr: 'Course 3',
|
||||
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
|
||||
status: Status.CLOSED,
|
||||
colors: getCourseColors('emerald', 500),
|
||||
blockData: {
|
||||
calendarGridPoint: {
|
||||
dayIndex: 1,
|
||||
startIndex: 10,
|
||||
endIndex: 12,
|
||||
},
|
||||
componentProps: {
|
||||
courseDeptAndInstr: 'Course 3',
|
||||
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
|
||||
status: Status.CLOSED,
|
||||
blockData: {} as CalendarGridCourse,
|
||||
},
|
||||
course: ExampleCourse,
|
||||
async: false,
|
||||
},
|
||||
},
|
||||
course: ExampleCourse,
|
||||
async: false,
|
||||
@@ -122,7 +205,21 @@ const testData: CalendarGridCourse[] = [
|
||||
courseDeptAndInstr: 'Course 4',
|
||||
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
|
||||
status: Status.CLOSED,
|
||||
colors: getCourseColors('emerald', 500),
|
||||
blockData: {
|
||||
calendarGridPoint: {
|
||||
dayIndex: 1,
|
||||
startIndex: 10,
|
||||
endIndex: 12,
|
||||
},
|
||||
componentProps: {
|
||||
courseDeptAndInstr: 'Course 4',
|
||||
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
|
||||
status: Status.CLOSED,
|
||||
blockData: {} as CalendarGridCourse,
|
||||
},
|
||||
course: ExampleCourse,
|
||||
async: false,
|
||||
},
|
||||
},
|
||||
course: ExampleCourse,
|
||||
async: false,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import CalendarHeader from '@views/components/calendar/CalenderHeader';
|
||||
import CalendarHeader from '@views/components/calendar/CalendarHeader/CalendarHeader';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Calendar/CalendarHeader',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { ThemeColor } from '@shared/types/ThemeColors';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import CourseCellColorPicker from '@views/components/calendar/CalendarCourseCellColorPicker/CourseCellColorPicker';
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Calendar/CourseCellColorPicker',
|
||||
@@ -12,15 +11,7 @@ export default meta;
|
||||
type Story = StoryObj<typeof CourseCellColorPicker>;
|
||||
|
||||
function CourseCellColorPickerWithState() {
|
||||
const [, setSelectedColor] = useState<ThemeColor | null>(null);
|
||||
const [isInvertColorsToggled, setIsInvertColorsToggled] = useState<boolean>(false);
|
||||
return (
|
||||
<CourseCellColorPicker
|
||||
setSelectedColor={setSelectedColor}
|
||||
isInvertColorsToggled={isInvertColorsToggled}
|
||||
setIsInvertColorsToggled={setIsInvertColorsToggled}
|
||||
/>
|
||||
);
|
||||
return <CourseCellColorPicker defaultColor='#000000' />;
|
||||
}
|
||||
|
||||
export const Default: Story = {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { OptionsStore } from '@shared/storage/OptionsStore';
|
||||
import type { Course, ScrapedRow } from '@shared/types/Course';
|
||||
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
||||
import AutoLoad from '@views/components/injected/AutoLoad/AutoLoad';
|
||||
import CourseCatalogInjectedPopup from '@views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup';
|
||||
import NewSearchLink from '@views/components/injected/NewSearchLink';
|
||||
import RecruitmentBanner from '@views/components/injected/RecruitmentBanner/RecruitmentBanner';
|
||||
import TableHead from '@views/components/injected/TableHead';
|
||||
import TableRow from '@views/components/injected/TableRow/TableRow';
|
||||
@@ -24,6 +26,7 @@ export default function CourseCatalogMain({ support }: Props): JSX.Element | nul
|
||||
const [rows, setRows] = React.useState<ScrapedRow[]>([]);
|
||||
const [selectedCourse, setSelectedCourse] = useState<Course | null>(null);
|
||||
const [showPopup, setShowPopup] = useState(false);
|
||||
const [enableScrollToLoad, setEnableScrollToLoad] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
populateSearchInputs();
|
||||
@@ -42,6 +45,10 @@ export default function CourseCatalogMain({ support }: Props): JSX.Element | nul
|
||||
setRows(scrapedRows);
|
||||
}, [support]);
|
||||
|
||||
useEffect(() => {
|
||||
OptionsStore.get('enableScrollToLoad').then(setEnableScrollToLoad);
|
||||
}, []);
|
||||
|
||||
const addRows = (newRows: ScrapedRow[]) => {
|
||||
newRows.forEach(row => {
|
||||
document.querySelector('table tbody')!.appendChild(row.element);
|
||||
@@ -61,6 +68,7 @@ export default function CourseCatalogMain({ support }: Props): JSX.Element | nul
|
||||
|
||||
return (
|
||||
<ExtensionRoot>
|
||||
<NewSearchLink />
|
||||
<RecruitmentBanner />
|
||||
<TableHead>Plus</TableHead>
|
||||
{rows.map(
|
||||
@@ -81,7 +89,7 @@ export default function CourseCatalogMain({ support }: Props): JSX.Element | nul
|
||||
onClose={() => setShowPopup(false)}
|
||||
afterLeave={() => setSelectedCourse(null)}
|
||||
/>
|
||||
<AutoLoad addRows={addRows} />
|
||||
{enableScrollToLoad && <AutoLoad addRows={addRows} />}
|
||||
</ExtensionRoot>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,29 +1,23 @@
|
||||
import splashText from '@assets/insideJokes';
|
||||
import createSchedule from '@pages/background/lib/createSchedule';
|
||||
import { CalendarDots, GearSix, Plus } from '@phosphor-icons/react';
|
||||
import { background } from '@shared/messages';
|
||||
import { initSettings, OptionsStore } from '@shared/storage/OptionsStore';
|
||||
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
||||
import { openReportWindow } from '@shared/util/openReportWindow';
|
||||
import Divider from '@views/components/common/Divider';
|
||||
import List from '@views/components/common/List';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import { useEnforceScheduleLimit } from '@views/hooks/useEnforceScheduleLimit';
|
||||
import useSchedules, { getActiveSchedule, replaceSchedule, switchSchedule } from '@views/hooks/useSchedules';
|
||||
import { getUpdatedAtDateTimeString } from '@views/lib/getUpdatedAtDateTimeString';
|
||||
import useKC_DABR_WASM from 'kc-dabr-wasm';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import AddSchedule from '~icons/material-symbols/add';
|
||||
import CalendarIcon from '~icons/material-symbols/calendar-month';
|
||||
import Feedback from '~icons/material-symbols/flag';
|
||||
import SettingsIcon from '~icons/material-symbols/settings';
|
||||
|
||||
import { Button } from './common/Button';
|
||||
import CourseStatus from './common/CourseStatus';
|
||||
import { SmallLogo } from './common/LogoIcon';
|
||||
import PopupCourseBlock from './common/PopupCourseBlock';
|
||||
import ScheduleDropdown from './common/ScheduleDropdown';
|
||||
import ScheduleListItem from './common/ScheduleListItem';
|
||||
import { SortableList } from './common/SortableList';
|
||||
|
||||
/**
|
||||
* Renders the main popup component.
|
||||
@@ -31,14 +25,14 @@ import ScheduleListItem from './common/ScheduleListItem';
|
||||
*/
|
||||
export default function PopupMain(): JSX.Element {
|
||||
const [enableCourseStatusChips, setEnableCourseStatusChips] = useState<boolean>(false);
|
||||
const [enableDataRefreshing, setEnableDataRefreshing] = useState<boolean>(false);
|
||||
// const [enableDataRefreshing, setEnableDataRefreshing] = useState<boolean>(false);
|
||||
useKC_DABR_WASM();
|
||||
|
||||
useEffect(() => {
|
||||
const initAllSettings = async () => {
|
||||
const { enableCourseStatusChips, enableDataRefreshing } = await initSettings();
|
||||
const { enableCourseStatusChips } = await initSettings();
|
||||
setEnableCourseStatusChips(enableCourseStatusChips);
|
||||
setEnableDataRefreshing(enableDataRefreshing);
|
||||
// setEnableDataRefreshing(enableDataRefreshing);
|
||||
};
|
||||
|
||||
initAllSettings();
|
||||
@@ -48,18 +42,19 @@ export default function PopupMain(): JSX.Element {
|
||||
// console.log('enableCourseStatusChips', newValue);
|
||||
});
|
||||
|
||||
const l2 = OptionsStore.listen('enableDataRefreshing', async ({ newValue }) => {
|
||||
setEnableDataRefreshing(newValue);
|
||||
// console.log('enableDataRefreshing', newValue);
|
||||
});
|
||||
// const l2 = OptionsStore.listen('enableDataRefreshing', async ({ newValue }) => {
|
||||
// setEnableDataRefreshing(newValue);
|
||||
// // console.log('enableDataRefreshing', newValue);
|
||||
// });
|
||||
|
||||
return () => {
|
||||
OptionsStore.removeListener(l1);
|
||||
OptionsStore.removeListener(l2);
|
||||
// OptionsStore.removeListener(l2);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const [activeSchedule, schedules] = useSchedules();
|
||||
|
||||
// const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [funny, setFunny] = useState<string>('');
|
||||
|
||||
@@ -89,29 +84,29 @@ export default function PopupMain(): JSX.Element {
|
||||
|
||||
return (
|
||||
<div className='h-screen max-h-full flex flex-col bg-white'>
|
||||
<div className='p-5 py-3.5'>
|
||||
<div className='px-spacing-6 py-spacing-5'>
|
||||
<div className='flex items-center justify-between bg-white'>
|
||||
<SmallLogo />
|
||||
<div className='flex items-center gap-2.5'>
|
||||
<button className='bg-ut-burntorange px-2 py-1.25 btn' onClick={handleCalendarOpenOnClick}>
|
||||
<CalendarIcon className='size-6 text-white' />
|
||||
</button>
|
||||
<button className='bg-transparent px-2 py-1.25 btn' onClick={handleOpenOptions}>
|
||||
<SettingsIcon className='size-6 color-ut-black' />
|
||||
</button>
|
||||
<button className='bg-transparent px-2 py-1.25 btn' onClick={openReportWindow}>
|
||||
<Feedback className='size-6 color-ut-black' />
|
||||
</button>
|
||||
<Button
|
||||
variant='filled'
|
||||
color='ut-burntorange'
|
||||
onClick={handleCalendarOpenOnClick}
|
||||
icon={CalendarDots}
|
||||
iconProps={{ weight: 'fill' }}
|
||||
>
|
||||
Calendar
|
||||
</Button>
|
||||
<Button variant='minimal' color='ut-black' onClick={handleOpenOptions} icon={GearSix} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Divider orientation='horizontal' size='100%' />
|
||||
<Divider className='bg-ut-offwhite/50' orientation='horizontal' size='100%' />
|
||||
<div className='px-5 pb-2.5 pt-3.75'>
|
||||
<ScheduleDropdown>
|
||||
<List
|
||||
<SortableList
|
||||
draggables={schedules}
|
||||
itemKey={schedule => schedule.id}
|
||||
onReordered={reordered => {
|
||||
onChange={reordered => {
|
||||
const activeSchedule = getActiveSchedule();
|
||||
const activeIndex = reordered.findIndex(s => s.id === activeSchedule.id);
|
||||
|
||||
@@ -119,27 +114,18 @@ export default function PopupMain(): JSX.Element {
|
||||
UserScheduleStore.set('schedules', reordered);
|
||||
UserScheduleStore.set('activeIndex', activeIndex);
|
||||
}}
|
||||
gap={10}
|
||||
>
|
||||
{(schedule, handleProps) => (
|
||||
<ScheduleListItem
|
||||
schedule={schedule}
|
||||
onClick={() => {
|
||||
switchSchedule(schedule.id);
|
||||
}}
|
||||
dragHandleProps={handleProps}
|
||||
/>
|
||||
renderItem={schedule => (
|
||||
<ScheduleListItem schedule={schedule} onClick={() => switchSchedule(schedule.id)} />
|
||||
)}
|
||||
</List>
|
||||
/>
|
||||
<div className='bottom-0 right-0 mt-2.5 w-full flex justify-end'>
|
||||
<Button
|
||||
variant='filled'
|
||||
size='mini'
|
||||
color='ut-burntorange'
|
||||
className='h-fit p-0 btn'
|
||||
onClick={handleAddSchedule}
|
||||
>
|
||||
<AddSchedule className='h-6 w-6' />
|
||||
</Button>
|
||||
icon={Plus}
|
||||
/>
|
||||
</div>
|
||||
</ScheduleDropdown>
|
||||
</div>
|
||||
@@ -153,57 +139,56 @@ export default function PopupMain(): JSX.Element {
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
<div className='flex-1 self-stretch overflow-y-auto px-5'>
|
||||
<div
|
||||
style={{ scrollbarGutter: 'stable' }}
|
||||
className='flex-1 self-stretch overflow-y-scroll pl-spacing-6 pr-[6px]'
|
||||
>
|
||||
{activeSchedule?.courses?.length > 0 && (
|
||||
<List
|
||||
draggables={activeSchedule.courses}
|
||||
onReordered={reordered => {
|
||||
activeSchedule.courses = reordered;
|
||||
<SortableList
|
||||
draggables={activeSchedule.courses.map(course => ({
|
||||
id: course.uniqueId,
|
||||
...course,
|
||||
getConflicts: course.getConflicts,
|
||||
}))}
|
||||
onChange={reordered => {
|
||||
activeSchedule.courses = reordered.map(({ id: _id, ...course }) => course);
|
||||
replaceSchedule(getActiveSchedule(), activeSchedule);
|
||||
}}
|
||||
itemKey={e => e.uniqueId}
|
||||
gap={10}
|
||||
>
|
||||
{(course, handleProps) => (
|
||||
<PopupCourseBlock
|
||||
key={course.uniqueId}
|
||||
course={course}
|
||||
colors={course.colors}
|
||||
dragHandleProps={handleProps}
|
||||
/>
|
||||
renderItem={course => (
|
||||
<PopupCourseBlock key={course.id} course={course} colors={course.colors} />
|
||||
)}
|
||||
</List>
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className='w-full flex flex-col items-center gap-1.25 p-5 pt-3.75'>
|
||||
<div className='flex gap-2.5'>
|
||||
<div className='w-full flex flex-col items-center gap-1.25 px-spacing-6 py-spacing-5'>
|
||||
<div className='flex gap-spacing-6'>
|
||||
{enableCourseStatusChips && (
|
||||
<>
|
||||
<CourseStatus status='WAITLISTED' size='mini' />
|
||||
<CourseStatus status='CLOSED' size='mini' />
|
||||
<CourseStatus status='CANCELLED' size='mini' />
|
||||
<CourseStatus status='WAITLISTED' size='small' />
|
||||
<CourseStatus status='CLOSED' size='small' />
|
||||
<CourseStatus status='CANCELLED' size='small' />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{enableDataRefreshing && (
|
||||
<div className='inline-flex items-center self-center gap-1'>
|
||||
<Text variant='mini' className='text-ut-gray !font-normal'>
|
||||
{/* {enableDataRefreshing && (
|
||||
<div className='inline-flex items-center self-center gap-1'> */}
|
||||
{/* <Text variant='mini' className='text-ut-gray !font-normal'>
|
||||
LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)}
|
||||
</Text>
|
||||
{/* <button
|
||||
className='h-4 w-4 bg-transparent p-0 btn'
|
||||
onClick={() => {
|
||||
setIsRefreshing(true);
|
||||
}}
|
||||
>
|
||||
<RefreshIcon
|
||||
className={clsx('h-4 w-4 text-ut-black animate-duration-800', {
|
||||
'animate-spin': isRefreshing,
|
||||
})}
|
||||
/>
|
||||
</button> */}
|
||||
</div>
|
||||
)}
|
||||
</Text> */}
|
||||
{/* <button
|
||||
className='h-4 w-4 bg-transparent p-0 btn'
|
||||
onClick={() => {
|
||||
setIsRefreshing(true);
|
||||
}}
|
||||
>
|
||||
<RefreshIcon
|
||||
className={clsx('h-4 w-4 text-ut-black animate-duration-800', {
|
||||
'animate-spin': isRefreshing,
|
||||
})}
|
||||
/>
|
||||
</button> */}
|
||||
{/* </div>
|
||||
)} */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
import { Sidebar } from '@phosphor-icons/react';
|
||||
import type { CalendarTabMessages } from '@shared/messages/CalendarMessages';
|
||||
import type { Course } from '@shared/types/Course';
|
||||
import { CRX_PAGES } from '@shared/types/CRXPages';
|
||||
import { openReportWindow } from '@shared/util/openReportWindow';
|
||||
import CalendarBottomBar from '@views/components/calendar/CalendarBottomBar';
|
||||
import CalendarGrid from '@views/components/calendar/CalendarGrid';
|
||||
import CalendarHeader from '@views/components/calendar/CalendarHeader/CalendarHeader';
|
||||
import { CalendarSchedules } from '@views/components/calendar/CalendarSchedules';
|
||||
import CalendarHeader from '@views/components/calendar/CalenderHeader';
|
||||
import ImportantLinks from '@views/components/calendar/ImportantLinks';
|
||||
import ResourceLinks from '@views/components/calendar/ResourceLinks';
|
||||
import Divider from '@views/components/common/Divider';
|
||||
import CourseCatalogInjectedPopup from '@views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup';
|
||||
import { CalendarContext } from '@views/contexts/CalendarContext';
|
||||
import useCourseFromUrl from '@views/hooks/useCourseFromUrl';
|
||||
import { useFlattenedCourseSchedule } from '@views/hooks/useFlattenedCourseSchedule';
|
||||
import { MessageListener } from 'chrome-extension-toolkit';
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import OutwardArrowIcon from '~icons/material-symbols/arrow-outward';
|
||||
|
||||
import { Button } from '../common/Button';
|
||||
import { LargeLogo } from '../common/LogoIcon';
|
||||
import Text from '../common/Text/Text';
|
||||
import CalendarFooter from './CalendarFooter';
|
||||
import TeamLinks from './TeamLinks';
|
||||
|
||||
/**
|
||||
* Calendar page component
|
||||
@@ -54,26 +62,76 @@ export default function Calendar(): JSX.Element {
|
||||
return (
|
||||
<CalendarContext.Provider value>
|
||||
<div className='h-full w-full flex flex-col'>
|
||||
<CalendarHeader
|
||||
onSidebarToggle={() => {
|
||||
setShowSidebar(!showSidebar);
|
||||
}}
|
||||
/>
|
||||
<div className='h-full flex overflow-auto pl-3'>
|
||||
{showSidebar && (
|
||||
<div className='h-full flex flex-none flex-col justify-between pb-5 screenshot:hidden'>
|
||||
<div className='mb-3 h-full w-fit flex flex-col overflow-auto pb-2 pl-4.5 pr-4 pt-5'>
|
||||
<CalendarSchedules />
|
||||
<Divider orientation='horizontal' size='100%' className='my-5' />
|
||||
<ImportantLinks />
|
||||
<Divider orientation='horizontal' size='100%' className='my-5' />
|
||||
<TeamLinks />
|
||||
</div>
|
||||
<CalendarFooter />
|
||||
<div className='h-screen flex overflow-auto'>
|
||||
<div
|
||||
className={clsx(
|
||||
'py-spacing-6 relative h-full min-h-screen w-full flex flex-none flex-col justify-between overflow-clip whitespace-nowrap border-r border-ut-offwhite/50 shadow-[2px_0_10px,rgba(214_210_196_/_.1)] motion-safe:duration-300 motion-safe:ease-out-expo motion-safe:transition-[max-width] screenshot:hidden',
|
||||
{
|
||||
'max-w-[20.3125rem] ': showSidebar,
|
||||
'max-w-0 pointer-events-none': !showSidebar,
|
||||
}
|
||||
)}
|
||||
tabIndex={showSidebar ? 0 : -1}
|
||||
aria-hidden={!showSidebar}
|
||||
{...{ inert: !showSidebar }}
|
||||
>
|
||||
<div className='sticky top-0 z-50 w-full flex items-center justify-between gap-x-3xl bg-white px-spacing-8 pb-spacing-6'>
|
||||
<LargeLogo />
|
||||
<Button
|
||||
variant='minimal'
|
||||
color='theme-black'
|
||||
onClick={() => {
|
||||
setShowSidebar(!showSidebar);
|
||||
}}
|
||||
className='h-fit screenshot:hidden !p-0'
|
||||
icon={Sidebar}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className='h-full min-w-5xl flex flex-grow flex-col overflow-y-auto'>
|
||||
<div className='min-h-2xl flex-grow overflow-auto pl-2 pr-4 pt-6 screenshot:min-h-xl'>
|
||||
|
||||
<div
|
||||
style={{
|
||||
scrollbarGutter: 'stable',
|
||||
}}
|
||||
className='relative h-full w-full flex grow flex-col gap-y-spacing-6 overflow-x-clip overflow-y-auto pb-spacing-6 pl-spacing-8 pr-4.5'
|
||||
>
|
||||
<CalendarSchedules />
|
||||
<Divider orientation='horizontal' size='100%' />
|
||||
<ResourceLinks />
|
||||
<Divider orientation='horizontal' size='100%' />
|
||||
{/* <TeamLinks /> */}
|
||||
<a
|
||||
href={CRX_PAGES.REPORT}
|
||||
className='flex items-center gap-spacing-2 text-ut-burntorange underline-offset-2 hover:underline'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
onClick={event => {
|
||||
event.preventDefault();
|
||||
openReportWindow();
|
||||
}}
|
||||
>
|
||||
<Text variant='p'>Send us Feedback!</Text>
|
||||
<OutwardArrowIcon className='h-4 w-4' />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<CalendarFooter />
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={
|
||||
{
|
||||
// scrollbarGutter: 'stable',
|
||||
}
|
||||
}
|
||||
className='h-full flex flex-grow flex-col overflow-x-scroll px-spacing-5'
|
||||
>
|
||||
<CalendarHeader
|
||||
sidebarOpen={showSidebar}
|
||||
onSidebarToggle={() => {
|
||||
setShowSidebar(!showSidebar);
|
||||
}}
|
||||
/>
|
||||
<div className='min-h-2xl min-w-5xl flex-grow overflow-auto pl-spacing-3 pt-spacing-3 screenshot:min-h-xl'>
|
||||
<CalendarGrid courseCells={courseCells} setCourse={setCourse} />
|
||||
</div>
|
||||
<CalendarBottomBar courseCells={courseCells} setCourse={setCourse} />
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import type { Course } from '@shared/types/Course';
|
||||
import { saveAsCal, saveCalAsPng } from '@views/components/calendar/utils';
|
||||
import { Button } from '@views/components/common/Button';
|
||||
import Divider from '@views/components/common/Divider';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import { ColorPickerProvider } from '@views/contexts/ColorPickerContext';
|
||||
import type { CalendarGridCourse } from '@views/hooks/useFlattenedCourseSchedule';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import CalendarMonthIcon from '~icons/material-symbols/calendar-month';
|
||||
import ImageIcon from '~icons/material-symbols/image';
|
||||
|
||||
import CalendarCourseBlock from './CalendarCourseCell';
|
||||
|
||||
type CalendarBottomBarProps = {
|
||||
@@ -20,61 +15,48 @@ type CalendarBottomBarProps = {
|
||||
/**
|
||||
* Renders the bottom bar of the calendar component.
|
||||
*
|
||||
* @param {Object[]} courses - The list of courses to display in the calendar.
|
||||
* @returns {JSX.Element} The rendered bottom bar component.
|
||||
* @param courses - The list of courses to display in the calendar.
|
||||
* @returns The rendered bottom bar component.
|
||||
*/
|
||||
export default function CalendarBottomBar({ courseCells, setCourse }: CalendarBottomBarProps): JSX.Element {
|
||||
const asyncCourseCells = courseCells?.filter(block => block.async);
|
||||
const displayCourses = asyncCourseCells && asyncCourseCells.length > 0;
|
||||
|
||||
return (
|
||||
<div className='w-full flex py-1.25 pl-7.5 pr-6.25'>
|
||||
<div className='w-full flex pl-spacing-7 pr-spacing-3 pt-spacing-4'>
|
||||
<div
|
||||
className={clsx('flex flex-grow items-center gap-3.75 text-nowrap', {
|
||||
className={clsx('flex flex-grow items-center gap-1 text-nowrap', {
|
||||
'py-7.5': !displayCourses,
|
||||
})}
|
||||
>
|
||||
{displayCourses && (
|
||||
<>
|
||||
<Text variant='p' className='text-ut-black'>
|
||||
ASYNC / OTHER
|
||||
<Text variant='p' className='text-ut-black uppercase'>
|
||||
Unscheduled
|
||||
</Text>
|
||||
<Text variant='h4' className='text-gray-300'>
|
||||
<Text variant='h4' className='text-theme-offwhite/50'>
|
||||
—
|
||||
</Text>
|
||||
<div className='inline-flex gap-2.5'>
|
||||
{asyncCourseCells.map(block => {
|
||||
const { courseDeptAndInstr, status, colors, className } = block.componentProps;
|
||||
return (
|
||||
<CalendarCourseBlock
|
||||
courseDeptAndInstr={courseDeptAndInstr}
|
||||
status={status}
|
||||
colors={colors}
|
||||
key={courseDeptAndInstr}
|
||||
className={clsx(className, 'w-35! h-15!')}
|
||||
onClick={() => setCourse(block.course)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<ColorPickerProvider>
|
||||
{asyncCourseCells.map(block => {
|
||||
const { courseDeptAndInstr, status, className } = block.componentProps;
|
||||
return (
|
||||
<CalendarCourseBlock
|
||||
key={block.course.uniqueId}
|
||||
courseDeptAndInstr={courseDeptAndInstr}
|
||||
status={status}
|
||||
className={clsx(className, 'w-35! h-12.5! items-center')}
|
||||
onClick={() => setCourse(block.course)}
|
||||
blockData={block}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ColorPickerProvider>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex items-center screenshot:hidden'>
|
||||
{displayCourses && <Divider orientation='vertical' size='1rem' className='mx-1.25' />}
|
||||
<Button variant='single' color='ut-black' icon={CalendarMonthIcon} onClick={saveAsCal}>
|
||||
Save as .CAL
|
||||
</Button>
|
||||
<Divider orientation='vertical' size='1rem' className='mx-1.25' />
|
||||
<Button
|
||||
variant='single'
|
||||
color='ut-black'
|
||||
icon={ImageIcon}
|
||||
onClick={() => requestAnimationFrame(() => saveCalAsPng())}
|
||||
>
|
||||
Save as .PNG
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { ClockUser, LockKey, Palette, Prohibit } from '@phosphor-icons/react';
|
||||
import { initSettings, OptionsStore } from '@shared/storage/OptionsStore';
|
||||
import type { StatusType } from '@shared/types/Course';
|
||||
import { Status } from '@shared/types/Course';
|
||||
import type { CourseColors } from '@shared/types/ThemeColors';
|
||||
import { pickFontColor } from '@shared/util/colors';
|
||||
import { hexToRGB, pickFontColor } from '@shared/util/colors';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import { useColorPickerContext } from '@views/contexts/ColorPickerContext';
|
||||
import type { CalendarGridCourse } from '@views/hooks/useFlattenedCourseSchedule';
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import ClosedIcon from '~icons/material-symbols/lock';
|
||||
import WaitlistIcon from '~icons/material-symbols/timelapse';
|
||||
import CancelledIcon from '~icons/material-symbols/warning';
|
||||
import { Button } from '../common/Button';
|
||||
import CourseCellColorPicker from './CalendarCourseCellColorPicker/CourseCellColorPicker';
|
||||
|
||||
/**
|
||||
* Props for the CalendarCourseCell component.
|
||||
@@ -18,32 +19,44 @@ export interface CalendarCourseCellProps {
|
||||
courseDeptAndInstr: string;
|
||||
timeAndLocation?: string;
|
||||
status: StatusType;
|
||||
colors: CourseColors;
|
||||
className?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
blockData: CalendarGridCourse;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a cell for a calendar course.
|
||||
*
|
||||
* @component
|
||||
* @param {CalendarCourseCellProps} props - The component props.
|
||||
* @param {string} props.courseDeptAndInstr - The course department and instructor.
|
||||
* @param {string} props.timeAndLocation - The time and location of the course.
|
||||
* @param {StatusType} props.status - The status of the course.
|
||||
* @param {Colors} props.colors - The colors for styling the cell.
|
||||
* @param {string} props.className - Additional CSS class name for the cell.
|
||||
* @returns {JSX.Element} The rendered component.
|
||||
* @param courseDeptAndInstr - The course department and instructor.
|
||||
* @param timeAndLocation - The time and location of the course.
|
||||
* @param status - The status of the course.
|
||||
* @param colors - The colors for styling the cell.
|
||||
* @param className - Additional CSS class name for the cell.
|
||||
* @returns The rendered component.
|
||||
*/
|
||||
export default function CalendarCourseCell({
|
||||
courseDeptAndInstr,
|
||||
timeAndLocation,
|
||||
status,
|
||||
colors,
|
||||
className,
|
||||
onClick,
|
||||
blockData,
|
||||
className,
|
||||
}: CalendarCourseCellProps): JSX.Element {
|
||||
const [enableCourseStatusChips, setEnableCourseStatusChips] = useState<boolean>(false);
|
||||
const colorPickerRef = useRef<HTMLDivElement>(null);
|
||||
const { selectedColor, setSelectedCourse, handleCloseColorPicker, isSelectedBlock, isSelectedCourse } =
|
||||
useColorPickerContext();
|
||||
|
||||
const { colors, uniqueId: courseID } = blockData.course;
|
||||
const { dayIndex, startIndex } = blockData.calendarGridPoint;
|
||||
|
||||
let selectedCourse = false;
|
||||
let selectedBlock = false;
|
||||
|
||||
if (isSelectedCourse && isSelectedBlock) {
|
||||
selectedCourse = isSelectedCourse(courseID);
|
||||
selectedBlock = isSelectedBlock(courseID, dayIndex, startIndex);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
initSettings().then(({ enableCourseStatusChips }) => setEnableCourseStatusChips(enableCourseStatusChips));
|
||||
@@ -58,14 +71,34 @@ export default function CalendarCourseCell({
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (selectedBlock && colorPickerRef.current) {
|
||||
const path = event.composedPath();
|
||||
const isClickOutside = !path.some(
|
||||
element => (element as HTMLElement).classList === colorPickerRef.current?.classList
|
||||
);
|
||||
|
||||
if (isClickOutside) {
|
||||
handleCloseColorPicker();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [handleCloseColorPicker, selectedBlock]);
|
||||
|
||||
let rightIcon: React.ReactNode | null = null;
|
||||
if (enableCourseStatusChips) {
|
||||
if (status === Status.WAITLISTED) {
|
||||
rightIcon = <WaitlistIcon className='h-5 w-5' />;
|
||||
rightIcon = <ClockUser weight='fill' className='h-5 w-5' />;
|
||||
} else if (status === Status.CLOSED) {
|
||||
rightIcon = <ClosedIcon className='h-5 w-5' />;
|
||||
rightIcon = <LockKey weight='fill' className='h-5 w-5' />;
|
||||
} else if (status === Status.CANCELLED) {
|
||||
rightIcon = <CancelledIcon className='h-5 w-5' />;
|
||||
rightIcon = <Prohibit weight='fill' className='h-5 w-5' />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +109,7 @@ export default function CalendarCourseCell({
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'h-full w-0 flex justify-center rounded p-x-2 p-y-1.2 cursor-pointer hover:shadow-md transition-shadow-100 ease-out',
|
||||
'h-full w-0 flex group relative justify-center rounded p-x-2 p-y-1.2 cursor-pointer screenshot:p-1.5 hover:shadow-md transition-shadow-100 ease-out',
|
||||
{
|
||||
'min-w-full': timeAndLocation,
|
||||
'w-full': !timeAndLocation,
|
||||
@@ -101,7 +134,7 @@ export default function CalendarCourseCell({
|
||||
{courseDeptAndInstr}
|
||||
</Text>
|
||||
{timeAndLocation && (
|
||||
<Text variant='h3-course' as='p'>
|
||||
<Text variant='h3-course' as='p' className='whitespace-pre-line'>
|
||||
{timeAndLocation}
|
||||
</Text>
|
||||
)}
|
||||
@@ -116,6 +149,62 @@ export default function CalendarCourseCell({
|
||||
{rightIcon}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className={clsx(
|
||||
'absolute screenshot:opacity-0! text-black transition-all ease-in-out group-focus-within:pointer-events-auto group-hover:pointer-events-auto group-focus-within:opacity-100 group-hover:opacity-100 gap-y-0.75',
|
||||
dayIndex === 4 ? 'left-0 -translate-x-full pr-0.75 items-end' : 'right-0 translate-x-full pl-0.75', // If the cell is on the right side of the screen
|
||||
selectedBlock ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'
|
||||
)}
|
||||
style={{
|
||||
// Prevents from button from appear on top of color picker
|
||||
zIndex: selectedBlock ? 30 : 29,
|
||||
}}
|
||||
>
|
||||
<div className={clsx('relative', dayIndex === 4 && 'flex flex-col items-end')}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (selectedBlock) {
|
||||
handleCloseColorPicker();
|
||||
} else {
|
||||
setSelectedCourse(courseID, dayIndex, startIndex);
|
||||
}
|
||||
}}
|
||||
icon={Palette}
|
||||
iconProps={{
|
||||
fill: colors.secondaryColor,
|
||||
weight: 'fill',
|
||||
}}
|
||||
variant='outline'
|
||||
className={clsx(
|
||||
'size-8.5! border border-white rounded-full !p-1 bg-opacity-100 !hover:enabled:bg-opacity-100 rounded-full shadow-lg shadow-black/20'
|
||||
)}
|
||||
color='ut-gray'
|
||||
style={{
|
||||
color: colors.secondaryColor,
|
||||
backgroundColor: selectedCourse
|
||||
? (selectedColor ?? colors.primaryColor)
|
||||
: `rgba(${hexToRGB(`${colors.primaryColor}`)}, var(--un-bg-opacity))`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{selectedBlock && (
|
||||
<div
|
||||
ref={colorPickerRef}
|
||||
className={
|
||||
startIndex < 21 && !blockData.async
|
||||
? 'relative top-0.75 w-max'
|
||||
: 'absolute bottom-full mb-0.75 w-max'
|
||||
}
|
||||
>
|
||||
<CourseCellColorPicker defaultColor={colors.primaryColor} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,38 +1,48 @@
|
||||
import { getThemeColorHexByName } from '@shared/util/themeColors';
|
||||
import { Check } from '@phosphor-icons/react';
|
||||
import { useColorPickerContext } from '@views/contexts/ColorPickerContext';
|
||||
import React from 'react';
|
||||
|
||||
import CheckIcon from '~icons/material-symbols/check';
|
||||
|
||||
/**
|
||||
* Props for the ColorPatch component
|
||||
*/
|
||||
interface ColorPatchProps {
|
||||
color: string;
|
||||
isSelected: boolean;
|
||||
handleSetSelectedColor: (color: string) => void;
|
||||
handleSelectColorPatch: (color: string) => void;
|
||||
defaultColor: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a color patch square used in the CalendarCourseCellColorPicker component.
|
||||
*
|
||||
* @param {Object} props - The component props.
|
||||
* @param {string} props.color - The color value (as a hex string with a hash prefix) to display in the patch.
|
||||
* @param {boolean} props.isSelected - Indicates whether the patch is selected.
|
||||
* @param {Function} props.handleSetSelectedColor - Function from parent component to control selection state of a patch.
|
||||
* color is a hex string with a hash prefix.
|
||||
* @returns {JSX.Element} The rendered color patch button.
|
||||
* @param color - The color value (as a hex string with a hash prefix) to display in the patch.
|
||||
* @param isSelected - Indicates whether the patch is selected.
|
||||
* @param handleSetSelectedColor - Function from parent component to control selection state of a patch.
|
||||
* @returns The rendered color patch button.
|
||||
*/
|
||||
export default function ColorPatch({ color, isSelected, handleSetSelectedColor }: ColorPatchProps): JSX.Element {
|
||||
const handleClick = () => {
|
||||
handleSetSelectedColor(isSelected ? getThemeColorHexByName('ut-gray') : color);
|
||||
export default function ColorPatch({
|
||||
color,
|
||||
isSelected,
|
||||
handleSelectColorPatch,
|
||||
defaultColor,
|
||||
}: ColorPatchProps): JSX.Element {
|
||||
const { handleCloseColorPicker } = useColorPickerContext();
|
||||
|
||||
const handleClick = async () => {
|
||||
// If the color patch is already selected, close the color picker
|
||||
if (isSelected) {
|
||||
handleCloseColorPicker();
|
||||
} else {
|
||||
handleSelectColorPatch(isSelected ? defaultColor : color);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<button
|
||||
className='h-5.5 w-5.5 p-0 transition-all duration-200 hover:scale-110 btn'
|
||||
className='size-6.5 p-0 transition-all duration-200 hover:scale-110 btn'
|
||||
style={{ backgroundColor: color }}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{isSelected && <CheckIcon className='h-5 w-5 color-white' />}
|
||||
{isSelected && <Check className='h-5 w-5 color-white' />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import type { ThemeColor, TWIndex } from '@shared/types/ThemeColors';
|
||||
import { getThemeColorHexByName } from '@shared/util/themeColors';
|
||||
import Divider from '@views/components/common/Divider';
|
||||
import { useColorPickerContext } from '@views/contexts/ColorPickerContext';
|
||||
import React from 'react';
|
||||
import { theme } from 'unocss/preset-mini';
|
||||
|
||||
import InvertColorsIcon from '~icons/material-symbols/invert-colors';
|
||||
import InvertColorsOffIcon from '~icons/material-symbols/invert-colors-off';
|
||||
|
||||
import ColorPatch from './ColorPatch';
|
||||
import HexColorEditor from './HexColorEditor';
|
||||
|
||||
@@ -55,16 +53,18 @@ const hexCodeToBaseColor = new Map<string, string>(
|
||||
* Props for the CourseCellColorPicker component.
|
||||
*/
|
||||
export interface CourseCellColorPickerProps {
|
||||
setSelectedColor: React.Dispatch<React.SetStateAction<ThemeColor | null>>;
|
||||
isInvertColorsToggled: boolean;
|
||||
setIsInvertColorsToggled: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
defaultColor: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CourseCellColorPickerProps} props - the props for the component
|
||||
* @param {React.Dispatch<React.SetStateAction<string | null>>} props.setSelectedColor - set state function passed down from the parent component
|
||||
* @param {boolean} props.isInvertColorsToggled - boolean state passed down from the parent component that indicates whether the color picker is in invert colors mode
|
||||
* @param {React.Dispatch<React.SetStateAction<boolean>>} props.setIsInvertColorsToggled - set state function passed down from the parent component to set invert colors mode
|
||||
* The CourseCellColorPicker component that displays a color palette with a list of color patches.
|
||||
*
|
||||
* @remarks This component is available when a user hovers over a course cell in their calendar to
|
||||
* color for the course cell. The user can set any valid hex color they want.
|
||||
*
|
||||
* @param setSelectedColor - Set state function passed down from the parent component
|
||||
* @param isInvertColorsToggled - Boolean state passed down from the parent component that indicates whether the color picker is in invert colors mode
|
||||
* @param setIsInvertColorsToggled - Set state function passed down from the parent component to set invert colors mode
|
||||
* that will be called when a color is selected. The user can set any valid hex color they want.
|
||||
*
|
||||
* @example
|
||||
@@ -80,53 +80,45 @@ export interface CourseCellColorPickerProps {
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @returns {JSX.Element} - the color picker component that displays a color palette with a list of color patches.
|
||||
* This component is available when a user hovers over a course cell in their calendar to
|
||||
* color for the course cell. The user can set any valid hex color they want.
|
||||
* @returns The color picker component that displays a color palette with a list of color patches.
|
||||
*/
|
||||
export default function CourseCellColorPicker({
|
||||
setSelectedColor: setFinalColor,
|
||||
isInvertColorsToggled,
|
||||
setIsInvertColorsToggled,
|
||||
}: CourseCellColorPickerProps): JSX.Element {
|
||||
export default function CourseCellColorPicker({ defaultColor }: CourseCellColorPickerProps): JSX.Element {
|
||||
// hexCode mirrors contents of HexColorEditor which has no hash prefix
|
||||
const [hexCode, setHexCode] = React.useState<string>(
|
||||
getThemeColorHexByName('ut-gray').slice(1).toLocaleLowerCase()
|
||||
defaultColor.slice(1).toLocaleLowerCase() || getThemeColorHexByName('ut-gray')
|
||||
);
|
||||
|
||||
const { setSelectedColor } = useColorPickerContext();
|
||||
|
||||
const hexCodeWithHash = `#${hexCode}` as ThemeColor;
|
||||
const selectedBaseColor = hexCodeToBaseColor.get(hexCodeWithHash);
|
||||
|
||||
const handleSelectColorPatch = (baseColor: string) => {
|
||||
setHexCode(baseColor.slice(1).toLocaleLowerCase());
|
||||
let hexCode = baseColor.toLocaleLowerCase();
|
||||
|
||||
if (hexCode.startsWith('#')) {
|
||||
hexCode = baseColor.slice(1);
|
||||
}
|
||||
|
||||
setHexCode(hexCode);
|
||||
setSelectedColor(`#${hexCode}` as ThemeColor);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
setFinalColor(hexCodeWithHash);
|
||||
}, [hexCodeWithHash, setFinalColor]);
|
||||
|
||||
return (
|
||||
<div className='inline-flex flex-col border border-1 border-ut-offwhite rounded-1 p-1.25'>
|
||||
<div className='inline-flex flex-col border border-ut-offwhite rounded-1 bg-white p-1.25'>
|
||||
<div className='grid grid-cols-6 gap-1'>
|
||||
{Array.from(colorPatchColors.keys()).map(baseColor => (
|
||||
<ColorPatch
|
||||
key={baseColor}
|
||||
color={baseColor}
|
||||
isSelected={baseColor === selectedBaseColor}
|
||||
handleSetSelectedColor={handleSelectColorPatch}
|
||||
handleSelectColorPatch={handleSelectColorPatch}
|
||||
defaultColor={defaultColor}
|
||||
/>
|
||||
))}
|
||||
<div className='col-span-3 flex items-center justify-center overflow-hidden'>
|
||||
<HexColorEditor hexCode={hexCode} setHexCode={setHexCode} />
|
||||
<HexColorEditor hexCode={hexCode} setHexCode={handleSelectColorPatch} />
|
||||
</div>
|
||||
<button
|
||||
className='h-5.5 w-5.5 bg-ut-black p-0 transition-all duration-200 hover:scale-110 btn'
|
||||
onClick={() => setIsInvertColorsToggled(prev => !prev)}
|
||||
>
|
||||
{isInvertColorsToggled ? (
|
||||
<InvertColorsIcon className='h-3.5 w-3.5 color-white' />
|
||||
) : (
|
||||
<InvertColorsOffIcon className='h-3.5 w-3.5 color-white' />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{selectedBaseColor && (
|
||||
<>
|
||||
@@ -136,9 +128,11 @@ export default function CourseCellColorPicker({
|
||||
.get(selectedBaseColor)
|
||||
?.map(shadeColor => (
|
||||
<ColorPatch
|
||||
key={shadeColor}
|
||||
color={shadeColor}
|
||||
isSelected={shadeColor === hexCodeWithHash}
|
||||
handleSetSelectedColor={handleSelectColorPatch}
|
||||
handleSelectColorPatch={handleSelectColorPatch}
|
||||
defaultColor={defaultColor}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,44 +1,62 @@
|
||||
import { Hash } from '@phosphor-icons/react';
|
||||
import { isValidHexColor, pickFontColor } from '@shared/util/colors';
|
||||
import { getThemeColorHexByName } from '@shared/util/themeColors';
|
||||
import { useDebounce } from '@views/hooks/useDebounce';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import TagIcon from '~icons/material-symbols/tag';
|
||||
|
||||
/**
|
||||
* Props for the HexColorEditor component
|
||||
*/
|
||||
export interface HexColorEditorProps {
|
||||
hexCode: string;
|
||||
setHexCode: React.Dispatch<React.SetStateAction<string>>;
|
||||
setHexCode: (hexCode: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility component to allow the user to enter a valid hex color code
|
||||
*
|
||||
* @param {HexColorEditorProps} props - the props for the component
|
||||
* @param {string} props.hexCode - the current hex color code displayed in this component. Note that this code does not
|
||||
* @param hexCode - The current hex color code displayed in this component. Note that this code does not
|
||||
* include the leading '#' character since it is already included in the component. Passed down from the parent component.
|
||||
* @param {React.Dispatch<React.SetStateAction<string>>} props.setHexCode - set state fn to control the hex color code from parent
|
||||
* @returns {JSX.Element} - the hex color editor component
|
||||
* @param setHexCode - Set state fn to control the hex color code from parent
|
||||
* @returns The HexColorEditor component
|
||||
*/
|
||||
export default function HexColorEditor({ hexCode, setHexCode }: HexColorEditorProps): JSX.Element {
|
||||
const baseColor = React.useMemo(() => getThemeColorHexByName('ut-gray'), []);
|
||||
const previewColor = hexCode.length === 6 ? `#${hexCode}` : baseColor;
|
||||
const previewColor = isValidHexColor(`#${hexCode}`) ? `#${hexCode}` : baseColor;
|
||||
const tagColor = pickFontColor(previewColor.slice(1) as `#${string}`);
|
||||
|
||||
const [localHexCode, setLocalHexCode] = React.useState(hexCode);
|
||||
const debouncedSetHexCode = useDebounce((value: string) => setHexCode(value), 500);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (hexCode !== localHexCode) {
|
||||
setLocalHexCode(hexCode);
|
||||
}
|
||||
}, [hexCode]);
|
||||
|
||||
React.useEffect(() => {
|
||||
debouncedSetHexCode(localHexCode);
|
||||
|
||||
// This is on purpose
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [localHexCode]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{ backgroundColor: previewColor }}
|
||||
className='h-5.5 w-5.25 flex items-center justify-center rounded-l-1'
|
||||
className='h-6.5 w-6.5 flex items-center justify-center rounded-l-1'
|
||||
>
|
||||
<TagIcon className='h-4 w-4 text-color-white' />
|
||||
<Hash className={clsx('h-5 w-5 text-color-white', tagColor)} />
|
||||
</div>
|
||||
<div className='h-5.5 w-[53px] flex flex-1 items-center justify-center border-b border-r border-t rounded-br rounded-tr p-1.25'>
|
||||
<div className='h-6.5 w-[53px] flex flex-1 items-center justify-center border-b border-r border-t rounded-br rounded-tr p-1.25'>
|
||||
<input
|
||||
type='text'
|
||||
maxLength={6}
|
||||
className='w-full border-none bg-transparent font-size-2.75 font-normal outline-none focus:outline-none'
|
||||
value={hexCode}
|
||||
onChange={e => setHexCode(e.target.value)}
|
||||
className='w-full border-none bg-transparent font-size-2.75 font-normal font-mono outline-none focus:outline-none'
|
||||
value={localHexCode}
|
||||
onChange={e => setLocalHexCode(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { GearSix } from '@phosphor-icons/react';
|
||||
import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript';
|
||||
import React from 'react';
|
||||
|
||||
import DiscordIcon from '~icons/bi/discord';
|
||||
@@ -5,36 +7,65 @@ import GithubIcon from '~icons/ri/github-fill';
|
||||
import InstagramIcon from '~icons/ri/instagram-line';
|
||||
import LinkedinIcon from '~icons/ri/linkedin-box-fill';
|
||||
|
||||
import { Button } from '../common/Button';
|
||||
import Link from '../common/Link';
|
||||
|
||||
interface SocialLink {
|
||||
icon: React.FC<React.SVGProps<SVGSVGElement>>;
|
||||
url: string;
|
||||
}
|
||||
|
||||
const socialLinks: SocialLink[] = [
|
||||
{
|
||||
icon: InstagramIcon,
|
||||
url: 'https://www.instagram.com/longhorndevelopers',
|
||||
},
|
||||
{
|
||||
icon: DiscordIcon,
|
||||
url: 'https://discord.gg/7pQDBGdmb7',
|
||||
},
|
||||
{
|
||||
icon: GithubIcon,
|
||||
url: 'https://github.com/Longhorn-Developers',
|
||||
},
|
||||
{
|
||||
icon: LinkedinIcon,
|
||||
url: 'https://www.linkedin.com/company/longhorn-developers/posts/?feedView=all',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Opens the options page in a new tab.
|
||||
* @returns A promise that resolves when the options page is opened.
|
||||
*/
|
||||
const handleOpenOptions = async (): Promise<void> => {
|
||||
const url = chrome.runtime.getURL('/options.html');
|
||||
await openTabFromContentScript(url);
|
||||
};
|
||||
|
||||
/**
|
||||
* The footer section of the calendar's sidebar
|
||||
* @returns
|
||||
*/
|
||||
export default function CalendarFooter(): JSX.Element {
|
||||
return (
|
||||
<footer className='min-w-full w-0 pl-4.5 space-y-2'>
|
||||
<div className='flex gap-2'>
|
||||
<Link className='linkanimate' href='https://www.instagram.com/longhorndevelopers'>
|
||||
<InstagramIcon className='h-6 w-6' />
|
||||
</Link>
|
||||
<Link className='linkanimate' href='https://discord.gg/7pQDBGdmb7'>
|
||||
<DiscordIcon className='h-6 w-6' />
|
||||
</Link>
|
||||
<Link className='linkanimate' href='https://github.com/Longhorn-Developers'>
|
||||
<GithubIcon className='h-6 w-6' />
|
||||
</Link>
|
||||
<Link
|
||||
className='linkanimate'
|
||||
href='https://www.linkedin.com/company/longhorn-developers/posts/?feedView=all'
|
||||
>
|
||||
<LinkedinIcon className='h-6 w-6 -mx-0.75' />
|
||||
</Link>
|
||||
<footer className='min-w-full w-0 flex items-center justify-between bg-white px-spacing-8 pt-spacing-4'>
|
||||
<div className='flex gap-spacing-5'>
|
||||
{socialLinks.map(({ icon: Icon, url }) => (
|
||||
<Link className='linkanimate' href={url}>
|
||||
<Icon className='h-6 w-6' />
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
className='h-fit w-fit !p-0'
|
||||
variant='minimal'
|
||||
icon={GearSix}
|
||||
color='ut-black'
|
||||
onClick={handleOpenOptions}
|
||||
/>
|
||||
</div>
|
||||
<p className='text-2.5 text-ut-concrete font-light tracking-wide'>
|
||||
UT Registration Plus is a project under Longhorn Developers, a student-led organization aimed at
|
||||
addressing issues at UT Austin.
|
||||
</p>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Course } from '@shared/types/Course';
|
||||
import CalendarCourseCell from '@views/components/calendar/CalendarCourseCell';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import { ColorPickerProvider } from '@views/contexts/ColorPickerContext';
|
||||
import type { CalendarGridCourse } from '@views/hooks/useFlattenedCourseSchedule';
|
||||
import React from 'react';
|
||||
|
||||
@@ -43,7 +44,11 @@ function makeGridRow(row: number, cols: number): JSX.Element {
|
||||
|
||||
/**
|
||||
* Grid of CalendarGridCell components forming the user's course schedule calendar view
|
||||
* @param props
|
||||
*
|
||||
* @param courseCells - The courses to display on the calendar
|
||||
* @param saturdayClass - Whether the user has a Saturday class
|
||||
* @param setCourse - Function to set the course to display in the course details panel
|
||||
* @returns The CalendarGrid component
|
||||
*/
|
||||
export default function CalendarGrid({
|
||||
courseCells,
|
||||
@@ -69,7 +74,9 @@ export default function CalendarGrid({
|
||||
.map(() => (
|
||||
<div className='h-4 flex items-end justify-center border-r border-gray-300' />
|
||||
))}
|
||||
{courseCells ? <AccountForCourseConflicts courseCells={courseCells} setCourse={setCourse} /> : null}
|
||||
<ColorPickerProvider>
|
||||
{courseCells && <AccountForCourseConflicts courseCells={courseCells} setCourse={setCourse} />}
|
||||
</ColorPickerProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -143,8 +150,8 @@ function AccountForCourseConflicts({ courseCells, setCourse }: AccountForCourseC
|
||||
courseDeptAndInstr={courseDeptAndInstr}
|
||||
timeAndLocation={timeAndLocation}
|
||||
status={status}
|
||||
colors={block.course.colors}
|
||||
onClick={() => setCourse(block.course)}
|
||||
blockData={block}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,15 +7,18 @@ interface Props {
|
||||
|
||||
/**
|
||||
* Component representing each 1 hour time block of a calendar
|
||||
* @param props
|
||||
*
|
||||
* @param row - The row of the cell
|
||||
* @param col - The column of the cell
|
||||
* @returns The CalendarCell component
|
||||
*/
|
||||
function CalendarCell(props: Props): JSX.Element {
|
||||
function CalendarCell({ row, col }: Props): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
className='h-full w-full flex items-center border-b border-r border-gray-300'
|
||||
style={{
|
||||
gridColumn: props.col + 3,
|
||||
gridRow: `${2 * props.row + 2} / ${2 * props.row + 4}`,
|
||||
gridColumn: col + 3,
|
||||
gridRow: `${2 * row + 2} / ${2 * row + 4}`,
|
||||
}}
|
||||
>
|
||||
<div className='h-0 w-full border-t border-gray-300/25' />
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
.cqInline {
|
||||
container-type: inline-size;
|
||||
|
||||
@mixin visually-hidden {
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: auto;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// value with all buttons is 610px
|
||||
@container (width < 90px) {
|
||||
.primaryActions span {
|
||||
@include visually-hidden;
|
||||
}
|
||||
}
|
||||
|
||||
// @container (width < 445px) {
|
||||
// .secondaryActions span {
|
||||
// @include visually-hidden;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
124
src/views/components/calendar/CalendarHeader/CalendarHeader.tsx
Normal file
124
src/views/components/calendar/CalendarHeader/CalendarHeader.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react';
|
||||
import { CalendarDots, Export, FilePng, Sidebar } from '@phosphor-icons/react';
|
||||
import styles from '@views/components/calendar/CalendarHeader/CalendarHeader.module.scss';
|
||||
import { Button } from '@views/components/common/Button';
|
||||
import DialogProvider from '@views/components/common/DialogProvider/DialogProvider';
|
||||
import Divider from '@views/components/common/Divider';
|
||||
import { ExtensionRootWrapper, styleResetClass } from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
||||
import ScheduleTotalHoursAndCourses from '@views/components/common/ScheduleTotalHoursAndCourses';
|
||||
import useSchedules from '@views/hooks/useSchedules';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import { saveAsCal, saveCalAsPng } from '../utils';
|
||||
|
||||
interface CalendarHeaderProps {
|
||||
sidebarOpen?: boolean;
|
||||
onSidebarToggle?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the header component for the calendar.
|
||||
* @returns The JSX element representing the calendar header.
|
||||
*/
|
||||
export default function CalendarHeader({ sidebarOpen, onSidebarToggle }: CalendarHeaderProps): JSX.Element {
|
||||
const [activeSchedule] = useSchedules();
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ scrollbarGutter: 'stable' }}
|
||||
className='sticky left-0 right-0 min-h-[85px] flex items-center gap-5 overflow-x-scroll overflow-y-hidden pl-spacing-7 pt-spacing-5'
|
||||
>
|
||||
{!sidebarOpen && (
|
||||
<Button
|
||||
variant='minimal'
|
||||
color='theme-black'
|
||||
onClick={onSidebarToggle}
|
||||
className='h-fit w-fit screenshot:hidden !p-0'
|
||||
icon={Sidebar}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='min-w-[11.5rem] screenshot:transform-origin-left screenshot:scale-120'>
|
||||
<ScheduleTotalHoursAndCourses
|
||||
scheduleName={activeSchedule.name}
|
||||
totalHours={activeSchedule.hours}
|
||||
totalCourses={activeSchedule.courses.length}
|
||||
/>
|
||||
</div>
|
||||
<Divider className='self-center screenshot:hidden' size='1.75rem' orientation='vertical' />
|
||||
{/* min-w-[310px] is the value with all the buttons */}
|
||||
<div className={clsx(styles.cqInline, 'flex flex-1 gap-5 min-w-[45x] screenshot:hidden')}>
|
||||
<div className={clsx(styles.primaryActions, 'min-w-fit flex gap-5')}>
|
||||
<DialogProvider>
|
||||
<Menu>
|
||||
<MenuButton className='bg-transparent'>
|
||||
<Button color='ut-black' size='small' variant='minimal' icon={Export}>
|
||||
Export
|
||||
</Button>
|
||||
</MenuButton>
|
||||
<MenuItems
|
||||
as={ExtensionRootWrapper}
|
||||
className={clsx([
|
||||
styleResetClass,
|
||||
'mt-spacing-3',
|
||||
'min-w-max cursor-pointer origin-top-right rounded bg-white p-1 text-black shadow-lg transition border border-ut-offwhite/50 focus:outline-none',
|
||||
'data-[closed]:(opacity-0 scale-95)',
|
||||
'data-[enter]:(ease-out-expo duration-150)',
|
||||
'data-[leave]:(ease-out duration-50)',
|
||||
])}
|
||||
transition
|
||||
anchor='bottom start'
|
||||
>
|
||||
<MenuItem>
|
||||
<Button
|
||||
className='w-full flex justify-start'
|
||||
onClick={() => requestAnimationFrame(() => saveCalAsPng())}
|
||||
color='ut-black'
|
||||
size='small'
|
||||
variant='minimal'
|
||||
icon={FilePng}
|
||||
>
|
||||
Save as .png
|
||||
</Button>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<Button
|
||||
className='w-full flex justify-start'
|
||||
onClick={saveAsCal}
|
||||
color='ut-black'
|
||||
size='small'
|
||||
variant='minimal'
|
||||
icon={CalendarDots}
|
||||
>
|
||||
Save as .cal
|
||||
</Button>
|
||||
</MenuItem>
|
||||
{/* <MenuItem>
|
||||
<Button color='ut-black' size='small' variant='minimal' icon={FileTxt}>
|
||||
Export Unique IDs
|
||||
</Button>
|
||||
</MenuItem> */}
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
</DialogProvider>
|
||||
{/* <Button className='invisible' color='ut-black' size='small' variant='minimal' icon={PlusCircle}>
|
||||
Quick Add
|
||||
</Button>
|
||||
<Button className='invisible' color='ut-black' size='small' variant='minimal' icon={SelectionPlus}>
|
||||
Block
|
||||
</Button> */}
|
||||
</div>
|
||||
{/* <Divider className='self-center' size='1.75rem' orientation='vertical' />
|
||||
<div className={clsx(styles.secondaryActions, 'min-w-fit flex flex-1 justify-end gap-5')}>
|
||||
<Button className='invisible' color='ut-black' size='small' variant='minimal' icon={BookmarkSimple}>
|
||||
Bookmarks
|
||||
</Button>
|
||||
<Button className='invisible' color='ut-black' size='small' variant='minimal' icon={MapPinArea}>
|
||||
UT Map
|
||||
</Button>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
import createSchedule from '@pages/background/lib/createSchedule';
|
||||
import { Plus } from '@phosphor-icons/react';
|
||||
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
||||
import { Button } from '@views/components/common/Button';
|
||||
import List from '@views/components/common/List';
|
||||
import ScheduleListItem from '@views/components/common/ScheduleListItem';
|
||||
import { SortableList } from '@views/components/common/SortableList';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import { useEnforceScheduleLimit } from '@views/hooks/useEnforceScheduleLimit';
|
||||
import useSchedules, { getActiveSchedule, switchSchedule } from '@views/hooks/useSchedules';
|
||||
import React from 'react';
|
||||
|
||||
import AddSchedule from '~icons/material-symbols/add';
|
||||
|
||||
/**
|
||||
* Renders a component that displays a list of schedules.
|
||||
*
|
||||
@@ -27,21 +26,24 @@ export function CalendarSchedules() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='min-w-full w-0 items-center'>
|
||||
<div className='m0 m-b-2 w-full flex justify-between'>
|
||||
<Text variant='h3' className='text-nowrap'>
|
||||
<div className='min-w-full w-0 flex flex-col items-center gap-y-spacing-3'>
|
||||
<div className='m0 w-full flex justify-between'>
|
||||
<Text variant='h3' className='text-nowrap text-theme-black'>
|
||||
MY SCHEDULES
|
||||
</Text>
|
||||
<Button variant='single' color='theme-black' className='h-fit p-0 btn' onClick={handleAddSchedule}>
|
||||
<AddSchedule className='h-6 w-6' />
|
||||
</Button>
|
||||
<Button
|
||||
variant='minimal'
|
||||
color='theme-black'
|
||||
className='h-fit w-fit !p-0 btn'
|
||||
onClick={handleAddSchedule}
|
||||
icon={Plus}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-col space-y-2.5'>
|
||||
<List
|
||||
gap={10}
|
||||
<div className='w-full flex flex-col'>
|
||||
<SortableList
|
||||
className='gap-spacing-3'
|
||||
draggables={schedules}
|
||||
itemKey={s => s.id}
|
||||
onReordered={reordered => {
|
||||
onChange={reordered => {
|
||||
const activeSchedule = getActiveSchedule();
|
||||
const activeIndex = reordered.findIndex(s => s.id === activeSchedule.id);
|
||||
|
||||
@@ -49,17 +51,10 @@ export function CalendarSchedules() {
|
||||
UserScheduleStore.set('schedules', reordered);
|
||||
UserScheduleStore.set('activeIndex', activeIndex);
|
||||
}}
|
||||
>
|
||||
{(schedule, handleProps) => (
|
||||
<ScheduleListItem
|
||||
schedule={schedule}
|
||||
onClick={() => {
|
||||
switchSchedule(schedule.id);
|
||||
}}
|
||||
dragHandleProps={handleProps}
|
||||
/>
|
||||
renderItem={schedule => (
|
||||
<ScheduleListItem schedule={schedule} onClick={() => switchSchedule(schedule.id)} />
|
||||
)}
|
||||
</List>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
import { initSettings, OptionsStore } from '@shared/storage/OptionsStore';
|
||||
import { Button } from '@views/components/common/Button';
|
||||
import CourseStatus from '@views/components/common/CourseStatus';
|
||||
import Divider from '@views/components/common/Divider';
|
||||
import { LargeLogo } from '@views/components/common/LogoIcon';
|
||||
import ScheduleTotalHoursAndCourses from '@views/components/common/ScheduleTotalHoursAndCourses';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import useSchedules from '@views/hooks/useSchedules';
|
||||
import { getUpdatedAtDateTimeString } from '@views/lib/getUpdatedAtDateTimeString';
|
||||
import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import MenuIcon from '~icons/material-symbols/menu';
|
||||
// import RefreshIcon from '~icons/material-symbols/refresh';
|
||||
import SettingsIcon from '~icons/material-symbols/settings';
|
||||
|
||||
/**
|
||||
* Opens the options page in a new tab.
|
||||
* @returns A promise that resolves when the options page is opened.
|
||||
*/
|
||||
const handleOpenOptions = async (): Promise<void> => {
|
||||
const url = chrome.runtime.getURL('/options.html');
|
||||
await openTabFromContentScript(url);
|
||||
};
|
||||
|
||||
interface CalendarHeaderProps {
|
||||
onSidebarToggle?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the header component for the calendar.
|
||||
* @returns The JSX element representing the calendar header.
|
||||
*/
|
||||
export default function CalendarHeader({ onSidebarToggle }: CalendarHeaderProps): JSX.Element {
|
||||
const [enableCourseStatusChips, setEnableCourseStatusChips] = useState<boolean>(false);
|
||||
const [enableDataRefreshing, setEnableDataRefreshing] = useState<boolean>(false);
|
||||
|
||||
const [activeSchedule] = useSchedules();
|
||||
|
||||
useEffect(() => {
|
||||
initSettings().then(({ enableCourseStatusChips, enableDataRefreshing }) => {
|
||||
setEnableCourseStatusChips(enableCourseStatusChips);
|
||||
setEnableDataRefreshing(enableDataRefreshing);
|
||||
});
|
||||
|
||||
const l1 = OptionsStore.listen('enableCourseStatusChips', async ({ newValue }) => {
|
||||
setEnableCourseStatusChips(newValue);
|
||||
// console.log('enableCourseStatusChips', newValue);
|
||||
});
|
||||
|
||||
const l2 = OptionsStore.listen('enableDataRefreshing', async ({ newValue }) => {
|
||||
setEnableDataRefreshing(newValue);
|
||||
// console.log('enableDataRefreshing', newValue);
|
||||
});
|
||||
|
||||
return () => {
|
||||
OptionsStore.removeListener(l1);
|
||||
OptionsStore.removeListener(l2);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-5 overflow-x-auto overflow-y-hidden border-b border-ut-offwhite px-7 py-4 md:overflow-x-hidden'>
|
||||
<Button
|
||||
variant='single'
|
||||
icon={MenuIcon}
|
||||
color='ut-gray'
|
||||
onClick={onSidebarToggle}
|
||||
className='screenshot:hidden'
|
||||
/>
|
||||
<LargeLogo />
|
||||
<Divider className='mx-2 self-center md:mx-4' size='2.5rem' orientation='vertical' />
|
||||
<div className='flex-1 screenshot:transform-origin-left screenshot:scale-120'>
|
||||
<ScheduleTotalHoursAndCourses
|
||||
scheduleName={activeSchedule.name}
|
||||
totalHours={activeSchedule.hours}
|
||||
totalCourses={activeSchedule.courses.length}
|
||||
/>
|
||||
{enableDataRefreshing && (
|
||||
<div className='flex items-center gap-1 screenshot:hidden'>
|
||||
<Text variant='mini' className='text-nowrap text-ut-gray font-normal!'>
|
||||
LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)}
|
||||
</Text>
|
||||
{/* <button className='inline-block h-4 w-4 bg-transparent p-0 btn'>
|
||||
<RefreshIcon className='h-4 w-4 animate-duration-800 text-ut-black' />
|
||||
</button> */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='hidden flex-row items-center justify-end gap-6 screenshot:hidden lg:flex'>
|
||||
{enableCourseStatusChips && (
|
||||
<>
|
||||
<CourseStatus status='WAITLISTED' size='mini' />
|
||||
<CourseStatus status='CLOSED' size='mini' />
|
||||
<CourseStatus status='CANCELLED' size='mini' />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* <Button variant='single' icon={UndoIcon} color='ut-black' />
|
||||
<Button variant='single' icon={RedoIcon} color='ut-black' /> */}
|
||||
<Button variant='single' icon={SettingsIcon} color='ut-black' onClick={handleOpenOptions} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import { ArrowUpRight } from '@phosphor-icons/react';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import OutwardArrowIcon from '~icons/material-symbols/arrow-outward';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
@@ -61,7 +60,7 @@ export default function ImportantLinks({ className }: Props): JSX.Element {
|
||||
rel='noreferrer'
|
||||
>
|
||||
<Text variant='p'>{link.text}</Text>
|
||||
<OutwardArrowIcon className='h-3 w-3' />
|
||||
<ArrowUpRight className='h-4 w-4' />
|
||||
</a>
|
||||
))}
|
||||
</article>
|
||||
|
||||
73
src/views/components/calendar/ResourceLinks.tsx
Normal file
73
src/views/components/calendar/ResourceLinks.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import OutwardArrowIcon from '~icons/material-symbols/arrow-outward';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
interface LinkItem {
|
||||
text: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
const links: LinkItem[] = [
|
||||
{
|
||||
text: "Spring '25 Course Schedule",
|
||||
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20252/',
|
||||
},
|
||||
{
|
||||
text: 'Course Schedule Archives',
|
||||
url: 'https://registrar.utexas.edu/schedules/archive',
|
||||
},
|
||||
{
|
||||
text: 'My Degree Audit (IDA)',
|
||||
url: 'https://utdirect.utexas.edu/apps/degree/audits/',
|
||||
},
|
||||
// {
|
||||
// text: "Summer '24 Course Schedule",
|
||||
// url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20246/',
|
||||
// },
|
||||
// {
|
||||
// text: "'24-'25 Academic Calendar",
|
||||
// url: 'https://registrar.utexas.edu/calendars/24-25',
|
||||
// },
|
||||
{
|
||||
text: 'Registration Info Sheet (RIS)',
|
||||
url: 'https://utdirect.utexas.edu/registrar/ris.WBX',
|
||||
},
|
||||
{
|
||||
text: 'Register for Courses',
|
||||
url: 'https://utdirect.utexas.edu/registration/chooseSemester.WBX',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* The "Resources" section of the calendar website
|
||||
* @returns
|
||||
*/
|
||||
export default function ResourceLinks({ className }: Props): JSX.Element {
|
||||
return (
|
||||
<article className={clsx(className, 'flex flex-col gap-spacing-3')}>
|
||||
<Text className='text-theme-black uppercase' variant='h3'>
|
||||
RESOURCES
|
||||
</Text>
|
||||
<div className='flex flex-col gap-spacing-3'>
|
||||
{links.map(link => (
|
||||
<a
|
||||
key={link.text}
|
||||
href={link.url}
|
||||
className='flex items-center gap-spacing-2 text-ut-burntorange underline-offset-2 hover:underline'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
<Text variant='p'>{link.text}</Text>
|
||||
<OutwardArrowIcon className='h-4 w-4' />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
import { ArrowUpRight } from '@phosphor-icons/react';
|
||||
import { CRX_PAGES } from '@shared/types/CRXPages';
|
||||
import { openReportWindow } from '@shared/util/openReportWindow';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import OutwardArrowIcon from '~icons/material-symbols/arrow-outward';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
@@ -63,7 +62,7 @@ export default function TeamLinks({ className }: Props): JSX.Element {
|
||||
onClick={event => handleClick(link, event)}
|
||||
>
|
||||
<Text variant='p'>{link.text}</Text>
|
||||
<OutwardArrowIcon className='h-3 w-3' />
|
||||
<ArrowUpRight className='h-4 w-4' />
|
||||
</a>
|
||||
))}
|
||||
</article>
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import type { Icon, IconProps } from '@phosphor-icons/react';
|
||||
import type { ThemeColor } from '@shared/types/ThemeColors';
|
||||
import { getThemeColorHexByName, getThemeColorRgbByName } from '@shared/util/themeColors';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import type IconComponent from '~icons/material-symbols';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
ref?: React.ForwardedRef<HTMLButtonElement>;
|
||||
style?: React.CSSProperties;
|
||||
variant: 'filled' | 'outline' | 'single';
|
||||
onClick?: () => void;
|
||||
icon?: typeof IconComponent;
|
||||
variant?: 'filled' | 'outline' | 'minimal';
|
||||
size?: 'regular' | 'small' | 'mini';
|
||||
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
icon?: Icon;
|
||||
iconProps?: IconProps;
|
||||
disabled?: boolean;
|
||||
title?: string;
|
||||
color: ThemeColor;
|
||||
@@ -23,10 +25,13 @@ interface Props {
|
||||
*/
|
||||
export function Button({
|
||||
className,
|
||||
ref,
|
||||
style,
|
||||
variant,
|
||||
variant = 'filled',
|
||||
size = 'regular',
|
||||
onClick,
|
||||
icon,
|
||||
iconProps,
|
||||
disabled,
|
||||
title,
|
||||
color,
|
||||
@@ -39,11 +44,12 @@ export function Button({
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
style={
|
||||
{
|
||||
...style,
|
||||
color: colorHex,
|
||||
backgroundColor: `rgb(${colorRgb} / var(--un-bg-opacity)`,
|
||||
...style,
|
||||
} satisfies React.CSSProperties
|
||||
}
|
||||
className={clsx(
|
||||
@@ -51,11 +57,15 @@ export function Button({
|
||||
{
|
||||
'text-white! bg-opacity-100 hover:enabled:shadow-md active:enabled:shadow-sm shadow-black/20':
|
||||
variant === 'filled',
|
||||
'bg-opacity-0 border-current hover:enabled:bg-opacity-8 border': variant === 'outline',
|
||||
'bg-opacity-0 border-none hover:enabled:bg-opacity-8': variant === 'single', // settings is the only "single"
|
||||
'px-2 py-1.25': isIconOnly && variant !== 'outline',
|
||||
'px-1.75 py-1.25': isIconOnly && variant === 'outline',
|
||||
'px-3.75': variant === 'outline' && !isIconOnly,
|
||||
'bg-opacity-0 border-current hover:enabled:bg-opacity-8 border stroke-width-[1px]':
|
||||
variant === 'outline',
|
||||
'bg-opacity-0 border-none hover:enabled:bg-opacity-8': variant === 'minimal',
|
||||
'h-10 gap-spacing-3 px-spacing-5': size === 'regular' && !isIconOnly,
|
||||
'h-10 w-10 p-spacing-2': size === 'regular' && isIconOnly,
|
||||
'h-[35px] gap-spacing-3 px-spacing-3': size === 'small' && !isIconOnly,
|
||||
'h-[35px] w-[35px] p-spacing-2': size === 'small' && isIconOnly,
|
||||
'h-6 p-spacing-2': size === 'mini' && !isIconOnly,
|
||||
'h-6 w-6 p-0': size === 'mini' && isIconOnly,
|
||||
},
|
||||
className
|
||||
)}
|
||||
@@ -63,9 +73,12 @@ export function Button({
|
||||
disabled={disabled}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
>
|
||||
{Icon && <Icon className='h-6 w-6' />}
|
||||
{Icon && <Icon {...iconProps} className={clsx('h-6 w-6', iconProps?.className)} />}
|
||||
{!isIconOnly && (
|
||||
<Text variant='h4' className='inline-flex translate-y-0.08 items-center gap-2'>
|
||||
<Text
|
||||
variant={size === 'regular' ? 'h4' : 'small'}
|
||||
className='inline-flex translate-y-0.08 items-center gap-2'
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -15,7 +15,9 @@ export interface ConflictsWithWarningProps {
|
||||
* The ConflictsWithWarning component is used to display a warning message when a course conflicts
|
||||
* with another course as part of the labels and details section
|
||||
*
|
||||
* @param props ConflictsWithWarningProps
|
||||
* @param className - The class name for the component
|
||||
* @param conflicts - The courses that conflict with the current course
|
||||
* @returns The ConflictsWithWarning component
|
||||
*/
|
||||
export default function ConflictsWithWarning({ className, conflicts }: ConflictsWithWarningProps): JSX.Element {
|
||||
return (
|
||||
|
||||
@@ -17,7 +17,9 @@ export interface CourseStatusProps {
|
||||
/**
|
||||
* The CourseStatus component as per the Labels and Details Figma section
|
||||
*
|
||||
* @param props CourseStatusProps
|
||||
* @param status - The status of the course
|
||||
* @param size - The size of the component
|
||||
* @returns The CourseStatus component
|
||||
*/
|
||||
export default function CourseStatus({ status, size }: CourseStatusProps): JSX.Element {
|
||||
const statusIconSizeClass = clsx({
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function Divider({ className, testId, size, orientation }: Divide
|
||||
<div
|
||||
style={style}
|
||||
data-testid={testId}
|
||||
className={clsx('border-solid border-ut-offwhite w-0 h-0', className)}
|
||||
className={clsx('border-solid border-ut-offwhite/50 w-0 h-0', className)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
@import 'src/views/styles/base.module.scss';
|
||||
@use 'sass:meta';
|
||||
@use 'src/views/styles/base.module.scss';
|
||||
|
||||
@layer base {
|
||||
.extensionRoot {
|
||||
@import 'tailwind-compat';
|
||||
@include meta.load-css('tailwind-compat');
|
||||
}
|
||||
|
||||
span {
|
||||
|
||||
84
src/views/components/common/FileUpload.tsx
Normal file
84
src/views/components/common/FileUpload.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import type { Icon, IconProps } from '@phosphor-icons/react';
|
||||
import type { ThemeColor } from '@shared/types/ThemeColors';
|
||||
import { getThemeColorHexByName, getThemeColorRgbByName } from '@shared/util/themeColors';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
variant?: 'filled' | 'outline' | 'minimal';
|
||||
size?: 'regular' | 'small' | 'mini';
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
icon?: Icon;
|
||||
iconProps?: IconProps;
|
||||
disabled?: boolean;
|
||||
title?: string;
|
||||
color: ThemeColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* A reusable input button component that follows the Button.tsx consistency
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
export default function FileUpload({
|
||||
className,
|
||||
style,
|
||||
variant = 'filled',
|
||||
size = 'regular',
|
||||
onChange,
|
||||
icon,
|
||||
iconProps,
|
||||
disabled,
|
||||
title,
|
||||
color,
|
||||
children,
|
||||
}: React.PropsWithChildren<Props>): JSX.Element {
|
||||
const Icon = icon;
|
||||
const isIconOnly = !children && !!icon;
|
||||
const colorHex = getThemeColorHexByName(color);
|
||||
const colorRgb = getThemeColorRgbByName(color)?.join(' ');
|
||||
|
||||
return (
|
||||
<label
|
||||
style={
|
||||
{
|
||||
...style,
|
||||
color: disabled ? 'ut-gray' : colorHex,
|
||||
backgroundColor: `rgb(${colorRgb} / var(--un-bg-opacity)`,
|
||||
} satisfies React.CSSProperties
|
||||
}
|
||||
className={clsx(
|
||||
'btn',
|
||||
{
|
||||
'text-white! bg-opacity-100 hover:enabled:shadow-md active:enabled:shadow-sm shadow-black/20':
|
||||
variant === 'filled',
|
||||
'bg-opacity-0 border-current hover:enabled:bg-opacity-8 border stroke-width-[1px]':
|
||||
variant === 'outline',
|
||||
'bg-opacity-0 border-none hover:enabled:bg-opacity-8': variant === 'minimal',
|
||||
'h-10 gap-spacing-3 px-spacing-5': size === 'regular' && !isIconOnly,
|
||||
'h-10 w-10 p-spacing-2': size === 'regular' && isIconOnly,
|
||||
'h-[35px] gap-spacing-3 px-spacing-3': size === 'small' && !isIconOnly,
|
||||
'h-[35px] w-[35px] p-spacing-2': size === 'small' && isIconOnly,
|
||||
'h-6 p-spacing-2': size === 'mini' && !isIconOnly,
|
||||
'h-6 w-6 p-0': size === 'mini' && isIconOnly,
|
||||
},
|
||||
className
|
||||
)}
|
||||
title={title}
|
||||
>
|
||||
{Icon && <Icon {...iconProps} className={clsx('h-6 w-6', iconProps?.className)} />}
|
||||
{!isIconOnly && (
|
||||
<Text
|
||||
variant={size === 'regular' ? 'h4' : 'small'}
|
||||
className='inline-flex translate-y-0.08 items-center gap-2'
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
)}
|
||||
<input type='file' className='hidden' disabled={disabled} onChange={disabled ? undefined : onChange} />
|
||||
</label>
|
||||
);
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
import type { DraggableProvided, DraggableProvidedDragHandleProps, OnDragEndResponder } from '@hello-pangea/dnd';
|
||||
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
|
||||
import type { ReactElement } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import ExtensionRoot from './ExtensionRoot/ExtensionRoot';
|
||||
|
||||
/*
|
||||
* Ctrl + f dragHandleProps on PopupCourseBlock.tsx for example implementation of drag handle (two lines of code)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Props for the List component.
|
||||
*/
|
||||
export interface ListProps<T> {
|
||||
draggables: T[];
|
||||
children: (draggable: T, handleProps: DraggableProvidedDragHandleProps) => ReactElement;
|
||||
onReordered: (elements: T[]) => void;
|
||||
itemKey: (item: T) => React.Key;
|
||||
gap?: number; // Impacts the spacing between items in the list
|
||||
}
|
||||
|
||||
function wrap<T>(draggableElements: T[], keyTransform: ListProps<T>['itemKey']) {
|
||||
return draggableElements.map(element => ({
|
||||
id: keyTransform(element),
|
||||
content: element,
|
||||
}));
|
||||
}
|
||||
|
||||
function reorder<T>(list: T[], startIndex: number, endIndex: number) {
|
||||
const listCopy = [...list];
|
||||
|
||||
const [removed] = listCopy.splice(startIndex, 1);
|
||||
if (removed) {
|
||||
listCopy.splice(endIndex, 0, removed);
|
||||
}
|
||||
|
||||
return listCopy;
|
||||
}
|
||||
|
||||
function getStyle(provided: DraggableProvided, style: React.CSSProperties) {
|
||||
const combined = {
|
||||
...style,
|
||||
...provided.draggableProps.style,
|
||||
};
|
||||
|
||||
return combined;
|
||||
}
|
||||
|
||||
function Item<T>(props: {
|
||||
provided: DraggableProvided;
|
||||
style: React.CSSProperties;
|
||||
item: T;
|
||||
isDragging: boolean;
|
||||
children: React.ReactElement;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
{...props.provided.draggableProps}
|
||||
ref={props.provided.innerRef}
|
||||
style={getStyle(props.provided, props.style)}
|
||||
className={props.isDragging ? 'is-dragging' : ''}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* `List` is a functional component that displays a course meeting.
|
||||
*
|
||||
* @example
|
||||
* <List draggableElements={elements} />
|
||||
*/
|
||||
function List<T>({ draggables, itemKey, children, onReordered, gap }: ListProps<T>): JSX.Element {
|
||||
const [items, setItems] = useState(wrap(draggables, itemKey));
|
||||
|
||||
const transformFunction = children;
|
||||
|
||||
useEffect(() => {
|
||||
setItems(wrap(draggables, itemKey));
|
||||
|
||||
// This is on purpose
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [draggables]);
|
||||
|
||||
const onDragEnd: OnDragEndResponder = useCallback(
|
||||
({ destination, source }) => {
|
||||
if (!destination) return;
|
||||
if (source.index === destination.index) return;
|
||||
|
||||
// will reorder in place
|
||||
const reordered = reorder(items, source.index, destination.index);
|
||||
|
||||
setItems(reordered);
|
||||
onReordered(reordered.map(item => item.content));
|
||||
},
|
||||
|
||||
// This is on purpose
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[items]
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={{ overflow: 'hidden' }}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable
|
||||
droppableId='droppable'
|
||||
direction='vertical'
|
||||
renderClone={(provided, snapshot, rubric) => {
|
||||
let { style } = provided.draggableProps;
|
||||
const transform = style?.transform;
|
||||
|
||||
if (snapshot.isDragging && transform) {
|
||||
let [, , y] = transform.match(/translate\(([-\d]+)px, ([-\d]+)px\)/) || [];
|
||||
|
||||
style.transform = `translate3d(0px, ${y}px, 0px)`; // Apply constrained y value
|
||||
}
|
||||
|
||||
return (
|
||||
<Item
|
||||
provided={provided}
|
||||
isDragging={snapshot.isDragging}
|
||||
item={items[rubric.source.index]}
|
||||
style={{
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
<ExtensionRoot>
|
||||
{transformFunction(items[rubric.source.index]!.content, provided.dragHandleProps!)}
|
||||
</ExtensionRoot>
|
||||
</Item>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{provided => (
|
||||
<div {...provided.droppableProps} ref={provided.innerRef} style={{ marginBottom: `-${gap}px` }}>
|
||||
{items.map((item, index) => (
|
||||
<Draggable key={item.id} draggableId={item.id.toString()} index={index}>
|
||||
{({ innerRef, draggableProps, dragHandleProps }) => (
|
||||
<div
|
||||
ref={innerRef}
|
||||
{...draggableProps}
|
||||
style={{
|
||||
...draggableProps.style,
|
||||
// if last item, don't add margin
|
||||
marginBottom: `${gap}px`,
|
||||
}}
|
||||
>
|
||||
{
|
||||
transformFunction(
|
||||
item.content,
|
||||
dragHandleProps!
|
||||
) /* always exists; only doesn't when "isDragDisabled" is set */
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(List) as typeof List;
|
||||
@@ -2,10 +2,15 @@ import clsx from 'clsx';
|
||||
import type { SVGProps } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
interface LogoIconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the logo icon.
|
||||
* @param {SVGProps<SVGSVGElement>} props - The SVG props.
|
||||
* @returns {JSX.Element} The rendered logo icon.
|
||||
*
|
||||
* @param props - The SVG props.
|
||||
* @returns The rendered logo icon.
|
||||
*/
|
||||
export function LogoIcon(props: SVGProps<SVGSVGElement>): JSX.Element {
|
||||
return (
|
||||
@@ -20,11 +25,11 @@ export function LogoIcon(props: SVGProps<SVGSVGElement>): JSX.Element {
|
||||
|
||||
/**
|
||||
* Renders the small logo.
|
||||
* @param {Object} props - The component props.
|
||||
* @param {string} props.className - The class name for the logo container.
|
||||
* @returns {JSX.Element} The rendered small logo.
|
||||
*
|
||||
* @param className - The class name for the logo container.
|
||||
* @returns The rendered small logo.
|
||||
*/
|
||||
export function SmallLogo({ className }: { className?: string }): JSX.Element {
|
||||
export function SmallLogo({ className }: LogoIconProps): JSX.Element {
|
||||
return (
|
||||
<div className={clsx('flex items-center gap-2', className)}>
|
||||
<LogoIcon />
|
||||
@@ -43,15 +48,15 @@ export function SmallLogo({ className }: { className?: string }): JSX.Element {
|
||||
|
||||
/**
|
||||
* Renders the large logo.
|
||||
* @param {Object} props - The component props.
|
||||
* @param {string} props.className - The class name for the logo container.
|
||||
* @returns {JSX.Element} The rendered large logo.
|
||||
*
|
||||
* @param className - The class name for the logo container.
|
||||
* @returns The rendered large logo.
|
||||
*/
|
||||
export function LargeLogo({ className }: { className?: string }): JSX.Element {
|
||||
export function LargeLogo({ className }: LogoIconProps): JSX.Element {
|
||||
return (
|
||||
<div className={clsx('flex items-center gap-2', className)}>
|
||||
<div className={clsx('flex items-center gap-spacing-3', className)}>
|
||||
<LogoIcon className='h-12 w-12' />
|
||||
<div className='mt-1 hidden flex-col text-[1.35rem] font-medium leading-[1em] md:flex screenshot:flex'>
|
||||
<div className='mt-1 flex flex-col text-[1.35rem] font-medium leading-[1em] screenshot:flex'>
|
||||
<p className='text-nowrap text-ut-burntorange'>UT Registration</p>
|
||||
<p className='text-ut-burntorange'>
|
||||
Plus{' '}
|
||||
|
||||
@@ -53,7 +53,7 @@ function MigrationButtons({ close }: { close: () => void }): JSX.Element {
|
||||
</Text>
|
||||
)}
|
||||
<Button
|
||||
variant='single'
|
||||
variant='minimal'
|
||||
color='ut-black'
|
||||
onClick={() => {
|
||||
close();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { DraggableProvidedDragHandleProps } from '@hello-pangea/dnd';
|
||||
import { Check, Copy, DotsSixVertical } from '@phosphor-icons/react';
|
||||
import { background } from '@shared/messages';
|
||||
import { initSettings, OptionsStore } from '@shared/storage/OptionsStore';
|
||||
import type { Course } from '@shared/types/Course';
|
||||
@@ -8,9 +8,10 @@ import { pickFontColor } from '@shared/util/colors';
|
||||
import { StatusIcon } from '@shared/util/icons';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { memo, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import DragIndicatorIcon from '~icons/material-symbols/drag-indicator';
|
||||
import { Button } from './Button';
|
||||
import { SortableListDragHandle } from './SortableListDragHandle';
|
||||
|
||||
/**
|
||||
* Props for PopupCourseBlock
|
||||
@@ -19,28 +20,60 @@ export interface PopupCourseBlockProps {
|
||||
className?: string;
|
||||
course: Course;
|
||||
colors: CourseColors;
|
||||
dragHandleProps?: DraggableProvidedDragHandleProps;
|
||||
}
|
||||
|
||||
const IS_STORYBOOK = import.meta.env.STORYBOOK;
|
||||
|
||||
const CourseMeeting = memo(
|
||||
({ meeting, fontColor }: { meeting: Course['schedule']['meetings'][0]; fontColor: string }) => {
|
||||
const dateString = meeting.getDaysString({ format: 'short' });
|
||||
return (
|
||||
<Text key={dateString} className={clsx('flex-1 truncate select-none', fontColor)} variant='h3-course'>
|
||||
{`${dateString} ${meeting.getTimeString({ separator: '-' })}${
|
||||
meeting.location ? `, ${meeting.location.building} ${meeting.location.room}` : ''
|
||||
}`}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* The "course block" to be used in the extension popup.
|
||||
*
|
||||
* @param props PopupCourseBlockProps
|
||||
* @param className - The class name to apply to the component.
|
||||
* @param course - The course object to display.
|
||||
* @param colors - The colors to use for the course block.
|
||||
* @param dragHandleProps - The drag handle props for the course block.
|
||||
* @returns The rendered PopupCourseBlock component.
|
||||
*/
|
||||
export default function PopupCourseBlock({
|
||||
className,
|
||||
course,
|
||||
colors,
|
||||
dragHandleProps,
|
||||
}: PopupCourseBlockProps): JSX.Element {
|
||||
export default function PopupCourseBlock({ className, course, colors }: PopupCourseBlockProps): JSX.Element {
|
||||
const [enableCourseStatusChips, setEnableCourseStatusChips] = useState<boolean>(false);
|
||||
|
||||
const [isCopied, setIsCopied] = useState<boolean>(false);
|
||||
const lastCopyTime = useRef<number>(0);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
initSettings().then(({ enableCourseStatusChips }) => setEnableCourseStatusChips(enableCourseStatusChips));
|
||||
const initAllSettings = async () => {
|
||||
const { enableCourseStatusChips } = await initSettings();
|
||||
setEnableCourseStatusChips(enableCourseStatusChips);
|
||||
};
|
||||
|
||||
initAllSettings();
|
||||
|
||||
const l1 = OptionsStore.listen('enableCourseStatusChips', async ({ newValue }) => {
|
||||
setEnableCourseStatusChips(newValue);
|
||||
// console.log('enableCourseStatusChips', newValue);
|
||||
});
|
||||
|
||||
// adds transition for shadow hover after three frames
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
if (ref.current) {
|
||||
ref.current.classList.add('transition-shadow-100');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return () => {
|
||||
@@ -57,41 +90,117 @@ export default function PopupCourseBlock({
|
||||
window.close();
|
||||
};
|
||||
|
||||
const handleCopy = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
const now = Date.now();
|
||||
if (now - lastCopyTime.current < 500) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastCopyTime.current = now;
|
||||
await navigator.clipboard.writeText(formattedUniqueId);
|
||||
setIsCopied(true);
|
||||
setTimeout(() => setIsCopied(false), 500);
|
||||
};
|
||||
|
||||
const meetings = useMemo(
|
||||
() =>
|
||||
course.schedule.meetings.map(meeting => (
|
||||
<CourseMeeting
|
||||
key={meeting.getDaysString({ format: 'short' })}
|
||||
meeting={meeting}
|
||||
fontColor={fontColor}
|
||||
/>
|
||||
)),
|
||||
[course.schedule.meetings, fontColor]
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: colors.primaryColor,
|
||||
}}
|
||||
className={clsx(
|
||||
'h-full w-full inline-flex items-center justify-center gap-1 rounded pr-3 focusable cursor-pointer text-left',
|
||||
'w-full inline-flex items-center justify-center gap-1 rounded focusable cursor-pointer text-left hover:shadow-md ease-out group-[.is-dragging]:shadow-md min-h-[55px]',
|
||||
className
|
||||
)}
|
||||
onClick={handleClick}
|
||||
ref={ref}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: colors.secondaryColor,
|
||||
}}
|
||||
className='flex items-center self-stretch rounded rounded-r-0 cursor-move!'
|
||||
{...dragHandleProps}
|
||||
>
|
||||
<DragIndicatorIcon className='h-6 w-6 text-white' />
|
||||
</div>
|
||||
<Text className={clsx('flex-1 py-3.5 truncate', fontColor)} variant='h1-course'>
|
||||
<span className='px-0.5 font-450'>{formattedUniqueId}</span> {course.department} {course.number}
|
||||
{course.instructors.length > 0 ? <> – </> : ''}
|
||||
{course.instructors.map(v => v.toString({ format: 'last', case: 'capitalize' })).join('; ')}
|
||||
</Text>
|
||||
{enableCourseStatusChips && course.status !== Status.OPEN && (
|
||||
{IS_STORYBOOK ? (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: colors.secondaryColor,
|
||||
}}
|
||||
className='ml-1 flex items-center justify-center justify-self-end rounded p-1px text-white'
|
||||
className='flex cursor-move items-center self-stretch rounded rounded-r-0 px-spacing-2'
|
||||
>
|
||||
<StatusIcon status={course.status} className='h-5 w-5' />
|
||||
<DotsSixVertical weight='bold' className='h-6 w-6 cursor-move text-white' />
|
||||
</div>
|
||||
) : (
|
||||
<SortableListDragHandle
|
||||
style={{
|
||||
backgroundColor: colors.secondaryColor,
|
||||
}}
|
||||
className='flex cursor-move items-center self-stretch rounded rounded-r-0 px-spacing-2'
|
||||
>
|
||||
<DotsSixVertical weight='bold' className='h-6 w-6 cursor-move text-white' />
|
||||
</SortableListDragHandle>
|
||||
)}
|
||||
<div className='h-full flex flex-1 justify-center gap-spacing-3 p-spacing-3'>
|
||||
<div className='flex flex-1 flex-col justify-center gap-spacing-1'>
|
||||
<Text
|
||||
className={clsx(
|
||||
`truncate select-none justify-center ${meetings.length > 0 ? 'mb-auto' : 'my-auto'}`,
|
||||
fontColor
|
||||
)}
|
||||
variant='h1-course'
|
||||
>
|
||||
{course.department} {course.number}
|
||||
{course.instructors.length > 0 ? <> – </> : ''}
|
||||
{course.instructors.map(v => v.toString({ format: 'last' })).join('; ')}
|
||||
</Text>
|
||||
<div className='flex flex-col'>{meetings}</div>
|
||||
</div>
|
||||
{enableCourseStatusChips && course.status !== Status.OPEN && (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: colors.secondaryColor,
|
||||
}}
|
||||
className='ml-1 flex items-center justify-center justify-self-end rounded p-[3px] text-white'
|
||||
>
|
||||
<StatusIcon status={course.status} className='h-6 w-6' />
|
||||
</div>
|
||||
)}
|
||||
<div className='flex flex-col justify-center'>
|
||||
<Button
|
||||
color='ut-gray'
|
||||
onClick={handleCopy}
|
||||
className='h-full max-h-[30px] max-w-fit gap-spacing-2 rounded text-white px-spacing-3! py-spacing-2!'
|
||||
style={{
|
||||
backgroundColor: colors.secondaryColor,
|
||||
}}
|
||||
>
|
||||
<div className='relative h-[21px] w-[21px]'>
|
||||
<Check
|
||||
className={clsx(
|
||||
'absolute size-full inset-0 text-white transition-all duration-250 ease-in-out',
|
||||
isCopied ? 'opacity-100 scale-100' : 'opacity-0 scale-75'
|
||||
)}
|
||||
/>
|
||||
<Copy
|
||||
weight='fill'
|
||||
className={clsx(
|
||||
'absolute size-full inset-0 text-white transition-all duration-250 ease-in-out select-none',
|
||||
isCopied ? 'opacity-0 scale-75' : 'opacity-100 scale-100'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Text variant='h2' className='select-none text-base!'>
|
||||
{formattedUniqueId}
|
||||
</Text>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,13 +18,15 @@ export interface PromptDialogProps {
|
||||
|
||||
/**
|
||||
* A reusable dialog component that can be used to display a prompt to the user.
|
||||
* @param {PromptDialogProps} props.isOpen - Whether the dialog is open or not.
|
||||
* @param {Function} props.onClose - A function to call when the user exits the dialog.
|
||||
* @param {React.ReactElement<typeof Text>} props.title - The title of the dialog.
|
||||
* @param {React.ReactElement<typeof Text>} props.content - The content of the dialog.
|
||||
* @param {React.ReactElement<typeof Button>[]} props.children - The buttons to display in the dialog.
|
||||
*
|
||||
* @param isOpen - Whether the dialog is open or not.
|
||||
* @param onClose - A function to call when the user exits the dialog.
|
||||
* @param title - The title of the dialog.
|
||||
* @param content - The content of the dialog.
|
||||
* @param children - The buttons to display in the dialog.
|
||||
* @returns The rendered PromptDialog component.
|
||||
*/
|
||||
function PromptDialog({ isOpen, onClose, title, content, children }: PromptDialogProps) {
|
||||
function PromptDialog({ isOpen, onClose, title, content, children }: PromptDialogProps): JSX.Element {
|
||||
return (
|
||||
<Transition appear show={isOpen} as={React.Fragment}>
|
||||
<Dialog as='div' onClose={onClose} className='relative z-50'>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel, Transition } from '@headlessui/react';
|
||||
import { CaretDown, CaretUp } from '@phosphor-icons/react';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import useSchedules from '@views/hooks/useSchedules';
|
||||
import React from 'react';
|
||||
|
||||
import DropdownArrowDown from '~icons/material-symbols/arrow-drop-down';
|
||||
import DropdownArrowUp from '~icons/material-symbols/arrow-drop-up';
|
||||
|
||||
/**
|
||||
* Props for the Dropdown component.
|
||||
*/
|
||||
@@ -17,43 +15,52 @@ export type ScheduleDropdownProps = {
|
||||
/**
|
||||
* This is a reusable dropdown component that can be used to toggle the visiblity of information
|
||||
*/
|
||||
export default function ScheduleDropdown(props: ScheduleDropdownProps) {
|
||||
export default function ScheduleDropdown({ defaultOpen, children }: ScheduleDropdownProps) {
|
||||
const [activeSchedule] = useSchedules();
|
||||
|
||||
return (
|
||||
<div className='border border-ut-offwhite rounded border-solid bg-white'>
|
||||
<Disclosure defaultOpen={props.defaultOpen}>
|
||||
<div className='border border-ut-offwhite/50 rounded bg-white'>
|
||||
<Disclosure defaultOpen={defaultOpen}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<DisclosureButton className='w-full flex items-center border-none bg-transparent px-3.5 py-2.5 text-left'>
|
||||
<div className='flex-1'>
|
||||
<Text as='div' variant='h4' className='mb-1 w-100% text-ut-burntorange'>
|
||||
{(activeSchedule ? activeSchedule.name : 'Schedule').toUpperCase()}:
|
||||
<Text as='div' variant='h3' className='w-100% text-ut-burntorange normal-case!'>
|
||||
{activeSchedule ? activeSchedule.name : 'Schedule'}
|
||||
</Text>
|
||||
<p className='-mb-0.5'>
|
||||
<Text variant='h3' className='text-theme-black leading-[75%]!'>
|
||||
{activeSchedule ? activeSchedule.hours : 0} HOURS
|
||||
</Text>
|
||||
<Text variant='h4' className='ml-2.5 text-ut-black leading-[75%]!'>
|
||||
{activeSchedule ? activeSchedule.courses.length : 0} Courses
|
||||
</Text>
|
||||
</p>
|
||||
<div className='flex gap-2.5 text-theme-black leading-[75%]!'>
|
||||
<div className='flex gap-1.25'>
|
||||
<Text variant='h4'>{activeSchedule ? activeSchedule.hours : 0}</Text>
|
||||
<Text variant='h4' className='font-all-small-caps!'>
|
||||
{activeSchedule.hours === 1 ? 'HOUR' : 'HOURS'}
|
||||
</Text>
|
||||
</div>
|
||||
<div className='flex gap-1.25'>
|
||||
<Text variant='h4'>{activeSchedule ? activeSchedule.courses.length : 0}</Text>
|
||||
<Text variant='h4' className='font-all-small-caps!'>
|
||||
{activeSchedule.courses.length === 1 ? 'COURSE' : 'COURSES'}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Text className='text-ut-burntorange text-2xl! font-normal!'>
|
||||
{open ? <DropdownArrowDown /> : <DropdownArrowUp />}
|
||||
{open ? <CaretDown weight='fill' /> : <CaretUp weight='fill' />}
|
||||
</Text>
|
||||
</DisclosureButton>
|
||||
|
||||
<Transition
|
||||
as='div'
|
||||
className='contain-paint max-h-55 origin-top overflow-auto transition-all duration-400 ease-in-out-expo'
|
||||
enterFrom='transform scale-98 opacity-0 max-h-0!'
|
||||
enterTo='transform scale-100 opacity-100 max-h-55'
|
||||
leave='ease-out-expo'
|
||||
leaveFrom='transform scale-100 opacity-100 max-h-55'
|
||||
leaveTo='transform scale-98 opacity-0 max-h-0!'
|
||||
className='overflow-hidden'
|
||||
enter='transition-[max-height,opacity,padding] duration-300 ease-in-out-expo'
|
||||
enterFrom='max-h-0 opacity-0 p-0.5'
|
||||
enterTo='max-h-[440px] opacity-100 p-0'
|
||||
leave='transition-[max-height,opacity,padding] duration-300 ease-in-out-expo'
|
||||
leaveFrom='max-h-[440px] opacity-100 p-0'
|
||||
leaveTo='max-h-0 opacity-0 p-0.5'
|
||||
>
|
||||
<DisclosurePanel className='px-3.5 pb-2.5 pt-2'>{props.children}</DisclosurePanel>
|
||||
<div className='px-3.5 pb-2.5 pt-2'>
|
||||
<DisclosurePanel>{children}</DisclosurePanel>
|
||||
</div>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -2,37 +2,49 @@ import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react';
|
||||
import deleteSchedule from '@pages/background/lib/deleteSchedule';
|
||||
import duplicateSchedule from '@pages/background/lib/duplicateSchedule';
|
||||
import renameSchedule from '@pages/background/lib/renameSchedule';
|
||||
import {
|
||||
Circle,
|
||||
CopySimple,
|
||||
DotsSixVertical,
|
||||
DotsThree,
|
||||
PencilSimpleLine,
|
||||
RadioButton,
|
||||
Trash,
|
||||
} from '@phosphor-icons/react';
|
||||
import { background } from '@shared/messages';
|
||||
import type { UserSchedule } from '@shared/types/UserSchedule';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import { useEnforceScheduleLimit } from '@views/hooks/useEnforceScheduleLimit';
|
||||
import useSchedules from '@views/hooks/useSchedules';
|
||||
import { LONGHORN_DEVELOPERS_ADMINS, LONGHORN_DEVELOPERS_SWE } from '@views/lib/getGitHubStats';
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import DragIndicatorIcon from '~icons/material-symbols/drag-indicator';
|
||||
import MoreActionsIcon from '~icons/material-symbols/more-vert';
|
||||
|
||||
import { Button } from './Button';
|
||||
import DialogProvider, { usePrompt } from './DialogProvider/DialogProvider';
|
||||
import { ExtensionRootWrapper, styleResetClass } from './ExtensionRoot/ExtensionRoot';
|
||||
import Link from './Link';
|
||||
import { SortableListDragHandle } from './SortableListDragHandle';
|
||||
|
||||
/**
|
||||
* Props for the ScheduleListItem component.
|
||||
*/
|
||||
export type Props = {
|
||||
style?: React.CSSProperties;
|
||||
interface ScheduleListItemProps {
|
||||
schedule: UserSchedule;
|
||||
dragHandleProps?: Omit<React.HTMLAttributes<HTMLDivElement>, 'className'>;
|
||||
onClick?: React.DOMAttributes<HTMLDivElement>['onClick'];
|
||||
};
|
||||
}
|
||||
|
||||
const IS_STORYBOOK = import.meta.env.STORYBOOK;
|
||||
const teamMembers = [...LONGHORN_DEVELOPERS_ADMINS, ...LONGHORN_DEVELOPERS_SWE];
|
||||
|
||||
/**
|
||||
* This is a reusable dropdown component that can be used to toggle the visiblity of information
|
||||
*/
|
||||
export default function ScheduleListItem({ schedule, dragHandleProps, onClick }: Props): JSX.Element {
|
||||
export default function ScheduleListItem({ schedule, onClick }: ScheduleListItemProps): JSX.Element {
|
||||
const [activeSchedule] = useSchedules();
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [editorValue, setEditorValue] = useState(schedule.name);
|
||||
const teamMember = teamMembers[Math.floor(Math.random() * teamMembers.length)];
|
||||
|
||||
const showDialog = usePrompt();
|
||||
const enforceScheduleLimit = useEnforceScheduleLimit();
|
||||
@@ -58,80 +70,103 @@ export default function ScheduleListItem({ schedule, dragHandleProps, onClick }:
|
||||
const handleBlur = async () => {
|
||||
if (editorValue.trim() !== '' && editorValue.trim() !== schedule.name) {
|
||||
schedule.name = (await renameSchedule(schedule.id, editorValue.trim())) as string;
|
||||
|
||||
if (schedule.name === '404') {
|
||||
const url = chrome.runtime.getURL('/404.html');
|
||||
background.openNewTab({ url });
|
||||
}
|
||||
|
||||
if (Math.random() < 0.002) {
|
||||
showDialog({
|
||||
title: 'Schedule name already taken',
|
||||
description: (
|
||||
<>
|
||||
<Text>Schedule name</Text>
|
||||
<Text className='text-ut-burntorange'> {schedule.name} </Text>
|
||||
<Text>
|
||||
is already taken.
|
||||
<br />
|
||||
<br />
|
||||
Join the
|
||||
</Text>
|
||||
<Link className='link' href='https://discord.gg/7pQDBGdmb7'>
|
||||
<Text>Discord</Text>
|
||||
</Link>
|
||||
<Text> to contact {teamMember?.name as string}.</Text>
|
||||
</>
|
||||
),
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
buttons: close => (
|
||||
<Button variant='minimal' color='ut-black' onClick={close}>
|
||||
Go Back
|
||||
</Button>
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
if (schedule.id === activeSchedule.id) {
|
||||
showDialog({
|
||||
title: `Unable to delete active schedule.`,
|
||||
|
||||
description: (
|
||||
<>
|
||||
<Text>Deleting the active schedule</Text>
|
||||
<Text className='text-ut-burntorange'> {schedule.name} </Text>
|
||||
<Text>is not allowed. Please switch to another schedule and try again.</Text>
|
||||
</>
|
||||
),
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
buttons: close => (
|
||||
<Button variant='filled' color='ut-burntorange' onClick={close}>
|
||||
I Understand
|
||||
showDialog({
|
||||
title: 'Are you sure?',
|
||||
description: (
|
||||
<>
|
||||
<Text>Deleting</Text>
|
||||
<Text className='text-ut-burntorange'> {schedule.name} </Text>
|
||||
<Text>is permanent and will remove all added courses from that schedule.</Text>
|
||||
</>
|
||||
),
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
buttons: close => (
|
||||
<>
|
||||
<Button variant='minimal' color='ut-black' onClick={close}>
|
||||
Cancel
|
||||
</Button>
|
||||
),
|
||||
});
|
||||
} else {
|
||||
showDialog({
|
||||
title: `Are you sure?`,
|
||||
description: (
|
||||
<>
|
||||
<Text>Deleting</Text>
|
||||
<Text className='text-ut-burntorange'> {schedule.name} </Text>
|
||||
<Text>is permanent and will remove all added courses from that schedule.</Text>
|
||||
</>
|
||||
),
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
buttons: close => (
|
||||
<>
|
||||
<Button variant='single' color='ut-black' onClick={close}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='filled'
|
||||
color='theme-red'
|
||||
onClick={() => {
|
||||
close();
|
||||
deleteSchedule(schedule.id);
|
||||
}}
|
||||
>
|
||||
Delete Permanently
|
||||
</Button>
|
||||
</>
|
||||
),
|
||||
});
|
||||
}
|
||||
<Button
|
||||
variant='filled'
|
||||
color='theme-red'
|
||||
onClick={() => {
|
||||
close();
|
||||
deleteSchedule(schedule.id);
|
||||
}}
|
||||
>
|
||||
Delete Permanently
|
||||
</Button>
|
||||
</>
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='rounded bg-white'>
|
||||
<li className='w-full flex cursor-pointer items-center text-ut-burntorange'>
|
||||
<div className='h-full cursor-move focusable' {...dragHandleProps}>
|
||||
<DragIndicatorIcon className='h-6 w-6 cursor-move text-zinc-300 btn-transition -ml-1.5 hover:text-zinc-400' />
|
||||
</div>
|
||||
<div className='h-7.5 rounded bg-white'>
|
||||
<div className='h-full w-full flex cursor-pointer items-center gap-[1px] text-ut-burntorange'>
|
||||
{IS_STORYBOOK ? (
|
||||
<DotsSixVertical
|
||||
weight='bold'
|
||||
className='h-6 w-6 cursor-move text-zinc-300 btn-transition -ml-1.5 hover:text-zinc-400'
|
||||
/>
|
||||
) : (
|
||||
<SortableListDragHandle className='flex cursor-move items-center justify-center'>
|
||||
<DotsSixVertical
|
||||
weight='bold'
|
||||
className='h-6 w-6 cursor-move text-zinc-300 btn-transition -ml-1.5 hover:text-zinc-400'
|
||||
/>
|
||||
</SortableListDragHandle>
|
||||
)}
|
||||
<div className='group relative flex flex-1 items-center overflow-x-hidden'>
|
||||
<div
|
||||
className='group/circle flex flex-grow items-center gap-1.5 overflow-x-hidden'
|
||||
className='group/circle flex flex-grow items-center gap-spacing-3 overflow-x-hidden'
|
||||
onClick={(...e) => !isEditing && onClick?.(...e)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'h-5.5 w-5.5 relative flex-shrink-0 border-2px border-current rounded-full btn-transition group-active/circle:scale-95 after:(absolute content-empty bg-current h-2.9 w-2.9 rounded-full transition transform-gpu scale-100 ease-out-expo duration-250 -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2)',
|
||||
{
|
||||
'after:(scale-0! opacity-0 ease-in-out! duration-200!)': !isActive,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
{isActive ? (
|
||||
<RadioButton
|
||||
className='h-7.5 w-7.5 shrink-0 btn-transition active:scale-95'
|
||||
weight='fill'
|
||||
/>
|
||||
) : (
|
||||
<Circle className='h-7.5 w-7.5 shrink-0 btn-transition active:scale-95' />
|
||||
)}
|
||||
{isEditing && (
|
||||
<Text
|
||||
variant='p'
|
||||
@@ -150,7 +185,11 @@ export default function ScheduleListItem({ schedule, dragHandleProps, onClick }:
|
||||
/>
|
||||
)}
|
||||
{!isEditing && (
|
||||
<Text variant='p' className='flex-1 truncate' onDoubleClick={() => setIsEditing(true)}>
|
||||
<Text
|
||||
variant='p'
|
||||
className='flex-1 select-none truncate'
|
||||
onDoubleClick={() => setIsEditing(true)}
|
||||
>
|
||||
{schedule.name}
|
||||
</Text>
|
||||
)}
|
||||
@@ -158,14 +197,14 @@ export default function ScheduleListItem({ schedule, dragHandleProps, onClick }:
|
||||
<DialogProvider>
|
||||
<Menu>
|
||||
<MenuButton className='invisible h-fit bg-transparent p-0 text-ut-gray btn-transition data-[open]:visible group-hover:visible'>
|
||||
<MoreActionsIcon className='h-6 w-6' />
|
||||
<DotsThree weight='bold' className='h-6 w-6' />
|
||||
</MenuButton>
|
||||
|
||||
<MenuItems
|
||||
as={ExtensionRootWrapper}
|
||||
className={clsx([
|
||||
styleResetClass,
|
||||
'w-30 cursor-pointer origin-top-right rounded bg-white p-1 text-black shadow-lg transition border border-ut-offwhite focus:outline-none',
|
||||
'w-fit cursor-pointer origin-top-right rounded bg-white p-1 text-black shadow-lg transition border border-ut-offwhite/50 focus:outline-none',
|
||||
'data-[closed]:(opacity-0 scale-95)',
|
||||
'data-[enter]:(ease-out-expo duration-150)',
|
||||
'data-[leave]:(ease-out duration-50)',
|
||||
@@ -174,40 +213,46 @@ export default function ScheduleListItem({ schedule, dragHandleProps, onClick }:
|
||||
anchor='bottom end'
|
||||
>
|
||||
<MenuItem>
|
||||
<Text
|
||||
as='button'
|
||||
variant='small'
|
||||
<Button
|
||||
className='w-full flex justify-start'
|
||||
onClick={() => setIsEditing(true)}
|
||||
className='w-full rounded bg-transparent p-2 text-left data-[focus]:bg-gray-200/40'
|
||||
color='ut-black'
|
||||
size='small'
|
||||
variant='minimal'
|
||||
icon={PencilSimpleLine}
|
||||
>
|
||||
Rename
|
||||
</Text>
|
||||
</Button>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<Text
|
||||
as='button'
|
||||
variant='small'
|
||||
<Button
|
||||
className='w-full flex justify-start'
|
||||
onClick={() => handleDuplicateSchedule(schedule.id)}
|
||||
className='w-full rounded bg-transparent p-2 text-left data-[focus]:bg-gray-200/40'
|
||||
color='ut-black'
|
||||
size='small'
|
||||
variant='minimal'
|
||||
icon={CopySimple}
|
||||
>
|
||||
Duplicate
|
||||
</Text>
|
||||
</Button>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<Text
|
||||
as='button'
|
||||
variant='small'
|
||||
<Button
|
||||
className='w-full flex justify-start'
|
||||
onClick={handleDelete}
|
||||
className='w-full rounded bg-transparent p-2 text-left text-theme-red data-[focus]:bg-red-200/40'
|
||||
color='theme-red'
|
||||
size='small'
|
||||
variant='minimal'
|
||||
icon={Trash}
|
||||
>
|
||||
Delete
|
||||
</Text>
|
||||
Delete Schedule
|
||||
</Button>
|
||||
</MenuItem>
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
</DialogProvider>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,10 @@ export interface ScheduleTotalHoursAndCoursesProps {
|
||||
/**
|
||||
* The ScheduleTotalHoursAndCourses as per the Labels and Details Figma section
|
||||
*
|
||||
* @param props ScheduleTotalHoursAndCoursesProps
|
||||
* @param scheduleName - The name of the schedule.
|
||||
* @param totalHours - The total number of hours.
|
||||
* @param totalCourses - The total number of courses.
|
||||
* @returns The rendered ScheduleTotalHoursAndCourses component.
|
||||
*/
|
||||
export default function ScheduleTotalHoursAndCourses({
|
||||
scheduleName,
|
||||
@@ -21,14 +24,24 @@ export default function ScheduleTotalHoursAndCourses({
|
||||
totalCourses,
|
||||
}: ScheduleTotalHoursAndCoursesProps): JSX.Element {
|
||||
return (
|
||||
<div className='min-w-full w-0 flex items-center gap-2.5 whitespace-nowrap'>
|
||||
<Text className='truncate text-ut-burntorange uppercase' variant='h1' as='span'>
|
||||
{`${scheduleName}: `}
|
||||
</Text>
|
||||
<Text variant='h3' as='div' className='flex flex-row items-center gap-2 text-theme-black'>
|
||||
{totalHours} {totalHours === 1 ? 'Hour' : 'Hours'}
|
||||
<Text variant='h4' as='span' className='hidden capitalize screenshot:inline sm:inline'>
|
||||
{totalCourses} {totalCourses === 1 ? 'Course' : 'Courses'}
|
||||
<div className='w-full flex flex-col items-start'>
|
||||
<div className='max-w-full overflow-hidden'>
|
||||
<Text className='block w-full truncate text-ut-burntorange' variant='h1' as='span'>
|
||||
{scheduleName}
|
||||
</Text>
|
||||
</div>
|
||||
<Text variant='h3' as='div' className='flex flex-row items-center gap-2.5 text-theme-black'>
|
||||
<Text variant='h4' as='span' className='inline text-theme-black'>
|
||||
{totalHours}
|
||||
<Text variant='h3' as='span' className='inline text-theme-black font-all-small-caps!'>
|
||||
{totalHours === 1 ? 'Hour' : 'Hours'}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text variant='h4' as='span' className='inline text-theme-black'>
|
||||
{totalCourses}
|
||||
<Text variant='h3' as='span' className='inline text-theme-black font-all-small-caps!'>
|
||||
{totalCourses === 1 ? 'Course' : 'Courses'}
|
||||
</Text>
|
||||
</Text>
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
21
src/views/components/common/SortableItemOverlay.tsx
Normal file
21
src/views/components/common/SortableItemOverlay.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { DropAnimation } from '@dnd-kit/core';
|
||||
import { defaultDropAnimationSideEffects, DragOverlay } from '@dnd-kit/core';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
const dropAnimationConfig: DropAnimation = {
|
||||
sideEffects: defaultDropAnimationSideEffects({
|
||||
styles: {
|
||||
active: {
|
||||
visibility: 'hidden',
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns Renders a visibly hidden sortable item in the sortable list while it is being dragged
|
||||
*/
|
||||
export function SortableItemOverlay({ children }: PropsWithChildren) {
|
||||
return <DragOverlay dropAnimation={dropAnimationConfig}>{children}</DragOverlay>;
|
||||
}
|
||||
149
src/views/components/common/SortableList.tsx
Normal file
149
src/views/components/common/SortableList.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
import type { Active, UniqueIdentifier } from '@dnd-kit/core';
|
||||
import { DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
|
||||
import { restrictToParentElement } from '@dnd-kit/modifiers';
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
useSortable,
|
||||
verticalListSortingStrategy,
|
||||
} from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { SortableItemProvider } from '@views/contexts/SortableItemContext';
|
||||
import { useCursor } from '@views/hooks/useCursor';
|
||||
import clsx from 'clsx';
|
||||
import type { CSSProperties, PropsWithChildren, ReactNode } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { SortableItemOverlay } from './SortableItemOverlay';
|
||||
|
||||
/**
|
||||
* Extendable Prop for Sortable Item Id
|
||||
*/
|
||||
export interface BaseItem {
|
||||
id: UniqueIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the SortableList component.
|
||||
*/
|
||||
export interface SortableListProps<T extends BaseItem> {
|
||||
draggables: T[];
|
||||
/**
|
||||
*
|
||||
* @param items - Handles the behavior of the list when items change order
|
||||
*/
|
||||
onChange(items: T[]): void;
|
||||
/**
|
||||
*
|
||||
* @param item - Renders a sortable item component
|
||||
*/
|
||||
renderItem(item: T): ReactNode;
|
||||
className?: string; // Impacts the spacing between items in the list
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the SortableListItem Component
|
||||
*/
|
||||
export interface SortableListItemProps {
|
||||
id: UniqueIdentifier;
|
||||
}
|
||||
|
||||
function SortableListItem({ children, id }: PropsWithChildren<SortableListItemProps>) {
|
||||
const { attributes, isDragging, listeners, setNodeRef, setActivatorNodeRef, transform, transition } = useSortable({
|
||||
id,
|
||||
});
|
||||
const context = useMemo(
|
||||
() => ({
|
||||
attributes,
|
||||
listeners,
|
||||
ref: setActivatorNodeRef,
|
||||
}),
|
||||
[attributes, listeners, setActivatorNodeRef]
|
||||
);
|
||||
|
||||
const style = {
|
||||
listStyle: 'none',
|
||||
visibility: isDragging ? 'hidden' : 'visible',
|
||||
transform: CSS.Translate.toString(transform),
|
||||
transition,
|
||||
} satisfies CSSProperties;
|
||||
|
||||
return (
|
||||
<SortableItemProvider value={context}>
|
||||
<li ref={setNodeRef} style={style}>
|
||||
{children}
|
||||
</li>
|
||||
</SortableItemProvider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* SortableList is a component that renders a drag-and-drop sortable list restricted by its parent
|
||||
*/
|
||||
export function SortableList<T extends BaseItem>({
|
||||
draggables,
|
||||
renderItem,
|
||||
onChange,
|
||||
className,
|
||||
}: SortableListProps<T>): JSX.Element {
|
||||
const [active, setActive] = useState<Active | null>(null);
|
||||
const [items, setItems] = useState<T[]>(draggables);
|
||||
const { setCursorGrabbing } = useCursor();
|
||||
|
||||
useEffect(() => {
|
||||
setItems(draggables);
|
||||
}, [draggables]);
|
||||
|
||||
const activeItem = useMemo(() => items.find(item => item.id === active?.id), [active, items]);
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={clsx('h-full w-full')}>
|
||||
<ul className={clsx('overflow-clip flex gap-spacing-3 flex-col', className)}>
|
||||
<DndContext
|
||||
modifiers={[restrictToParentElement]}
|
||||
sensors={sensors}
|
||||
onDragStart={({ active }) => {
|
||||
setCursorGrabbing(true);
|
||||
setActive(active);
|
||||
}}
|
||||
onDragEnd={({ active, over }) => {
|
||||
setCursorGrabbing(false);
|
||||
if (over && active.id !== over.id) {
|
||||
const activeIndex = items.findIndex(({ id }) => id === active.id);
|
||||
const overIndex = items.findIndex(({ id }) => id === over.id);
|
||||
const reorderedItems = arrayMove(items, activeIndex, overIndex);
|
||||
onChange(reorderedItems);
|
||||
setItems(reorderedItems);
|
||||
}
|
||||
setActive(null);
|
||||
}}
|
||||
onDragCancel={() => {
|
||||
setCursorGrabbing(false);
|
||||
setActive(null);
|
||||
}}
|
||||
>
|
||||
<SortableContext items={items} strategy={verticalListSortingStrategy}>
|
||||
{items.map(item => (
|
||||
<SortableListItem key={item.id} id={item.id}>
|
||||
{renderItem(item)}
|
||||
</SortableListItem>
|
||||
))}
|
||||
</SortableContext>
|
||||
<SortableItemOverlay>
|
||||
{activeItem ? (
|
||||
<SortableListItem id={activeItem.id}>{renderItem(activeItem)}</SortableListItem>
|
||||
) : null}
|
||||
</SortableItemOverlay>
|
||||
</DndContext>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
src/views/components/common/SortableListDragHandle.tsx
Normal file
22
src/views/components/common/SortableListDragHandle.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { useSortableItemContext } from '@views/contexts/SortableItemContext';
|
||||
import type { CSSProperties } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
interface SortableListDragHandleProps {
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns A wrapper component around sortable list item drag handles
|
||||
*/
|
||||
export function SortableListDragHandle({ className, style, children }: SortableListDragHandleProps) {
|
||||
const { attributes, listeners, ref } = useSortableItemContext();
|
||||
|
||||
return (
|
||||
<div className={className} {...attributes} {...listeners} ref={ref} style={style}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -9,11 +9,9 @@ type ToggleSwitchProps = {
|
||||
/**
|
||||
* A custom switch button component.
|
||||
*
|
||||
* @component
|
||||
* @param {Object} props - The component props.
|
||||
* @param {boolean} [props.isChecked=true] - The initial checked state of the switch button.
|
||||
* @param {Function} props.onChange - The callback function to be called when the switch button is toggled.
|
||||
* @returns {JSX.Element} The rendered SwitchButton component.
|
||||
* @param isChecked - The initial checked state of the switch button.
|
||||
* @param onChange - The callback function to be called when the switch button is toggled.
|
||||
* @returns The rendered SwitchButton component.
|
||||
*/
|
||||
const SwitchButton = ({ isChecked = true, onChange }: ToggleSwitchProps): JSX.Element => {
|
||||
const [enabled, setEnabled] = useState(isChecked);
|
||||
|
||||
@@ -34,13 +34,12 @@
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0;
|
||||
line-height: 100%; /* 0.6875rem */
|
||||
line-height: 0.75rem;
|
||||
}
|
||||
|
||||
.h3 {
|
||||
font-size: 1.26563rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
@@ -48,7 +47,6 @@
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.03125em;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.h2 {
|
||||
@@ -58,15 +56,13 @@
|
||||
}
|
||||
|
||||
.h1-course {
|
||||
font-size: 1rem;
|
||||
font-weight: 550;
|
||||
text-transform: capitalize;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.h1 {
|
||||
font-size: 1.60188rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user