Compare commits
3 Commits
v2.3.0
...
copilot/up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec63b5e903 | ||
|
|
3ec42bf207 | ||
|
|
c4193d52d3 |
@@ -8,5 +8,5 @@ trim_trailing_whitespace = true
|
|||||||
indent_size = 4
|
indent_size = 4
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
|
||||||
[*.{nix,yaml,yml}]
|
[*.nix]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (http://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/Release
|
|
||||||
|
|
||||||
# Dependency directories and management
|
|
||||||
node_modules/
|
|
||||||
jspm_packages/
|
|
||||||
package.json
|
|
||||||
package-lock.json
|
|
||||||
|
|
||||||
# TypeScript v1 declaration files
|
|
||||||
typings/
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# next.js build output
|
|
||||||
.next
|
|
||||||
|
|
||||||
# Webpack-built output
|
|
||||||
/dist
|
|
||||||
|
|
||||||
# Extension archives
|
|
||||||
/build
|
|
||||||
|
|
||||||
# VsCode
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
|
|
||||||
# macOS
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# Terraform
|
|
||||||
.terraform
|
|
||||||
|
|
||||||
# development dependencies
|
|
||||||
.dev/vue-devtools
|
|
||||||
.dev/browser-profiles
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
.idea
|
|
||||||
|
|
||||||
# Sylelint IntelliJ Plugin Requirement
|
|
||||||
.stylelintrc.json
|
|
||||||
|
|
||||||
# Local environment settings
|
|
||||||
.env.local
|
|
||||||
|
|
||||||
*.svg
|
|
||||||
|
|
||||||
config
|
|
||||||
.eslintrc.js
|
|
||||||
!.storybook
|
|
||||||
232
.eslintrc.cjs
232
.eslintrc.cjs
@@ -1,232 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
es6: true,
|
|
||||||
node: true,
|
|
||||||
webextensions: true,
|
|
||||||
},
|
|
||||||
ignorePatterns: ['*.html', 'tsconfig.json'],
|
|
||||||
extends: [
|
|
||||||
'eslint:recommended',
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'plugin:react/recommended',
|
|
||||||
'plugin:react-hooks/recommended',
|
|
||||||
'plugin:storybook/recommended',
|
|
||||||
'airbnb-base',
|
|
||||||
'airbnb/rules/react',
|
|
||||||
'airbnb-typescript',
|
|
||||||
'@unocss',
|
|
||||||
'prettier',
|
|
||||||
],
|
|
||||||
plugins: [
|
|
||||||
'import',
|
|
||||||
'import-essentials',
|
|
||||||
'jsdoc',
|
|
||||||
'eslint-plugin-tsdoc',
|
|
||||||
'react-prefer-function-component',
|
|
||||||
'@typescript-eslint',
|
|
||||||
'simple-import-sort',
|
|
||||||
],
|
|
||||||
globals: {
|
|
||||||
Atomics: 'readonly',
|
|
||||||
SharedArrayBuffer: 'readonly',
|
|
||||||
debugger: true,
|
|
||||||
browser: true,
|
|
||||||
context: true,
|
|
||||||
JSX: true,
|
|
||||||
},
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
parserOptions: {
|
|
||||||
project: './tsconfig.json',
|
|
||||||
ecmaVersion: 2022,
|
|
||||||
sourceType: 'module',
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
modules: true,
|
|
||||||
experimentalObjectRestSpread: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: 'detect',
|
|
||||||
},
|
|
||||||
jsdoc: {
|
|
||||||
mode: 'typescript',
|
|
||||||
},
|
|
||||||
'import/parsers': {
|
|
||||||
'@typescript-eslint/parser': ['.ts', '.tsx'],
|
|
||||||
},
|
|
||||||
'import/resolver': {
|
|
||||||
typescript: {
|
|
||||||
alwaysTryTypes: true,
|
|
||||||
project: './tsconfig.json',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'prefer-const': [
|
|
||||||
'off',
|
|
||||||
{
|
|
||||||
destructuring: 'any',
|
|
||||||
ignoreReadBeforeAssign: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'no-plusplus': 'off',
|
|
||||||
'no-inner-declarations': 'off',
|
|
||||||
'sort-imports': 'off',
|
|
||||||
'no-case-declarations': 'off',
|
|
||||||
'no-unreachable': 'warn',
|
|
||||||
'no-constant-condition': 'error',
|
|
||||||
'space-before-function-paren': 'off',
|
|
||||||
'no-undef': 'off',
|
|
||||||
'no-return-await': 'off',
|
|
||||||
'@typescript-eslint/return-await': 'off',
|
|
||||||
'@typescript-eslint/no-shadow': ['off'],
|
|
||||||
'@typescript-eslint/no-use-before-define': ['off'],
|
|
||||||
'class-methods-use-this': 'off',
|
|
||||||
'react-hooks/exhaustive-deps': 'warn',
|
|
||||||
'@typescript-eslint/lines-between-class-members': 'off',
|
|
||||||
'no-param-reassign': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
props: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'no-console': 'off',
|
|
||||||
'consistent-return': 'off',
|
|
||||||
'react/destructuring-assignment': 'off',
|
|
||||||
'import/prefer-default-export': 'off',
|
|
||||||
'no-promise-executor-return': 'off',
|
|
||||||
'import/no-cycle': 'off',
|
|
||||||
'import/no-extraneous-dependencies': 'off',
|
|
||||||
'react/jsx-props-no-spreading': 'off',
|
|
||||||
'react/jsx-no-useless-fragment': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
allowExpressions: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'keyword-spacing': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
before: true,
|
|
||||||
after: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'no-continue': 'off',
|
|
||||||
'space-before-blocks': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
functions: 'always',
|
|
||||||
keywords: 'always',
|
|
||||||
classes: 'always',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'react/jsx-filename-extension': [
|
|
||||||
1,
|
|
||||||
{
|
|
||||||
extensions: ['.tsx'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'react/no-deprecated': 'warn',
|
|
||||||
'react/prop-types': 'off',
|
|
||||||
'react-prefer-function-component/react-prefer-function-component': [
|
|
||||||
'warn',
|
|
||||||
{
|
|
||||||
allowComponentDidCatch: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'react/function-component-definition': 'off',
|
|
||||||
'react/button-has-type': 'off',
|
|
||||||
'jsdoc/require-param-type': 'off',
|
|
||||||
'jsdoc/require-returns-type': 'off',
|
|
||||||
'jsdoc/newline-after-description': 'off',
|
|
||||||
'react/require-default-props': 'off',
|
|
||||||
'jsdoc/require-jsdoc': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
enableFixer: false,
|
|
||||||
publicOnly: true,
|
|
||||||
checkConstructors: false,
|
|
||||||
require: {
|
|
||||||
ArrowFunctionExpression: true,
|
|
||||||
ClassDeclaration: true,
|
|
||||||
ClassExpression: true,
|
|
||||||
FunctionExpression: true,
|
|
||||||
},
|
|
||||||
contexts: [
|
|
||||||
'MethodDefinition:not([key.name="componentDidMount"]):not([key.name="render"])',
|
|
||||||
'ArrowFunctionExpression',
|
|
||||||
'ClassDeclaration',
|
|
||||||
'ClassExpression',
|
|
||||||
'ClassProperty:not([key.name="state"]):not([key.name="componentDidMount"])',
|
|
||||||
'FunctionDeclaration',
|
|
||||||
'FunctionExpression',
|
|
||||||
'TSDeclareFunction',
|
|
||||||
'TSEnumDeclaration',
|
|
||||||
'TSInterfaceDeclaration',
|
|
||||||
'TSMethodSignature',
|
|
||||||
'TSModuleDeclaration',
|
|
||||||
'TSTypeAliasDeclaration',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'tsdoc/syntax': 'error',
|
|
||||||
'@typescript-eslint/no-explicit-any': 'error',
|
|
||||||
'@typescript-eslint/no-unused-vars': [
|
|
||||||
'warn',
|
|
||||||
{
|
|
||||||
argsIgnorePattern: '^_',
|
|
||||||
varsIgnorePattern: '^_',
|
|
||||||
caughtErrorsIgnorePattern: '^_',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'@typescript-eslint/naming-convention': 'off',
|
|
||||||
'@typescript-eslint/space-before-function-paren': 'off',
|
|
||||||
'@typescript-eslint/ban-ts-comment': 'off',
|
|
||||||
'@typescript-eslint/no-empty-interface': 'warn',
|
|
||||||
'import/no-restricted-paths': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
zones: [
|
|
||||||
{
|
|
||||||
target: './src/background',
|
|
||||||
from: './src/views',
|
|
||||||
message:
|
|
||||||
'You cannot import into the `background` directory from the `views` directory (i.e. content script files) because it will break the build!',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
target: './src/views',
|
|
||||||
from: './src/background',
|
|
||||||
message:
|
|
||||||
'You cannot import into the `views` directory from the `background` directory (i.e. background script files) because it will break the build!',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
target: './src/shared',
|
|
||||||
from: './',
|
|
||||||
except: ['./src/shared', './node_modules'],
|
|
||||||
message: 'You cannot import into `shared` from an external directory.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'import/extensions': 'off',
|
|
||||||
'no-restricted-syntax': [
|
|
||||||
'error',
|
|
||||||
'ForInStatement',
|
|
||||||
'LabeledStatement',
|
|
||||||
'WithStatement',
|
|
||||||
{
|
|
||||||
selector: 'TSEnumDeclaration',
|
|
||||||
message: "Don't declare enums",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'@typescript-eslint/consistent-type-exports': 'error',
|
|
||||||
'@typescript-eslint/consistent-type-imports': 'error',
|
|
||||||
'simple-import-sort/imports': 'error',
|
|
||||||
'simple-import-sort/exports': 'error',
|
|
||||||
'import-essentials/restrict-import-depth': 'error',
|
|
||||||
'import-essentials/check-path-alias': 'error',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
7
.github/ISSUE_TEMPLATE/bug_report.md
vendored
7
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,9 +1,10 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
title: '[BUG] '
|
title: "[BUG] "
|
||||||
labels: ''
|
labels: ''
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Pre-submission Checklist**
|
**Pre-submission Checklist**
|
||||||
@@ -29,11 +30,11 @@ assignees: ''
|
|||||||
|
|
||||||
**Expected Behavior**
|
**Expected Behavior**
|
||||||
|
|
||||||
<!-- What you expected to happen -->
|
<!-- A clear and concise description of what you expected to happen -->
|
||||||
|
|
||||||
**Current Behavior**
|
**Current Behavior**
|
||||||
|
|
||||||
<!-- What actually happened -->
|
<!-- A clear and concise description of what actually happened -->
|
||||||
|
|
||||||
**Screenshots**
|
**Screenshots**
|
||||||
|
|
||||||
|
|||||||
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,9 +1,10 @@
|
|||||||
---
|
---
|
||||||
name: Feature Request
|
name: Feature Request
|
||||||
about: Suggest an idea for this project
|
about: Suggest an idea for this project
|
||||||
title: '[FEATURE] '
|
title: "[FEATURE] "
|
||||||
labels: feature
|
labels: feature
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Pre-submission Checklist**
|
**Pre-submission Checklist**
|
||||||
@@ -16,14 +17,18 @@ assignees: ''
|
|||||||
- [ ] I have reviewed the documentation to confirm this feature doesn't exist
|
- [ ] I have reviewed the documentation to confirm this feature doesn't exist
|
||||||
- [ ] I have completed all sections below with detailed information
|
- [ ] I have completed all sections below with detailed information
|
||||||
|
|
||||||
**Your Idea**
|
**Feature Description**
|
||||||
|
|
||||||
<!-- A clear and concise description of the feature you'd like to see, and how it would work -->
|
<!-- 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**
|
**UI/UX Considerations**
|
||||||
|
|
||||||
<!-- If this feature involves UI changes (aka how it looks), please describe the visual aspects -->
|
<!-- If this feature involves UI changes, please describe the visual aspects -->
|
||||||
|
|
||||||
**Other**
|
**Technical Implementation Details**
|
||||||
|
|
||||||
<!-- Any other comments you have can go here! -->
|
<!-- If you have specific technical suggestions, list them here -->
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ about: Updating Build Dependencies
|
|||||||
title: ''
|
title: ''
|
||||||
labels: build, dependencies
|
labels: build, dependencies
|
||||||
assignees: doprz, Razboy20
|
assignees: doprz, Razboy20
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] Updated Nix Flake
|
- [ ] Updated Nix Flake
|
||||||
|
|||||||
1
.github/dependabot.yml
vendored
1
.github/dependabot.yml
vendored
@@ -15,6 +15,7 @@ updates:
|
|||||||
major-updates:
|
major-updates:
|
||||||
update-types:
|
update-types:
|
||||||
- 'major'
|
- 'major'
|
||||||
|
|
||||||
ignore:
|
ignore:
|
||||||
- dependency-name: '@crxjs/vite-plugin'
|
- dependency-name: '@crxjs/vite-plugin'
|
||||||
- dependency-name: '@unocss/vite'
|
- dependency-name: '@unocss/vite'
|
||||||
|
|||||||
10
.github/workflows/best-practices.yml
vendored
10
.github/workflows/best-practices.yml
vendored
@@ -1,33 +1,43 @@
|
|||||||
name: Best Practices
|
name: Best Practices
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Run ESLint
|
- name: Run ESLint
|
||||||
run: pnpm run lint
|
run: pnpm run lint
|
||||||
format:
|
format:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Run Prettier
|
- name: Run Prettier
|
||||||
run: pnpm run prettier
|
run: pnpm run prettier
|
||||||
|
|||||||
6
.github/workflows/check-types.yml
vendored
6
.github/workflows/check-types.yml
vendored
@@ -1,18 +1,24 @@
|
|||||||
name: Type Check
|
name: Type Check
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
type-check:
|
type-check:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: pnpm run check-types
|
run: pnpm run check-types
|
||||||
|
|||||||
4
.github/workflows/chromatic.yml
vendored
4
.github/workflows/chromatic.yml
vendored
@@ -1,5 +1,7 @@
|
|||||||
name: 'Chromatic'
|
name: 'Chromatic'
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
chromatic:
|
chromatic:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -12,8 +14,10 @@ jobs:
|
|||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Publish to Chromatic
|
- name: Publish to Chromatic
|
||||||
uses: chromaui/action@latest
|
uses: chromaui/action@latest
|
||||||
with:
|
with:
|
||||||
|
|||||||
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -15,6 +15,7 @@ jobs:
|
|||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
- name: Get file permission
|
- name: Get file permission
|
||||||
run: chmod -R 777 .
|
run: chmod -R 777 .
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: Release with semantic-release
|
- name: Release with semantic-release
|
||||||
|
|||||||
6
.github/workflows/tests.yml
vendored
6
.github/workflows/tests.yml
vendored
@@ -1,18 +1,24 @@
|
|||||||
name: Tests
|
name: Tests
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: pnpm test
|
run: pnpm test
|
||||||
|
|||||||
3
.github/workflows/validate-pr.yml
vendored
3
.github/workflows/validate-pr.yml
vendored
@@ -1,6 +1,8 @@
|
|||||||
name: Validate PR Title
|
name: Validate PR Title
|
||||||
|
|
||||||
# thank you ben limmer for this workflow:
|
# thank you ben limmer for this workflow:
|
||||||
# https://github.com/blimmer/semantic-release-demo-2/blob/main/.github/workflows/lint-pr.yml
|
# https://github.com/blimmer/semantic-release-demo-2/blob/main/.github/workflows/lint-pr.yml
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types:
|
types:
|
||||||
@@ -8,6 +10,7 @@ on:
|
|||||||
- reopened
|
- reopened
|
||||||
- edited
|
- edited
|
||||||
- synchronize
|
- synchronize
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -212,9 +212,4 @@ package-lock.json
|
|||||||
storybook-static/
|
storybook-static/
|
||||||
package/
|
package/
|
||||||
|
|
||||||
# Nix
|
.direnv/
|
||||||
result
|
|
||||||
result-*
|
|
||||||
|
|
||||||
# direnv
|
|
||||||
.direnv
|
|
||||||
|
|||||||
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
@@ -6,9 +6,13 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Run current script",
|
"name": "Run current script",
|
||||||
"runtimeExecutable": "npx",
|
"runtimeExecutable": "npx",
|
||||||
"runtimeArgs": ["tsx"],
|
"runtimeArgs": [
|
||||||
|
"tsx"
|
||||||
|
],
|
||||||
"program": "${file}",
|
"program": "${file}",
|
||||||
"skipFiles": ["<node_internals>/**"]
|
"skipFiles": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
],
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -26,7 +26,7 @@
|
|||||||
"navigation": "Routes",
|
"navigation": "Routes",
|
||||||
"logging": "log",
|
"logging": "log",
|
||||||
"popup": "Layout",
|
"popup": "Layout",
|
||||||
"storage": "Database"
|
"storage": "Database",
|
||||||
},
|
},
|
||||||
"material-icon-theme.files.associations": {
|
"material-icon-theme.files.associations": {
|
||||||
"tsconfig.extension.json": "tsconfig",
|
"tsconfig.extension.json": "tsconfig",
|
||||||
@@ -36,5 +36,5 @@
|
|||||||
"[html]": {
|
"[html]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"typescript.tsdk": "node_modules/typescript/lib"
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
}
|
}
|
||||||
|
|||||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,18 +1,3 @@
|
|||||||
## [2.3.0](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/v2.2.2...v2.3.0) (2026-01-07)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add drag-and-drop import for schedules ([#661](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/661)) ([549c52a](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/549c52a39fee718f2bb07cfce33a294835a2246b)), closes [#446](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/446)
|
|
||||||
* allow bypassing the 10-schedule limit ([#675](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/675)) ([6a67a32](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6a67a32e4f50a5bdd20aa43789f199b822483e2d))
|
|
||||||
* condense resourceLinks course schedule ([#676](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/676)) ([cee5f02](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/cee5f0284f09f39ca5ae64559d0b697646c77e74))
|
|
||||||
* LHD birthday ([#717](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/717)) ([2d18553](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2d18553f98c5146fa18699ae20462e7dcbc9d35c))
|
|
||||||
* **nix:** add prettier-version-match check ([#713](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/713)) ([8ccf7fb](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8ccf7fb37e769ba445f39c140ca9c1c4245cc1c1))
|
|
||||||
* **nix:** build UTRP ([#714](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/714)) ([38bb29b](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/38bb29b20b97ed3cf8fd6511df16553fed1d58bb))
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* .editorconfig syntax for nix files ([b406d4d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b406d4dd244a25688c2b9621cf5d441228bd8913))
|
|
||||||
* toSorted outdated chrome bug ([#694](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/694)) ([4f5d8c6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4f5d8c6d20e3cfeb7b62520ba1819e297d2cc60f))
|
|
||||||
## [2.2.2](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/v2.2.1...v2.2.2) (2025-10-13)
|
## [2.2.2](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/v2.2.1...v2.2.2) (2025-10-13)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
@@ -21,7 +6,6 @@
|
|||||||
* automatically select new or duplicated schedules ([#583](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/583)) ([#589](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/589)) ([2a50f55](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2a50f5580d3dbeb0d66546c23cf29bbb37d80da2))
|
* automatically select new or duplicated schedules ([#583](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/583)) ([#589](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/589)) ([2a50f55](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2a50f5580d3dbeb0d66546c23cf29bbb37d80da2))
|
||||||
* **env:** add SENTRY env vars ([8f7e1bc](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8f7e1bc0af6336549068e02b80df21d4e8f4ef9c))
|
* **env:** add SENTRY env vars ([8f7e1bc](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8f7e1bc0af6336549068e02b80df21d4e8f4ef9c))
|
||||||
* export schedule button add to calendar ([#594](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/594)) ([5994ded](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5994ded8be876cb55174d27d3fdb0832b21a0ff9))
|
* export schedule button add to calendar ([#594](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/594)) ([5994ded](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/5994ded8be876cb55174d27d3fdb0832b21a0ff9))
|
||||||
* **release:** v2.2.2 ([c21cbd7](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/c21cbd77f0764c03a711589ff4f957cb8c936eec))
|
|
||||||
* search result shading ([#617](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/617)) ([be861b8](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/be861b823cb2cb7f6f4a1f266351eec3fc1c2f99))
|
* search result shading ([#617](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/617)) ([be861b8](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/be861b823cb2cb7f6f4a1f266351eec3fc1c2f99))
|
||||||
* show warning for courses of different semesters ([#570](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/570)) ([2e7dac1](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2e7dac1e3eba757231ac07ac966231c08c703a16))
|
* show warning for courses of different semesters ([#570](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/570)) ([2e7dac1](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2e7dac1e3eba757231ac07ac966231c08c703a16))
|
||||||
* support summer grades, fix summer course parser ([#596](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/596)) ([2d92dd4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2d92dd47f00a44b7d48e92a8ffba94480e4e73f9))
|
* support summer grades, fix summer course parser ([#596](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/596)) ([2d92dd4](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2d92dd47f00a44b7d48e92a8ffba94480e4e73f9))
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ Special thanks to the developers and contributors behind these amazing tools and
|
|||||||
|
|
||||||
## Activity
|
## Activity
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
|
|||||||
10
default.nix
10
default.nix
@@ -1,10 +0,0 @@
|
|||||||
(import (
|
|
||||||
let
|
|
||||||
rev = "v1.1.0";
|
|
||||||
sha256 = "sha256:19d2z6xsvpxm184m41qrpi1bplilwipgnzv9jy17fgw421785q1m";
|
|
||||||
in
|
|
||||||
fetchTarball {
|
|
||||||
inherit sha256;
|
|
||||||
url = "https://github.com/NixOS/flake-compat/archive/${rev}.tar.gz";
|
|
||||||
}
|
|
||||||
) { src = ./.; }).defaultNix
|
|
||||||
@@ -24,7 +24,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate the mode
|
# Validate the mode
|
||||||
if [[ ! " ${SUPPORTED_MODES[*]} " =~ ${mode} ]]; then
|
if [[ ! " ${SUPPORTED_MODES[*]} " =~ " ${mode} " ]]; then
|
||||||
echo "Error: Invalid mode '${mode}'" >&2
|
echo "Error: Invalid mode '${mode}'" >&2
|
||||||
usage
|
usage
|
||||||
fi
|
fi
|
||||||
|
|||||||
257
eslint.config.mjs
Normal file
257
eslint.config.mjs
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
import { fixupConfigRules } from '@eslint/compat';
|
||||||
|
import { FlatCompat } from '@eslint/eslintrc';
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import importEssentials from 'eslint-plugin-import-essentials';
|
||||||
|
import jsdoc from 'eslint-plugin-jsdoc';
|
||||||
|
import reactPreferFunction from 'eslint-plugin-react-prefer-function-component';
|
||||||
|
import simpleImportSort from 'eslint-plugin-simple-import-sort';
|
||||||
|
import tsdoc from 'eslint-plugin-tsdoc';
|
||||||
|
import unocss from '@unocss/eslint-config/flat';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
ignores: ['*.html', 'tsconfig.json', 'dist/**', 'build/**', 'node_modules/**'],
|
||||||
|
},
|
||||||
|
js.configs.recommended,
|
||||||
|
...fixupConfigRules(
|
||||||
|
compat.extends(
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
'plugin:storybook/recommended',
|
||||||
|
'airbnb-base',
|
||||||
|
'airbnb/rules/react',
|
||||||
|
'airbnb-typescript',
|
||||||
|
'prettier'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
unocss,
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
'import-essentials': importEssentials,
|
||||||
|
jsdoc,
|
||||||
|
tsdoc,
|
||||||
|
'react-prefer-function-component': reactPreferFunction,
|
||||||
|
'simple-import-sort': simpleImportSort,
|
||||||
|
},
|
||||||
|
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
Atomics: 'readonly',
|
||||||
|
SharedArrayBuffer: 'readonly',
|
||||||
|
debugger: true,
|
||||||
|
browser: true,
|
||||||
|
context: true,
|
||||||
|
JSX: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
ecmaVersion: 2022,
|
||||||
|
sourceType: 'module',
|
||||||
|
|
||||||
|
parserOptions: {
|
||||||
|
project: './tsconfig.json',
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
modules: true,
|
||||||
|
experimentalObjectRestSpread: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
jsdoc: {
|
||||||
|
mode: 'typescript',
|
||||||
|
},
|
||||||
|
'import/parsers': {
|
||||||
|
'@typescript-eslint/parser': ['.ts', '.tsx'],
|
||||||
|
},
|
||||||
|
'import/resolver': {
|
||||||
|
typescript: {
|
||||||
|
alwaysTryTypes: true,
|
||||||
|
project: './tsconfig.json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
// Disable rules removed in @typescript-eslint v8
|
||||||
|
'@typescript-eslint/no-throw-literal': 'off',
|
||||||
|
'prefer-const': [
|
||||||
|
'off',
|
||||||
|
{
|
||||||
|
destructuring: 'any',
|
||||||
|
ignoreReadBeforeAssign: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'no-plusplus': 'off',
|
||||||
|
'no-inner-declarations': 'off',
|
||||||
|
'sort-imports': 'off',
|
||||||
|
'no-case-declarations': 'off',
|
||||||
|
'no-unreachable': 'warn',
|
||||||
|
'no-constant-condition': 'error',
|
||||||
|
'space-before-function-paren': 'off',
|
||||||
|
'no-undef': 'off',
|
||||||
|
'no-return-await': 'off',
|
||||||
|
'@typescript-eslint/return-await': 'off',
|
||||||
|
'@typescript-eslint/no-shadow': ['off'],
|
||||||
|
'@typescript-eslint/no-use-before-define': ['off'],
|
||||||
|
'class-methods-use-this': 'off',
|
||||||
|
'react-hooks/exhaustive-deps': 'warn',
|
||||||
|
'@typescript-eslint/lines-between-class-members': 'off',
|
||||||
|
'no-param-reassign': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
props: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'no-console': 'off',
|
||||||
|
'consistent-return': 'off',
|
||||||
|
'react/destructuring-assignment': 'off',
|
||||||
|
'import/prefer-default-export': 'off',
|
||||||
|
'no-promise-executor-return': 'off',
|
||||||
|
'import/no-cycle': 'off',
|
||||||
|
'import/no-extraneous-dependencies': 'off',
|
||||||
|
'react/jsx-props-no-spreading': 'off',
|
||||||
|
'react/jsx-no-useless-fragment': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
allowExpressions: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'keyword-spacing': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
before: true,
|
||||||
|
after: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'no-continue': 'off',
|
||||||
|
'space-before-blocks': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
functions: 'always',
|
||||||
|
keywords: 'always',
|
||||||
|
classes: 'always',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'react/jsx-filename-extension': [
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
extensions: ['.tsx'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'react/no-deprecated': 'warn',
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
'react-prefer-function-component/react-prefer-function-component': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
allowComponentDidCatch: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'react/function-component-definition': 'off',
|
||||||
|
'react/button-has-type': 'off',
|
||||||
|
'jsdoc/require-param-type': 'off',
|
||||||
|
'jsdoc/require-returns-type': 'off',
|
||||||
|
'jsdoc/newline-after-description': 'off',
|
||||||
|
'react/require-default-props': 'off',
|
||||||
|
'jsdoc/require-jsdoc': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
enableFixer: false,
|
||||||
|
publicOnly: true,
|
||||||
|
checkConstructors: false,
|
||||||
|
require: {
|
||||||
|
ArrowFunctionExpression: true,
|
||||||
|
ClassDeclaration: true,
|
||||||
|
ClassExpression: true,
|
||||||
|
FunctionExpression: true,
|
||||||
|
},
|
||||||
|
contexts: [
|
||||||
|
'MethodDefinition:not([key.name="componentDidMount"]):not([key.name="render"])',
|
||||||
|
'ArrowFunctionExpression',
|
||||||
|
'ClassDeclaration',
|
||||||
|
'ClassExpression',
|
||||||
|
'ClassProperty:not([key.name="state"]):not([key.name="componentDidMount"])',
|
||||||
|
'FunctionDeclaration',
|
||||||
|
'FunctionExpression',
|
||||||
|
'TSDeclareFunction',
|
||||||
|
'TSEnumDeclaration',
|
||||||
|
'TSInterfaceDeclaration',
|
||||||
|
'TSMethodSignature',
|
||||||
|
'TSModuleDeclaration',
|
||||||
|
'TSTypeAliasDeclaration',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'tsdoc/syntax': 'error',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'error',
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
caughtErrorsIgnorePattern: '^_',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/naming-convention': 'off',
|
||||||
|
'@typescript-eslint/space-before-function-paren': 'off',
|
||||||
|
'@typescript-eslint/ban-ts-comment': 'off',
|
||||||
|
'@typescript-eslint/no-empty-interface': 'warn',
|
||||||
|
'import/no-restricted-paths': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
zones: [
|
||||||
|
{
|
||||||
|
target: './src/background',
|
||||||
|
from: './src/views',
|
||||||
|
message:
|
||||||
|
'You cannot import into the `background` directory from the `views` directory (i.e. content script files) because it will break the build!',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: './src/views',
|
||||||
|
from: './src/background',
|
||||||
|
message:
|
||||||
|
'You cannot import into the `views` directory from the `background` directory (i.e. background script files) because it will break the build!',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: './src/shared',
|
||||||
|
from: './',
|
||||||
|
except: ['./src/shared', './node_modules'],
|
||||||
|
message: 'You cannot import into `shared` from an external directory.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'import/extensions': 'off',
|
||||||
|
'no-restricted-syntax': [
|
||||||
|
'error',
|
||||||
|
'ForInStatement',
|
||||||
|
'LabeledStatement',
|
||||||
|
'WithStatement',
|
||||||
|
{
|
||||||
|
selector: 'TSEnumDeclaration',
|
||||||
|
message: "Don't declare enums",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/consistent-type-exports': 'error',
|
||||||
|
'@typescript-eslint/consistent-type-imports': 'error',
|
||||||
|
'simple-import-sort/imports': 'error',
|
||||||
|
'simple-import-sort/exports': 'error',
|
||||||
|
'import-essentials/restrict-import-depth': 'error',
|
||||||
|
'import-essentials/check-path-alias': 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
79
flake.lock
generated
79
flake.lock
generated
@@ -1,30 +1,30 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"flake-parts": {
|
"flake-utils": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs-lib": "nixpkgs-lib"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1767609335,
|
"lastModified": 1731533236,
|
||||||
"narHash": "sha256-feveD98mQpptwrAEggBQKJTYbvwwglSbOv53uCfH9PY=",
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
"owner": "hercules-ci",
|
"owner": "numtide",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-utils",
|
||||||
"rev": "250481aafeb741edfe23d29195671c19b36b6dca",
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "hercules-ci",
|
"owner": "numtide",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-utils",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1767640445,
|
"lastModified": 1759831965,
|
||||||
"narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=",
|
"narHash": "sha256-vgPm2xjOmKdZ0xKA6yLXPJpjOtQPHfaZDRtH+47XEBo=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "9f0c42f8bc7151b8e7e5840fb3bd454ad850d8c5",
|
"rev": "c9b6fb798541223bbb396d287d16f43520250518",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -34,59 +34,24 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs-lib": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1765674936,
|
|
||||||
"narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "nixpkgs.lib",
|
|
||||||
"rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "nixpkgs.lib",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1761236834,
|
|
||||||
"narHash": "sha256-+pthv6hrL5VLW2UqPdISGuLiUZ6SnAXdd2DdUE+fV2Q=",
|
|
||||||
"owner": "nixos",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "d5faa84122bc0a1fd5d378492efce4e289f8eac1",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nixos",
|
|
||||||
"ref": "nixpkgs-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-parts": "flake-parts",
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs"
|
||||||
"treefmt-nix": "treefmt-nix"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"treefmt-nix": {
|
"systems": {
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": "nixpkgs_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1767468822,
|
"lastModified": 1681028828,
|
||||||
"narHash": "sha256-MpffQxHxmjVKMiQd0Tg2IM/bSjjdQAM+NDcX6yxj7rE=",
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
"owner": "numtide",
|
"owner": "nix-systems",
|
||||||
"repo": "treefmt-nix",
|
"repo": "default",
|
||||||
"rev": "d56486eb9493ad9c4777c65932618e9c2d0468fc",
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "numtide",
|
"owner": "nix-systems",
|
||||||
"repo": "treefmt-nix",
|
"repo": "default",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
54
flake.nix
54
flake.nix
@@ -1,33 +1,43 @@
|
|||||||
{
|
{
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
treefmt-nix.url = "github:numtide/treefmt-nix";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
inputs@{ flake-parts, ... }:
|
|
||||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
|
||||||
systems = inputs.nixpkgs.lib.systems.flakeExposed;
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
./nix/packages.nix
|
|
||||||
./nix/devShells.nix
|
|
||||||
./nix/treefmt.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
perSystem =
|
|
||||||
{ system, ... }:
|
|
||||||
{
|
{
|
||||||
_module.args.pkgs = import inputs.nixpkgs {
|
self,
|
||||||
inherit system;
|
nixpkgs,
|
||||||
overlays = [
|
flake-utils,
|
||||||
(final: prev: {
|
}:
|
||||||
nodejs = prev.nodejs_20; # v20.19.5
|
flake-utils.lib.eachDefaultSystem (
|
||||||
})
|
system:
|
||||||
|
let
|
||||||
|
pkgs = (import nixpkgs { inherit system; });
|
||||||
|
|
||||||
|
commonPackages = with pkgs; [
|
||||||
|
nodejs_20 # v20.19.5
|
||||||
|
pnpm_10 # v10.18.0
|
||||||
];
|
];
|
||||||
config = { };
|
|
||||||
};
|
additionalPackages = with pkgs; [
|
||||||
|
bun
|
||||||
|
nodePackages.conventional-changelog-cli
|
||||||
|
sentry-cli
|
||||||
|
];
|
||||||
|
in
|
||||||
|
{
|
||||||
|
formatter = pkgs.nixfmt-rfc-style;
|
||||||
|
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
name = "utrp-dev";
|
||||||
|
buildInputs = commonPackages;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
devShells.full = pkgs.mkShell {
|
||||||
|
name = "utrp-dev-full";
|
||||||
|
buildInputs = commonPackages ++ additionalPackages;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
perSystem =
|
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
commonPackages = with pkgs; [
|
|
||||||
nodejs # Defined in overlay
|
|
||||||
pnpm_10 # v10.18.2
|
|
||||||
];
|
|
||||||
|
|
||||||
additionalPackages = with pkgs; [
|
|
||||||
bun
|
|
||||||
nodePackages.conventional-changelog-cli
|
|
||||||
sentry-cli
|
|
||||||
];
|
|
||||||
in
|
|
||||||
{
|
|
||||||
devShells.default = pkgs.mkShell {
|
|
||||||
name = "utrp-dev";
|
|
||||||
packages = commonPackages;
|
|
||||||
};
|
|
||||||
|
|
||||||
devShells.full = pkgs.mkShell {
|
|
||||||
name = "utrp-dev-full";
|
|
||||||
packages = commonPackages ++ additionalPackages;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
{
|
|
||||||
stdenv,
|
|
||||||
lib,
|
|
||||||
nodejs,
|
|
||||||
pnpm_10,
|
|
||||||
git,
|
|
||||||
version ? "dev",
|
|
||||||
gitRev ? "unknown",
|
|
||||||
gitBranch ? "unknown",
|
|
||||||
buildScript ? "build",
|
|
||||||
}:
|
|
||||||
|
|
||||||
stdenv.mkDerivation (finalAttrs: {
|
|
||||||
inherit version;
|
|
||||||
pname = "ut-registration-plus";
|
|
||||||
|
|
||||||
src = ../.;
|
|
||||||
|
|
||||||
nativeBuildInputs = [
|
|
||||||
nodejs
|
|
||||||
pnpm_10.configHook
|
|
||||||
git
|
|
||||||
];
|
|
||||||
|
|
||||||
pnpmDeps = pnpm_10.fetchDeps {
|
|
||||||
inherit (finalAttrs) pname version src;
|
|
||||||
fetcherVersion = 2;
|
|
||||||
hash = "sha256-UqHymJWvlTV4glra/6DkxuCxbG5dpPkFcnvq3vuxsJ8=";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Pass git info to the build
|
|
||||||
VITE_GIT_COMMIT = gitRev;
|
|
||||||
VITE_GIT_BRANCH = gitBranch;
|
|
||||||
|
|
||||||
buildPhase = ''
|
|
||||||
pnpm run ${buildScript}
|
|
||||||
'';
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out
|
|
||||||
cp -r dist/* $out/
|
|
||||||
'';
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
description = "UT Registration Plus";
|
|
||||||
homepage = "https://github.com/Longhorn-Developers/UT-Registration-Plus";
|
|
||||||
license = lib.licenses.mit;
|
|
||||||
maintainers = lib.maintainers.doprz;
|
|
||||||
platforms = lib.platforms.unix;
|
|
||||||
};
|
|
||||||
})
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
{ inputs, ... }:
|
|
||||||
{
|
|
||||||
perSystem =
|
|
||||||
{ pkgs, ... }:
|
|
||||||
let
|
|
||||||
packageJson = builtins.fromJSON (builtins.readFile ../package.json);
|
|
||||||
gitRev = inputs.self.shortRev or inputs.self.dirtyShortRev or "dev";
|
|
||||||
gitBranch = if inputs.self ? ref then inputs.self.ref else "unknown";
|
|
||||||
baseVersion = packageJson.version;
|
|
||||||
|
|
||||||
commonArgs = {
|
|
||||||
inherit gitRev gitBranch;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Prod variant
|
|
||||||
ut-registration-plus = pkgs.callPackage ./package.nix (
|
|
||||||
commonArgs
|
|
||||||
// {
|
|
||||||
version = "${baseVersion}+git.${gitRev}";
|
|
||||||
buildScript = "build";
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
# Dev variant
|
|
||||||
ut-registration-plus-dev = pkgs.callPackage ./package.nix (
|
|
||||||
commonArgs
|
|
||||||
// {
|
|
||||||
version = "${baseVersion}-dev+git.${gitRev}";
|
|
||||||
buildScript = "build:dev";
|
|
||||||
}
|
|
||||||
);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
packages = {
|
|
||||||
inherit ut-registration-plus ut-registration-plus-dev;
|
|
||||||
default = ut-registration-plus;
|
|
||||||
dev = ut-registration-plus-dev;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
{ inputs, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
inputs.treefmt-nix.flakeModule
|
|
||||||
];
|
|
||||||
|
|
||||||
perSystem =
|
|
||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
treefmt = {
|
|
||||||
projectRootFile = "flake.nix";
|
|
||||||
programs.nixfmt.enable = pkgs.lib.meta.availableOn pkgs.stdenv.buildPlatform pkgs.nixfmt-rfc-style.compiler;
|
|
||||||
programs.nixfmt.package = pkgs.nixfmt-rfc-style;
|
|
||||||
|
|
||||||
# NOTE: Make sure the prettier version in package.json and the one used by treefmt are the same for consistent results
|
|
||||||
programs.prettier.enable = true;
|
|
||||||
programs.shellcheck.enable = true;
|
|
||||||
programs.yamlfmt.enable = true;
|
|
||||||
programs.dockerfmt.enable = true;
|
|
||||||
|
|
||||||
settings.formatter.prettier.excludes = [ "pnpm-lock.yaml" ];
|
|
||||||
settings.formatter.shellcheck.excludes = [ ".envrc" ];
|
|
||||||
settings.formatter.yamlfmt.excludes = [ "pnpm-lock.yaml" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
checks = {
|
|
||||||
prettier-version-match =
|
|
||||||
pkgs.runCommand "check-prettier-version"
|
|
||||||
{
|
|
||||||
buildInputs = [ pkgs.jq ];
|
|
||||||
}
|
|
||||||
''
|
|
||||||
# Extract prettier version from package.json
|
|
||||||
packageJsonVersion=$(jq -r '.devDependencies.prettier // empty' ${../package.json})
|
|
||||||
|
|
||||||
if [ -z "$packageJsonVersion" ]; then
|
|
||||||
echo "Error: prettier not found in package.json devDependencies"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Remove any semver prefix characters (^, ~, etc...)
|
|
||||||
packageJsonVersion=$(echo "$packageJsonVersion" | sed 's/^[\^~>=<]*//')
|
|
||||||
|
|
||||||
# Get prettier version from nixpkgs
|
|
||||||
nixVersion="${pkgs.nodePackages.prettier.version}"
|
|
||||||
|
|
||||||
if [ "$packageJsonVersion" != "$nixVersion" ]; then
|
|
||||||
echo ""
|
|
||||||
echo "ERROR: Prettier version mismatch!"
|
|
||||||
echo " package.json: $packageJsonVersion"
|
|
||||||
echo " nixpkgs: $nixVersion"
|
|
||||||
echo ""
|
|
||||||
echo "Please update one of the following:"
|
|
||||||
echo " - Update prettier in package.json to match nixpkgs: $nixVersion"
|
|
||||||
echo " - Override prettier in your flake to match package.json"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
touch $out
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
39
package.json
39
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ut-registration-plus",
|
"name": "ut-registration-plus",
|
||||||
"displayName": "UT Registration Plus",
|
"displayName": "UT Registration Plus",
|
||||||
"version": "2.3.0",
|
"version": "2.2.2",
|
||||||
"description": "UT Registration Plus is a Chrome extension that allows students to easily register for classes.",
|
"description": "UT Registration Plus is a Chrome extension that allows students to easily register for classes.",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "https://github.com/Longhorn-Developers/UT-Registration-Plus",
|
"homepage": "https://github.com/Longhorn-Developers/UT-Registration-Plus",
|
||||||
@@ -15,8 +15,8 @@
|
|||||||
"zip:to-publish": "SENTRY_ENV='production' pnpm zip",
|
"zip:to-publish": "SENTRY_ENV='production' pnpm zip",
|
||||||
"prettier": "prettier src --check",
|
"prettier": "prettier src --check",
|
||||||
"prettier:fix": "prettier src --write",
|
"prettier:fix": "prettier src --write",
|
||||||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives",
|
"lint": "eslint src --report-unused-disable-directives",
|
||||||
"lint:fix": "eslint src --ext ts,tsx --report-unused-disable-directives --fix",
|
"lint:fix": "eslint src --report-unused-disable-directives --fix",
|
||||||
"check-types": "tsc --noEmit",
|
"check-types": "tsc --noEmit",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:ui": "vitest --ui",
|
"test:ui": "vitest --ui",
|
||||||
@@ -39,9 +39,6 @@
|
|||||||
"@phosphor-icons/react": "^2.1.7",
|
"@phosphor-icons/react": "^2.1.7",
|
||||||
"@sentry/react": "^8.55.0",
|
"@sentry/react": "^8.55.0",
|
||||||
"@tanstack/react-query": "^5.69.0",
|
"@tanstack/react-query": "^5.69.0",
|
||||||
"@tsparticles/engine": "^3.9.1",
|
|
||||||
"@tsparticles/react": "^3.0.0",
|
|
||||||
"@tsparticles/slim": "^3.9.1",
|
|
||||||
"@unocss/vite": "^0.63.6",
|
"@unocss/vite": "^0.63.6",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"chrome-extension-toolkit": "^0.0.54",
|
"chrome-extension-toolkit": "^0.0.54",
|
||||||
@@ -70,6 +67,9 @@
|
|||||||
"@commitlint/config-conventional": "^19.7.1",
|
"@commitlint/config-conventional": "^19.7.1",
|
||||||
"@commitlint/types": "^19.5.0",
|
"@commitlint/types": "^19.5.0",
|
||||||
"@crxjs/vite-plugin": "2.0.0-beta.21",
|
"@crxjs/vite-plugin": "2.0.0-beta.21",
|
||||||
|
"@eslint/compat": "^2.0.0",
|
||||||
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
|
"@eslint/js": "^9.39.1",
|
||||||
"@iconify-json/bi": "^1.2.2",
|
"@iconify-json/bi": "^1.2.2",
|
||||||
"@iconify-json/ic": "^1.2.2",
|
"@iconify-json/ic": "^1.2.2",
|
||||||
"@iconify-json/iconoir": "^1.2.7",
|
"@iconify-json/iconoir": "^1.2.7",
|
||||||
@@ -99,8 +99,8 @@
|
|||||||
"@types/semantic-release": "^20.0.6",
|
"@types/semantic-release": "^20.0.6",
|
||||||
"@types/semver": "^7.5.8",
|
"@types/semver": "^7.5.8",
|
||||||
"@types/sql.js": "^1.4.9",
|
"@types/sql.js": "^1.4.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
"@typescript-eslint/eslint-plugin": "^8.47.0",
|
||||||
"@typescript-eslint/parser": "^7.18.0",
|
"@typescript-eslint/parser": "^8.47.0",
|
||||||
"@unocss/eslint-config": "^0.63.6",
|
"@unocss/eslint-config": "^0.63.6",
|
||||||
"@unocss/postcss": "^0.63.6",
|
"@unocss/postcss": "^0.63.6",
|
||||||
"@unocss/preset-uno": "^0.63.6",
|
"@unocss/preset-uno": "^0.63.6",
|
||||||
@@ -117,29 +117,29 @@
|
|||||||
"cssnano-preset-advanced": "^7.0.6",
|
"cssnano-preset-advanced": "^7.0.6",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"es-module-lexer": "^1.6.0",
|
"es-module-lexer": "^1.6.0",
|
||||||
"eslint": "^8.57.1",
|
"eslint": "^9.39.1",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-import-resolver-typescript": "^3.8.3",
|
"eslint-import-resolver-typescript": "^4.4.4",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
"eslint-plugin-import-essentials": "^0.2.1",
|
"eslint-plugin-import-essentials": "^0.2.1",
|
||||||
"eslint-plugin-jsdoc": "^50.6.3",
|
"eslint-plugin-jsdoc": "^61.2.1",
|
||||||
"eslint-plugin-prettier": "^5.2.3",
|
"eslint-plugin-prettier": "^5.2.3",
|
||||||
"eslint-plugin-react": "^7.37.4",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-prefer-function-component": "^3.4.0",
|
"eslint-plugin-react-prefer-function-component": "^3.4.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.19",
|
"eslint-plugin-react-refresh": "^0.4.19",
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
"eslint-plugin-storybook": "^0.9.0",
|
"eslint-plugin-storybook": "^10.0.8",
|
||||||
"eslint-plugin-tsdoc": "^0.3.0",
|
"eslint-plugin-tsdoc": "^0.3.0",
|
||||||
"gulp": "^5.0.0",
|
"gulp": "^5.0.0",
|
||||||
"gulp-execa": "^7.0.1",
|
"gulp-execa": "^7.0.1",
|
||||||
"gulp-zip": "^6.1.0",
|
"gulp-zip": "^6.1.0",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"prettier": "3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"react-dev-utils": "^12.0.1",
|
"react-dev-utils": "^12.0.1",
|
||||||
"semantic-release": "^24.2.3",
|
"semantic-release": "^24.2.3",
|
||||||
"storybook": "^8.6.0",
|
"storybook": "^8.6.0",
|
||||||
@@ -162,10 +162,7 @@
|
|||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"es-module-lexer": "^1.5.4"
|
"es-module-lexer": "^1.5.4"
|
||||||
},
|
}
|
||||||
"onlyBuiltDependencies": [
|
|
||||||
"@tsparticles/engine"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "20.19.4",
|
"node": "20.19.4",
|
||||||
|
|||||||
1833
pnpm-lock.yaml
generated
1833
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
10
shell.nix
10
shell.nix
@@ -1,10 +0,0 @@
|
|||||||
(import (
|
|
||||||
let
|
|
||||||
rev = "v1.1.0";
|
|
||||||
sha256 = "sha256:19d2z6xsvpxm184m41qrpi1bplilwipgnzv9jy17fgw421785q1m";
|
|
||||||
in
|
|
||||||
fetchTarball {
|
|
||||||
inherit sha256;
|
|
||||||
url = "https://github.com/NixOS/flake-compat/archive/${rev}.tar.gz";
|
|
||||||
}
|
|
||||||
) { src = ./.; }).shellNix
|
|
||||||
@@ -271,12 +271,12 @@ export default function Page404(): JSX.Element {
|
|||||||
}
|
}
|
||||||
function _0x5629d1() {
|
function _0x5629d1() {
|
||||||
let _0x13c635 = _0xdd3699;
|
let _0x13c635 = _0xdd3699;
|
||||||
(_0x5b7f43(),
|
_0x5b7f43(),
|
||||||
_0x16f39e[_0x13c635(0x81)](
|
_0x16f39e[_0x13c635(0x81)](
|
||||||
_0x228047,
|
_0x228047,
|
||||||
0x9c + -0x1 * 0x23ab + 0x230f * 0x1,
|
0x9c + -0x1 * 0x23ab + 0x230f * 0x1,
|
||||||
-0x1c26 + 0x7bf + -0x6cd * -0x3
|
-0x1c26 + 0x7bf + -0x6cd * -0x3
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
let _0x93f360 = 0x64 * 0x49 + 0x1e1e + -0x3aa2;
|
let _0x93f360 = 0x64 * 0x49 + 0x1e1e + -0x3aa2;
|
||||||
function _0x5b7f43() {
|
function _0x5b7f43() {
|
||||||
@@ -373,12 +373,12 @@ export default function Page404(): JSX.Element {
|
|||||||
(-0x2469 + 0x156a * -0x1 + 0x39e2)) +
|
(-0x2469 + 0x156a * -0x1 + 0x39e2)) +
|
||||||
(-0x1 * 0x13f8 + 0x6df + -0x1 * -0xd29);
|
(-0x1 * 0x13f8 + 0x6df + -0x1 * -0xd29);
|
||||||
if (_0x3e178a == 0x1 * -0x1a87 + 0x1fdd + -0x555 * 0x1) {
|
if (_0x3e178a == 0x1 * -0x1a87 + 0x1fdd + -0x555 * 0x1) {
|
||||||
((_0x546fb5 =
|
(_0x546fb5 =
|
||||||
(_0x227002 * (-0x10d7 + 0x1 * 0x15ad + -0x2 * 0x263)) &
|
(_0x227002 * (-0x10d7 + 0x1 * 0x15ad + -0x2 * 0x263)) &
|
||||||
(-0x25ca * 0x1 + 0x8 * 0x278 + -0x1 * -0x1219)),
|
(-0x25ca * 0x1 + 0x8 * 0x278 + -0x1 * -0x1219)),
|
||||||
(_0x1667c5 =
|
(_0x1667c5 =
|
||||||
(_0x32116b * (0x4d3 + 0x1c09 * -0x1 + 0x3 * 0x7c2)) &
|
(_0x32116b * (0x4d3 + 0x1c09 * -0x1 + 0x3 * 0x7c2)) &
|
||||||
(-0xf06 * 0x2 + -0x144f * -0x1 + -0x344 * -0x3)));
|
(-0xf06 * 0x2 + -0x144f * -0x1 + -0x344 * -0x3));
|
||||||
if (_0x5b3085 < -0xa * 0xed + -0xd19 + 0x1 * 0x165b)
|
if (_0x5b3085 < -0xa * 0xed + -0xd19 + 0x1 * 0x165b)
|
||||||
_0x1667c5 += -0xd48 + 0xf6c + 0xc * -0x2b;
|
_0x1667c5 += -0xd48 + 0xf6c + 0xc * -0x2b;
|
||||||
}
|
}
|
||||||
@@ -410,10 +410,10 @@ export default function Page404(): JSX.Element {
|
|||||||
(-0x2709 + -0x6 * -0x312 + -0x39a * -0x6)),
|
(-0x2709 + -0x6 * -0x312 + -0x39a * -0x6)),
|
||||||
(_0x267dd3 = _0x38c463));
|
(_0x267dd3 = _0x38c463));
|
||||||
}
|
}
|
||||||
((_0x227002 += _0x4b089b),
|
(_0x227002 += _0x4b089b),
|
||||||
(_0x2aec99 += _0x5b3085),
|
(_0x2aec99 += _0x5b3085),
|
||||||
(_0x32116b += _0x1eaaad),
|
(_0x32116b += _0x1eaaad),
|
||||||
(_0x38c463 += _0x57383c));
|
(_0x38c463 += _0x57383c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let _0x5cba48 =
|
let _0x5cba48 =
|
||||||
@@ -430,7 +430,7 @@ export default function Page404(): JSX.Element {
|
|||||||
let _0xdf8389 =
|
let _0xdf8389 =
|
||||||
((_0x13f1b0 & (-0x247a + -0x4 * -0x9c2 + -0x39 * 0x7)) * _0x2062a9 * _0x5c387a) /
|
((_0x13f1b0 & (-0x247a + -0x4 * -0x9c2 + -0x39 * 0x7)) * _0x2062a9 * _0x5c387a) /
|
||||||
((0x1d5 * 0xa + -0x250a + -0x31 * -0x67) * (-0x7 * 0x25f + -0xae7 + 0x1c7f * 0x1));
|
((0x1d5 * 0xa + -0x250a + -0x31 * -0x67) * (-0x7 * 0x25f + -0xae7 + 0x1c7f * 0x1));
|
||||||
((_0x228047[_0x4626de(0x8e)][
|
(_0x228047[_0x4626de(0x8e)][
|
||||||
(_0x132623 + _0x1a573d * _0x124180) * (0x29 * -0xa9 + -0x94 * -0x2b + -0x239 * -0x1) +
|
(_0x132623 + _0x1a573d * _0x124180) * (0x29 * -0xa9 + -0x94 * -0x2b + -0x239 * -0x1) +
|
||||||
(0x55d * 0x2 + 0xeed * 0x1 + -0xc7 * 0x21)
|
(0x55d * 0x2 + 0xeed * 0x1 + -0xc7 * 0x21)
|
||||||
] = _0x5cba48),
|
] = _0x5cba48),
|
||||||
@@ -441,7 +441,7 @@ export default function Page404(): JSX.Element {
|
|||||||
(_0x228047[_0x4626de(0x8e)][
|
(_0x228047[_0x4626de(0x8e)][
|
||||||
(_0x132623 + _0x1a573d * _0x124180) * (0x1e2a + -0x21df + -0x1 * -0x3b9) +
|
(_0x132623 + _0x1a573d * _0x124180) * (0x1e2a + -0x21df + -0x1 * -0x3b9) +
|
||||||
(0x1e79 + 0x860 * -0x2 + 0x1 * -0xdb7)
|
(0x1e79 + 0x860 * -0x2 + 0x1 * -0xdb7)
|
||||||
] = _0xdf8389));
|
] = _0xdf8389);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react';
|
|
||||||
import CalendarFooter from '@views/components/calendar/CalendarFooter';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
title: 'Components/Calendar/CalendarFooter',
|
|
||||||
component: CalendarFooter,
|
|
||||||
parameters: {
|
|
||||||
layout: 'centered',
|
|
||||||
},
|
|
||||||
tags: ['autodocs'],
|
|
||||||
} satisfies Meta<typeof CalendarFooter>;
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof meta>;
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
args: {},
|
|
||||||
};
|
|
||||||
@@ -114,12 +114,11 @@ export default function Calendar(): ReactNode {
|
|||||||
<LargeLogo />
|
<LargeLogo />
|
||||||
<Button
|
<Button
|
||||||
variant='minimal'
|
variant='minimal'
|
||||||
size='small'
|
|
||||||
color='theme-black'
|
color='theme-black'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowSidebar(!showSidebar);
|
setShowSidebar(!showSidebar);
|
||||||
}}
|
}}
|
||||||
className='screenshot:hidden'
|
className='h-fit screenshot:hidden !p-0'
|
||||||
icon={Sidebar}
|
icon={Sidebar}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -124,7 +124,9 @@ export default function CourseCellColorPicker({ defaultColor }: CourseCellColorP
|
|||||||
<>
|
<>
|
||||||
<Divider orientation='horizontal' size='100%' className='my-1' />
|
<Divider orientation='horizontal' size='100%' className='my-1' />
|
||||||
<div className='grid grid-cols-6 gap-1'>
|
<div className='grid grid-cols-6 gap-1'>
|
||||||
{colorPatchColors.get(selectedBaseColor)?.map(shadeColor => (
|
{colorPatchColors
|
||||||
|
.get(selectedBaseColor)
|
||||||
|
?.map(shadeColor => (
|
||||||
<ColorPatch
|
<ColorPatch
|
||||||
key={shadeColor}
|
key={shadeColor}
|
||||||
color={shadeColor}
|
color={shadeColor}
|
||||||
|
|||||||
@@ -58,7 +58,13 @@ export default function CalendarFooter(): JSX.Element {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Button variant='minimal' size='small' icon={GearSix} color='ut-black' onClick={handleOpenOptions} />
|
<Button
|
||||||
|
className='h-fit w-fit !p-0'
|
||||||
|
variant='minimal'
|
||||||
|
icon={GearSix}
|
||||||
|
color='ut-black'
|
||||||
|
onClick={handleOpenOptions}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,15 +28,14 @@ export default function CalendarHeader({ sidebarOpen, onSidebarToggle }: Calenda
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{ scrollbarGutter: 'stable' }}
|
style={{ scrollbarGutter: 'stable' }}
|
||||||
className='sticky left-0 right-0 top-0 z-10 min-h-[85px] flex items-center gap-5 overflow-x-auto overflow-y-hidden bg-white pl-spacing-7 pt-spacing-5'
|
className='sticky left-0 right-0 top-0 z-10 min-h-[85px] flex items-center gap-5 overflow-x-scroll overflow-y-hidden bg-white pl-spacing-7 pt-spacing-5'
|
||||||
>
|
>
|
||||||
{!sidebarOpen && (
|
{!sidebarOpen && (
|
||||||
<Button
|
<Button
|
||||||
variant='minimal'
|
variant='minimal'
|
||||||
size='small'
|
|
||||||
color='theme-black'
|
color='theme-black'
|
||||||
onClick={onSidebarToggle}
|
onClick={onSidebarToggle}
|
||||||
className='screenshot:hidden'
|
className='h-fit w-fit screenshot:hidden !p-0'
|
||||||
icon={Sidebar}
|
icon={Sidebar}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -26,16 +26,15 @@ export function CalendarSchedules() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='min-w-full w-0 flex flex-col items-center gap-y-spacing-2'>
|
<div className='min-w-full w-0 flex flex-col items-center gap-y-spacing-3'>
|
||||||
<div className='m0 w-full flex items-center justify-between'>
|
<div className='m0 w-full flex justify-between'>
|
||||||
<Text variant='h3' className='text-nowrap text-theme-black'>
|
<Text variant='h3' className='text-nowrap text-theme-black'>
|
||||||
MY SCHEDULES
|
MY SCHEDULES
|
||||||
</Text>
|
</Text>
|
||||||
<Button
|
<Button
|
||||||
variant='minimal'
|
variant='minimal'
|
||||||
size='small'
|
|
||||||
color='theme-black'
|
color='theme-black'
|
||||||
className='!p-0 btn'
|
className='h-fit w-fit !p-0 btn'
|
||||||
onClick={handleAddSchedule}
|
onClick={handleAddSchedule}
|
||||||
icon={Plus}
|
icon={Plus}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -444,8 +444,7 @@ export const calculateCourseCellColumns = (dayCells: CalendarGridCourse[]) => {
|
|||||||
typeof cell.calendarGridPoint.startIndex === 'number' &&
|
typeof cell.calendarGridPoint.startIndex === 'number' &&
|
||||||
cell.calendarGridPoint.startIndex >= 0
|
cell.calendarGridPoint.startIndex >= 0
|
||||||
)
|
)
|
||||||
.slice()
|
.toSorted((a, b) => a.calendarGridPoint.startIndex - b.calendarGridPoint.startIndex);
|
||||||
.sort((a, b) => a.calendarGridPoint.startIndex - b.calendarGridPoint.startIndex);
|
|
||||||
|
|
||||||
// Initialize metadata
|
// Initialize metadata
|
||||||
for (const cell of cells) {
|
for (const cell of cells) {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default function Link(props: PropsWithChildren<Props>): JSX.Element {
|
|||||||
tabIndex={isDisabled ? -1 : 0}
|
tabIndex={isDisabled ? -1 : 0}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
{
|
{
|
||||||
'underline cursor-pointer p-2': !isDisabled,
|
'underline cursor-pointer': !isDisabled,
|
||||||
'cursor-not-allowed color-ut-gray': isDisabled,
|
'cursor-not-allowed color-ut-gray': isDisabled,
|
||||||
},
|
},
|
||||||
className
|
className
|
||||||
|
|||||||
@@ -1,197 +0,0 @@
|
|||||||
import { Trash } from '@phosphor-icons/react';
|
|
||||||
import { OptionsStore } from '@shared/storage/OptionsStore';
|
|
||||||
import MIMEType from '@shared/types/MIMEType';
|
|
||||||
import type { UserSchedule } from '@shared/types/UserSchedule';
|
|
||||||
import { handleExportJson } from '@views/components/calendar/utils';
|
|
||||||
import { Button } from '@views/components/common/Button';
|
|
||||||
import Divider from '@views/components/common/Divider';
|
|
||||||
import SwitchButton from '@views/components/common/SwitchButton';
|
|
||||||
import Text from '@views/components/common/Text/Text';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import FileUpload from '../common/FileUpload';
|
|
||||||
import { DISPLAY_PREVIEWS, PREVIEW_SECTION_DIV_CLASSNAME } from './constants';
|
|
||||||
import Preview from './Preview';
|
|
||||||
|
|
||||||
interface AdvancedSettingsProps {
|
|
||||||
highlightConflicts: boolean;
|
|
||||||
setHighlightConflicts: (value: boolean) => void;
|
|
||||||
loadAllCourses: boolean;
|
|
||||||
setLoadAllCourses: (value: boolean) => void;
|
|
||||||
increaseScheduleLimit: boolean;
|
|
||||||
setIncreaseScheduleLimit: (value: boolean) => void;
|
|
||||||
calendarNewTab: boolean;
|
|
||||||
setCalendarNewTab: (value: boolean) => void;
|
|
||||||
activeSchedule: UserSchedule;
|
|
||||||
handleEraseAll: () => void;
|
|
||||||
handleImportClick: (event: React.ChangeEvent<HTMLInputElement>) => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Settings section component for advanced settings
|
|
||||||
*/
|
|
||||||
export const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({
|
|
||||||
highlightConflicts,
|
|
||||||
setHighlightConflicts,
|
|
||||||
loadAllCourses,
|
|
||||||
setLoadAllCourses,
|
|
||||||
increaseScheduleLimit,
|
|
||||||
setIncreaseScheduleLimit,
|
|
||||||
calendarNewTab,
|
|
||||||
setCalendarNewTab,
|
|
||||||
activeSchedule,
|
|
||||||
handleEraseAll,
|
|
||||||
handleImportClick,
|
|
||||||
}) => (
|
|
||||||
<section className='mb-8'>
|
|
||||||
<h2 className='mb-4 text-xl text-ut-black font-semibold'>ADVANCED SETTINGS</h2>
|
|
||||||
<div className='flex space-x-4'>
|
|
||||||
<div className={PREVIEW_SECTION_DIV_CLASSNAME}>
|
|
||||||
<div className='flex items-center justify-between'>
|
|
||||||
<div className='max-w-xs'>
|
|
||||||
<Text variant='h4' className='text-ut-burntorange font-semibold'>
|
|
||||||
Export Current Schedule
|
|
||||||
</Text>
|
|
||||||
<p className='text-sm text-gray-600'>Backup your active schedule to a portable file</p>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant='outline'
|
|
||||||
color='ut-burntorange'
|
|
||||||
onClick={() => handleExportJson(activeSchedule.id)}
|
|
||||||
>
|
|
||||||
Export
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Divider size='auto' orientation='horizontal' />
|
|
||||||
|
|
||||||
<div className='flex items-center justify-between'>
|
|
||||||
<div className='max-w-xs'>
|
|
||||||
<Text variant='h4' className='text-ut-burntorange font-semibold'>
|
|
||||||
Import Schedule
|
|
||||||
</Text>
|
|
||||||
<p className='text-sm text-gray-600'>Import from a schedule file</p>
|
|
||||||
</div>
|
|
||||||
<FileUpload
|
|
||||||
variant='filled'
|
|
||||||
color='ut-burntorange'
|
|
||||||
onChange={handleImportClick}
|
|
||||||
accept={MIMEType.JSON}
|
|
||||||
>
|
|
||||||
Import Schedule
|
|
||||||
</FileUpload>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Divider size='auto' orientation='horizontal' />
|
|
||||||
|
|
||||||
<div className='flex items-center justify-between'>
|
|
||||||
<div className='max-w-xs'>
|
|
||||||
<Text variant='h4' className='text-ut-burntorange font-semibold'>
|
|
||||||
Course Conflict Highlight
|
|
||||||
</Text>
|
|
||||||
<p className='text-sm text-gray-600'>
|
|
||||||
Adds a red strikethrough to courses that have conflicting times.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<SwitchButton
|
|
||||||
isChecked={highlightConflicts}
|
|
||||||
onChange={() => {
|
|
||||||
setHighlightConflicts(!highlightConflicts);
|
|
||||||
OptionsStore.set('enableHighlightConflicts', !highlightConflicts);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Divider size='auto' orientation='horizontal' />
|
|
||||||
|
|
||||||
<div className='flex items-center justify-between'>
|
|
||||||
<div className='max-w-xs'>
|
|
||||||
<Text variant='h4' className='text-ut-burntorange font-semibold'>
|
|
||||||
Load All Courses in Course Schedule
|
|
||||||
</Text>
|
|
||||||
<p className='text-sm text-gray-600'>
|
|
||||||
Loads all courses in the Course Schedule site by scrolling, instead of using next/prev page
|
|
||||||
buttons.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<SwitchButton
|
|
||||||
isChecked={loadAllCourses}
|
|
||||||
onChange={() => {
|
|
||||||
setLoadAllCourses(!loadAllCourses);
|
|
||||||
OptionsStore.set('enableScrollToLoad', !loadAllCourses);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Divider size='auto' orientation='horizontal' />
|
|
||||||
|
|
||||||
<div className='flex items-center justify-between'>
|
|
||||||
<div className='max-w-xs'>
|
|
||||||
<Text variant='h4' className='text-ut-burntorange font-semibold'>
|
|
||||||
Allow more than 10 schedules
|
|
||||||
</Text>
|
|
||||||
<p className='text-sm text-gray-600'>
|
|
||||||
Allow bypassing the 10-schedule limit. Intended for advisors or staff who need to create
|
|
||||||
many schedules on behalf of students.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<SwitchButton
|
|
||||||
isChecked={increaseScheduleLimit}
|
|
||||||
onChange={() => {
|
|
||||||
setIncreaseScheduleLimit(!increaseScheduleLimit);
|
|
||||||
OptionsStore.set('allowMoreSchedules', !increaseScheduleLimit);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Divider size='auto' orientation='horizontal' />
|
|
||||||
|
|
||||||
<div className='flex items-center justify-between'>
|
|
||||||
<div className='max-w-xs'>
|
|
||||||
<Text variant='h4' className='text-ut-burntorange font-semibold'>
|
|
||||||
Always Open Calendar in New Tab
|
|
||||||
</Text>
|
|
||||||
<p className='text-sm text-gray-600'>
|
|
||||||
Always opens the calendar view in a new tab when navigating to the calendar page. May
|
|
||||||
prevent issues where the calendar refuses to open.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<SwitchButton
|
|
||||||
isChecked={calendarNewTab}
|
|
||||||
onChange={() => {
|
|
||||||
setCalendarNewTab(!calendarNewTab);
|
|
||||||
OptionsStore.set('alwaysOpenCalendarInNewTab', !calendarNewTab);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Divider size='auto' orientation='horizontal' />
|
|
||||||
|
|
||||||
<div className='flex items-center justify-between'>
|
|
||||||
<div className='max-w-xs'>
|
|
||||||
<Text variant='h4' className='text-ut-burntorange font-semibold'>
|
|
||||||
Reset All Data
|
|
||||||
</Text>
|
|
||||||
<p className='text-sm text-gray-600'>Erases all schedules and courses you have.</p>
|
|
||||||
</div>
|
|
||||||
<Button variant='outline' color='theme-red' icon={Trash} onClick={handleEraseAll}>
|
|
||||||
Erase All
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{DISPLAY_PREVIEWS && (
|
|
||||||
<Preview>
|
|
||||||
<Text
|
|
||||||
variant='h2-course'
|
|
||||||
className={clsx('text-center text-theme-red font-normal', {
|
|
||||||
'line-through': highlightConflicts,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
01234 MWF 10:00 AM - 11:00 AM UTC 1.234
|
|
||||||
</Text>
|
|
||||||
</Preview>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import Text from '@views/components/common/Text/Text';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface ContributorCardProps {
|
|
||||||
name: string;
|
|
||||||
githubUsername: string;
|
|
||||||
roles: string[];
|
|
||||||
stats?: {
|
|
||||||
commits: number;
|
|
||||||
linesAdded: number;
|
|
||||||
linesDeleted: number;
|
|
||||||
mergedPRs?: number;
|
|
||||||
};
|
|
||||||
showStats: boolean;
|
|
||||||
includeMergedPRs: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GitHub contributor card component
|
|
||||||
*/
|
|
||||||
export const ContributorCard: React.FC<ContributorCardProps> = ({
|
|
||||||
name,
|
|
||||||
githubUsername,
|
|
||||||
roles,
|
|
||||||
stats,
|
|
||||||
showStats,
|
|
||||||
includeMergedPRs,
|
|
||||||
}) => (
|
|
||||||
<div className='border border-gray-300 rounded bg-ut-gray/10 p-4'>
|
|
||||||
<Text
|
|
||||||
variant='p'
|
|
||||||
className='text-ut-burntorange font-semibold hover:cursor-pointer'
|
|
||||||
onClick={() => window.open(`https://github.com/${githubUsername}`, '_blank')}
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</Text>
|
|
||||||
{roles.map(role => (
|
|
||||||
<p key={`${githubUsername}-${role}`} className='text-sm text-gray-600'>
|
|
||||||
{role}
|
|
||||||
</p>
|
|
||||||
))}
|
|
||||||
{showStats && stats && (
|
|
||||||
<div className='mt-2'>
|
|
||||||
<p className='text-xs text-gray-500'>GitHub Stats (UTRP repo):</p>
|
|
||||||
{includeMergedPRs && stats.mergedPRs !== undefined && (
|
|
||||||
<p className='text-xs'>Merged PRs: {stats.mergedPRs}</p>
|
|
||||||
)}
|
|
||||||
<p className='text-xs'>Commits: {stats.commits}</p>
|
|
||||||
<p className='text-xs text-ut-green'>{stats.linesAdded}++</p>
|
|
||||||
<p className='text-xs text-theme-red'>{stats.linesDeleted}--</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
@@ -1,68 +1,115 @@
|
|||||||
// Pages
|
// import addCourse from '@pages/background/lib/addCourse';
|
||||||
import { addCourseByURL } from '@pages/background/lib/addCourseByURL';
|
import { addCourseByURL } from '@pages/background/lib/addCourseByURL';
|
||||||
import { deleteAllSchedules } from '@pages/background/lib/deleteSchedule';
|
import { deleteAllSchedules } from '@pages/background/lib/deleteSchedule';
|
||||||
import importSchedule from '@pages/background/lib/importSchedule';
|
import importSchedule from '@pages/background/lib/importSchedule';
|
||||||
import { CalendarDots } from '@phosphor-icons/react';
|
import { CalendarDots, Trash } from '@phosphor-icons/react';
|
||||||
// Shared
|
|
||||||
import { background } from '@shared/messages';
|
import { background } from '@shared/messages';
|
||||||
import { DevStore } from '@shared/storage/DevStore';
|
import { DevStore } from '@shared/storage/DevStore';
|
||||||
import { initSettings, OptionsStore } from '@shared/storage/OptionsStore';
|
import { initSettings, OptionsStore } from '@shared/storage/OptionsStore';
|
||||||
import { CRX_PAGES } from '@shared/types/CRXPages';
|
import { CRX_PAGES } from '@shared/types/CRXPages';
|
||||||
import Particles from '@tsparticles/react';
|
import MIMEType from '@shared/types/MIMEType';
|
||||||
|
// import { addCourseByUrl } from '@shared/util/courseUtils';
|
||||||
|
// import { getCourseColors } from '@shared/util/colors';
|
||||||
|
// import CalendarCourseCell from '@views/components/calendar/CalendarCourseCell';
|
||||||
import { Button } from '@views/components/common/Button';
|
import { Button } from '@views/components/common/Button';
|
||||||
import { usePrompt } from '@views/components/common/DialogProvider/DialogProvider';
|
import { usePrompt } from '@views/components/common/DialogProvider/DialogProvider';
|
||||||
// Views
|
|
||||||
import Divider from '@views/components/common/Divider';
|
import Divider from '@views/components/common/Divider';
|
||||||
import { LargeLogo } from '@views/components/common/LogoIcon';
|
import { LargeLogo } from '@views/components/common/LogoIcon';
|
||||||
|
// import PopupCourseBlock from '@views/components/common/PopupCourseBlock';
|
||||||
|
import SwitchButton from '@views/components/common/SwitchButton';
|
||||||
import Text from '@views/components/common/Text/Text';
|
import Text from '@views/components/common/Text/Text';
|
||||||
// Hooks
|
|
||||||
import useChangelog from '@views/hooks/useChangelog';
|
import useChangelog from '@views/hooks/useChangelog';
|
||||||
import useSchedules from '@views/hooks/useSchedules';
|
import useSchedules from '@views/hooks/useSchedules';
|
||||||
|
// import { CourseCatalogScraper } from '@views/lib/CourseCatalogScraper';
|
||||||
|
// import getCourseTableRows from '@views/lib/getCourseTableRows';
|
||||||
import { GitHubStatsService, LONGHORN_DEVELOPERS_ADMINS, LONGHORN_DEVELOPERS_SWE } from '@views/lib/getGitHubStats';
|
import { GitHubStatsService, LONGHORN_DEVELOPERS_ADMINS, LONGHORN_DEVELOPERS_SWE } from '@views/lib/getGitHubStats';
|
||||||
// Misc
|
// import { SiteSupport } from '@views/lib/getSiteSupport';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import clsx from 'clsx';
|
||||||
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
// Icons
|
|
||||||
import IconoirGitFork from '~icons/iconoir/git-fork';
|
import IconoirGitFork from '~icons/iconoir/git-fork';
|
||||||
|
|
||||||
|
import { handleExportJson } from '../calendar/utils';
|
||||||
|
// import { ExampleCourse } from 'src/stories/components/ConflictsWithWarning.stories';;
|
||||||
|
import FileUpload from '../common/FileUpload';
|
||||||
import { useMigrationDialog } from '../common/MigrationDialog';
|
import { useMigrationDialog } from '../common/MigrationDialog';
|
||||||
import { AdvancedSettings } from './AdvancedSettings';
|
// import RefreshIcon from '~icons/material-symbols/refresh';
|
||||||
import { DEV_MODE_CLICK_TARGET, INCLUDE_MERGED_PRS, STATS_TOGGLE_KEY } from './constants';
|
|
||||||
import { ContributorCard } from './ContributorCard';
|
|
||||||
import DevMode from './DevMode';
|
import DevMode from './DevMode';
|
||||||
import { useBirthdayCelebration } from './useBirthdayCelebration';
|
import Preview from './Preview';
|
||||||
import { useDevMode } from './useDevMode';
|
|
||||||
|
|
||||||
const manifest = chrome.runtime.getManifest();
|
const manifest = chrome.runtime.getManifest();
|
||||||
|
|
||||||
|
const gitHubStatsService = new GitHubStatsService();
|
||||||
|
const includeMergedPRs = false;
|
||||||
|
|
||||||
|
const DISPLAY_PREVIEWS = false;
|
||||||
|
const PREVIEW_SECTION_DIV_CLASSNAME = DISPLAY_PREVIEWS ? 'w-1/2 space-y-4' : 'flex-grow space-y-4';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main Settings Component for managing user settings and preferences.
|
* Custom hook for enabling developer mode.
|
||||||
|
*
|
||||||
|
* @param targetCount - The target count to activate developer mode.
|
||||||
|
* @returns A tuple containing a boolean indicating if developer mode is active and a function to increment the count.
|
||||||
|
*/
|
||||||
|
const useDevMode = (targetCount: number): [boolean, () => void] => {
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
const [active, setActive] = useState(false);
|
||||||
|
const [lastClick, setLastClick] = useState(0);
|
||||||
|
|
||||||
|
const incrementCount = useCallback(() => {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastClick < 500) {
|
||||||
|
setCount(prevCount => {
|
||||||
|
const newCount = prevCount + 1;
|
||||||
|
if (newCount === targetCount) {
|
||||||
|
setActive(true);
|
||||||
|
}
|
||||||
|
return newCount;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setCount(1);
|
||||||
|
}
|
||||||
|
setLastClick(now);
|
||||||
|
}, [lastClick, targetCount]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => setCount(0), 3000);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [count]);
|
||||||
|
|
||||||
|
return [active, incrementCount];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component for managing user settings and preferences.
|
||||||
*
|
*
|
||||||
* @returns The Settings component.
|
* @returns The Settings component.
|
||||||
*/
|
*/
|
||||||
export default function Settings(): JSX.Element {
|
export default function Settings(): JSX.Element {
|
||||||
const gitHubStatsService = useMemo(() => new GitHubStatsService(), []);
|
const [_enableCourseStatusChips, setEnableCourseStatusChips] = useState<boolean>(false);
|
||||||
|
// const [_showTimeLocation, setShowTimeLocation] = useState<boolean>(false);
|
||||||
|
const [highlightConflicts, setHighlightConflicts] = useState<boolean>(false);
|
||||||
|
const [loadAllCourses, setLoadAllCourses] = useState<boolean>(false);
|
||||||
|
const [_enableDataRefreshing, setEnableDataRefreshing] = useState<boolean>(false);
|
||||||
|
const [calendarNewTab, setCalendarNewTab] = useState<boolean>(false);
|
||||||
|
const [increaseScheduleLimit, setIncreaseScheduleLimit] = useState<boolean>(false);
|
||||||
|
|
||||||
// State
|
const showMigrationDialog = useMigrationDialog();
|
||||||
const [highlightConflicts, setHighlightConflicts] = useState(false);
|
|
||||||
const [loadAllCourses, setLoadAllCourses] = useState(false);
|
// Toggle GitHub stats when the user presses the 'S' key
|
||||||
const [calendarNewTab, setCalendarNewTab] = useState(false);
|
const [showGitHubStats, setShowGitHubStats] = useState<boolean>(false);
|
||||||
const [increaseScheduleLimit, setIncreaseScheduleLimit] = useState(false);
|
|
||||||
const [showGitHubStats, setShowGitHubStats] = useState(false);
|
|
||||||
const [githubStats, setGitHubStats] = useState<Awaited<
|
const [githubStats, setGitHubStats] = useState<Awaited<
|
||||||
ReturnType<typeof gitHubStatsService.fetchGitHubStats>
|
ReturnType<typeof gitHubStatsService.fetchGitHubStats>
|
||||||
> | null>(null);
|
> | null>(null);
|
||||||
const [isDeveloper, setIsDeveloper] = useState(false);
|
|
||||||
|
|
||||||
const [activeSchedule] = useSchedules();
|
const [activeSchedule] = useSchedules();
|
||||||
|
// const [isRefreshing, setIsRefreshing] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [isDeveloper, setIsDeveloper] = useState<boolean>(false);
|
||||||
|
|
||||||
const showDialog = usePrompt();
|
const showDialog = usePrompt();
|
||||||
const handleChangelogOnClick = useChangelog();
|
const handleChangelogOnClick = useChangelog();
|
||||||
const showMigrationDialog = useMigrationDialog();
|
|
||||||
|
|
||||||
const [devMode, toggleDevMode] = useDevMode(DEV_MODE_CLICK_TARGET);
|
|
||||||
const { showParticles, particlesInit, particlesOptions, triggerCelebration, isBirthday } = useBirthdayCelebration();
|
|
||||||
|
|
||||||
// Initialize settings and listeners
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchGitHubStats = async () => {
|
const fetchGitHubStats = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -74,10 +121,19 @@ export default function Settings(): JSX.Element {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const initAndSetSettings = async () => {
|
const initAndSetSettings = async () => {
|
||||||
const { enableHighlightConflicts, enableScrollToLoad, alwaysOpenCalendarInNewTab, allowMoreSchedules } =
|
const {
|
||||||
await initSettings();
|
enableCourseStatusChips,
|
||||||
|
enableHighlightConflicts,
|
||||||
|
enableScrollToLoad,
|
||||||
|
enableDataRefreshing,
|
||||||
|
alwaysOpenCalendarInNewTab,
|
||||||
|
allowMoreSchedules,
|
||||||
|
} = await initSettings();
|
||||||
|
setEnableCourseStatusChips(enableCourseStatusChips);
|
||||||
|
// setShowTimeLocation(enableTimeAndLocationInPopup);
|
||||||
setHighlightConflicts(enableHighlightConflicts);
|
setHighlightConflicts(enableHighlightConflicts);
|
||||||
setLoadAllCourses(enableScrollToLoad);
|
setLoadAllCourses(enableScrollToLoad);
|
||||||
|
setEnableDataRefreshing(enableDataRefreshing);
|
||||||
setCalendarNewTab(alwaysOpenCalendarInNewTab);
|
setCalendarNewTab(alwaysOpenCalendarInNewTab);
|
||||||
setIncreaseScheduleLimit(allowMoreSchedules);
|
setIncreaseScheduleLimit(allowMoreSchedules);
|
||||||
};
|
};
|
||||||
@@ -87,50 +143,79 @@ export default function Settings(): JSX.Element {
|
|||||||
setIsDeveloper(isDev);
|
setIsDeveloper(isDev);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyPress = (event: KeyboardEvent) => {
|
|
||||||
if (event.key === STATS_TOGGLE_KEY || event.key === STATS_TOGGLE_KEY.toUpperCase()) {
|
|
||||||
setShowGitHubStats(prev => !prev);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Listeners
|
|
||||||
const ds_l1 = DevStore.listen('isDeveloper', async ({ newValue }) => {
|
const ds_l1 = DevStore.listen('isDeveloper', async ({ newValue }) => {
|
||||||
setIsDeveloper(newValue);
|
setIsDeveloper(newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
const l1 = OptionsStore.listen('enableHighlightConflicts', async ({ newValue }) => {
|
|
||||||
setHighlightConflicts(newValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
const l2 = OptionsStore.listen('enableScrollToLoad', async ({ newValue }) => {
|
|
||||||
setLoadAllCourses(newValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
const l3 = OptionsStore.listen('alwaysOpenCalendarInNewTab', async ({ newValue }) => {
|
|
||||||
setCalendarNewTab(newValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
const l4 = OptionsStore.listen('allowMoreSchedules', async ({ newValue }) => {
|
|
||||||
setIncreaseScheduleLimit(newValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('keydown', handleKeyPress);
|
|
||||||
|
|
||||||
initDS();
|
initDS();
|
||||||
fetchGitHubStats();
|
fetchGitHubStats();
|
||||||
initAndSetSettings();
|
initAndSetSettings();
|
||||||
|
|
||||||
|
const handleKeyPress = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'S' || event.key === 's') {
|
||||||
|
setShowGitHubStats(prev => !prev);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleKeyPress);
|
||||||
|
|
||||||
|
// Listen for changes in the settings
|
||||||
|
const l1 = OptionsStore.listen('enableCourseStatusChips', async ({ newValue }) => {
|
||||||
|
setEnableCourseStatusChips(newValue);
|
||||||
|
// console.log('enableCourseStatusChips', newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
// const l2 = OptionsStore.listen('enableTimeAndLocationInPopup', async ({ newValue }) => {
|
||||||
|
// setShowTimeLocation(newValue);
|
||||||
|
// // console.log('enableTimeAndLocationInPopup', newValue);
|
||||||
|
// });
|
||||||
|
|
||||||
|
const l2 = OptionsStore.listen('enableHighlightConflicts', async ({ newValue }) => {
|
||||||
|
setHighlightConflicts(newValue);
|
||||||
|
// console.log('enableHighlightConflicts', newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
const l3 = OptionsStore.listen('enableScrollToLoad', async ({ newValue }) => {
|
||||||
|
setLoadAllCourses(newValue);
|
||||||
|
// console.log('enableScrollToLoad', newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
const l4 = OptionsStore.listen('enableDataRefreshing', async ({ newValue }) => {
|
||||||
|
setEnableDataRefreshing(newValue);
|
||||||
|
// console.log('enableDataRefreshing', newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
const l5 = OptionsStore.listen('alwaysOpenCalendarInNewTab', async ({ newValue }) => {
|
||||||
|
setCalendarNewTab(newValue);
|
||||||
|
// console.log('alwaysOpenCalendarInNewTab', newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
const l6 = OptionsStore.listen('alwaysOpenCalendarInNewTab', async ({ newValue }) => {
|
||||||
|
setCalendarNewTab(newValue);
|
||||||
|
// console.log('alwaysOpenCalendarInNewTab', newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
const l7 = OptionsStore.listen('allowMoreSchedules', async ({ newValue }) => {
|
||||||
|
setIncreaseScheduleLimit(newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove listeners when the component is unmounted
|
||||||
return () => {
|
return () => {
|
||||||
OptionsStore.removeListener(l1);
|
OptionsStore.removeListener(l1);
|
||||||
OptionsStore.removeListener(l2);
|
OptionsStore.removeListener(l2);
|
||||||
OptionsStore.removeListener(l3);
|
OptionsStore.removeListener(l3);
|
||||||
OptionsStore.removeListener(l4);
|
OptionsStore.removeListener(l4);
|
||||||
|
OptionsStore.removeListener(l5);
|
||||||
|
OptionsStore.removeListener(l6);
|
||||||
|
OptionsStore.removeListener(l7);
|
||||||
|
|
||||||
DevStore.removeListener(ds_l1);
|
DevStore.removeListener(ds_l1);
|
||||||
|
|
||||||
window.removeEventListener('keydown', handleKeyPress);
|
window.removeEventListener('keydown', handleKeyPress);
|
||||||
};
|
};
|
||||||
}, [gitHubStatsService]);
|
}, []);
|
||||||
|
|
||||||
const handleEraseAll = useCallback(() => {
|
const handleEraseAll = () => {
|
||||||
showDialog({
|
showDialog({
|
||||||
title: 'Erase All Course/Schedule Data',
|
title: 'Erase All Course/Schedule Data',
|
||||||
description: (
|
description: (
|
||||||
@@ -157,9 +242,9 @@ export default function Settings(): JSX.Element {
|
|||||||
</Button>
|
</Button>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}, [showDialog]);
|
};
|
||||||
|
|
||||||
const handleImportClick = useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleImportClick = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = event.target.files?.[0];
|
const file = event.target.files?.[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
@@ -172,30 +257,16 @@ export default function Settings(): JSX.Element {
|
|||||||
console.error('Error importing schedule:', error);
|
console.error('Error importing schedule:', error);
|
||||||
alert('Failed to import schedule. Make sure the file is a valid .json format.');
|
alert('Failed to import schedule. Make sure the file is a valid .json format.');
|
||||||
}
|
}
|
||||||
}, []);
|
};
|
||||||
|
// const handleAddCourseByLink = async () => {
|
||||||
|
// // todo: Use a proper modal instead of a prompt
|
||||||
|
// const link: string | null = prompt('Enter course link');
|
||||||
|
// // Exit if the user cancels the prompt
|
||||||
|
// if (link === null) return;
|
||||||
|
// await addCourseByUrl(link, activeSchedule);
|
||||||
|
// };
|
||||||
|
|
||||||
const sortedContributors = useMemo(() => {
|
const [devMode, toggleDevMode] = useDevMode(10);
|
||||||
if (!githubStats) return LONGHORN_DEVELOPERS_SWE;
|
|
||||||
return [...LONGHORN_DEVELOPERS_SWE].sort(
|
|
||||||
(a, b) =>
|
|
||||||
(githubStats.userGitHubStats[b.githubUsername]?.commits ?? 0) -
|
|
||||||
(githubStats.userGitHubStats[a.githubUsername]?.commits ?? 0)
|
|
||||||
);
|
|
||||||
}, [githubStats]);
|
|
||||||
|
|
||||||
const additionalContributors = useMemo(() => {
|
|
||||||
if (!githubStats) return [];
|
|
||||||
return Object.keys(githubStats.userGitHubStats)
|
|
||||||
.filter(
|
|
||||||
username =>
|
|
||||||
!LONGHORN_DEVELOPERS_ADMINS.some(admin => admin.githubUsername === username) &&
|
|
||||||
!LONGHORN_DEVELOPERS_SWE.some(swe => swe.githubUsername === username)
|
|
||||||
)
|
|
||||||
.sort(
|
|
||||||
(a, b) =>
|
|
||||||
(githubStats.userGitHubStats[b]?.commits ?? 0) - (githubStats.userGitHubStats[a]?.commits ?? 0)
|
|
||||||
);
|
|
||||||
}, [githubStats]);
|
|
||||||
|
|
||||||
if (devMode) {
|
if (devMode) {
|
||||||
DevStore.set('isDeveloper', true);
|
DevStore.set('isDeveloper', true);
|
||||||
@@ -203,32 +274,13 @@ export default function Settings(): JSX.Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='relative'>
|
<div>
|
||||||
{particlesInit && showParticles && (
|
|
||||||
<Particles
|
|
||||||
id='birthday-particles'
|
|
||||||
options={particlesOptions}
|
|
||||||
className='pointer-events-none absolute inset-0 z-50'
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<header 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'>
|
<header 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'>
|
||||||
<LargeLogo />
|
<LargeLogo />
|
||||||
<Divider className='mx-2 self-center md:mx-4' size='2.5rem' orientation='vertical' />
|
<Divider className='mx-2 self-center md:mx-4' size='2.5rem' orientation='vertical' />
|
||||||
<div className='flex flex-1 items-center gap-2'>
|
<Text variant='h1' className='flex-1 text-ut-burntorange normal-case!'>
|
||||||
<Text variant='h1' className='text-ut-burntorange normal-case'>
|
|
||||||
Settings and Credits
|
Settings and Credits
|
||||||
</Text>
|
</Text>
|
||||||
{isBirthday && (
|
|
||||||
<span
|
|
||||||
onClick={triggerCelebration}
|
|
||||||
className='cursor-pointer px-4 text-sm text-ut-burntorange transition-transform hover:scale-110'
|
|
||||||
title='Click to celebrate!'
|
|
||||||
>
|
|
||||||
🎉 Happy Birthday LHD! 🎉
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className='hidden flex-row items-center justify-end gap-6 screenshot:hidden lg:flex'>
|
<div className='hidden flex-row items-center justify-end gap-6 screenshot:hidden lg:flex'>
|
||||||
<Button variant='minimal' color='theme-black' onClick={handleChangelogOnClick}>
|
<Button variant='minimal' color='theme-black' onClick={handleChangelogOnClick}>
|
||||||
<IconoirGitFork className='h-6 w-6 text-ut-gray' />
|
<IconoirGitFork className='h-6 w-6 text-ut-gray' />
|
||||||
@@ -249,19 +301,238 @@ export default function Settings(): JSX.Element {
|
|||||||
|
|
||||||
<div className='p-6 lg:flex'>
|
<div className='p-6 lg:flex'>
|
||||||
<div className='mr-4 lg:w-1/2 xl:w-xl'>
|
<div className='mr-4 lg:w-1/2 xl:w-xl'>
|
||||||
<AdvancedSettings
|
{/* <section className='mb-8'>
|
||||||
highlightConflicts={highlightConflicts}
|
<h2 className='mb-4 text-xl text-ut-black font-semibold'>CUSTOMIZATION OPTIONS</h2>
|
||||||
setHighlightConflicts={setHighlightConflicts}
|
<div className='flex space-x-4'>
|
||||||
loadAllCourses={loadAllCourses}
|
<div className='w-1/2 space-y-4'>
|
||||||
setLoadAllCourses={setLoadAllCourses}
|
<div className='flex items-center justify-between'>
|
||||||
increaseScheduleLimit={increaseScheduleLimit}
|
<div className='max-w-xs'>
|
||||||
setIncreaseScheduleLimit={setIncreaseScheduleLimit}
|
<h3 className='text-ut-burntorange font-semibold'>Show Course Status</h3>
|
||||||
calendarNewTab={calendarNewTab}
|
<p className='text-sm text-gray-600'>
|
||||||
setCalendarNewTab={setCalendarNewTab}
|
Shows an indicator for waitlisted, cancelled, and closed courses.
|
||||||
activeSchedule={activeSchedule}
|
</p>
|
||||||
handleEraseAll={handleEraseAll}
|
</div>
|
||||||
handleImportClick={handleImportClick}
|
<SwitchButton
|
||||||
|
isChecked={enableCourseStatusChips}
|
||||||
|
onChange={() => {
|
||||||
|
setEnableCourseStatusChips(!enableCourseStatusChips);
|
||||||
|
OptionsStore.set('enableCourseStatusChips', !enableCourseStatusChips);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider size='auto' orientation='horizontal' />
|
||||||
|
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<div className='max-w-xs'>
|
||||||
|
<h3 className='text-ut-burntorange font-semibold'>
|
||||||
|
Show Time & Location in Popup
|
||||||
|
</h3>
|
||||||
|
<p className='text-sm text-gray-600'>
|
||||||
|
Shows the course's time and location in the extension's popup.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<SwitchButton
|
||||||
|
isChecked={showTimeLocation}
|
||||||
|
onChange={() => {
|
||||||
|
setShowTimeLocation(!showTimeLocation);
|
||||||
|
OptionsStore.set('enableTimeAndLocationInPopup', !showTimeLocation);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{DISPLAY_PREVIEWS && (
|
||||||
|
<Preview>
|
||||||
|
<CalendarCourseCell
|
||||||
|
colors={getCourseColors('orange')}
|
||||||
|
courseDeptAndInstr={ExampleCourse.department}
|
||||||
|
className={ExampleCourse.number}
|
||||||
|
status={ExampleCourse.status}
|
||||||
|
timeAndLocation={ExampleCourse.schedule.meetings[0]!.getTimeString({
|
||||||
|
separator: '-',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<PopupCourseBlock colors={getCourseColors('orange')} course={ExampleCourse} />
|
||||||
|
</Preview>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Divider size='auto' orientation='horizontal' /> */}
|
||||||
|
|
||||||
|
<section className='mb-8'>
|
||||||
|
<h2 className='mb-4 text-xl text-ut-black font-semibold'>ADVANCED SETTINGS</h2>
|
||||||
|
<div className='flex space-x-4'>
|
||||||
|
<div className={PREVIEW_SECTION_DIV_CLASSNAME}>
|
||||||
|
{/* <div className='flex items-center justify-between'>
|
||||||
|
<div className='max-w-xs'>
|
||||||
|
<h3 className='text-ut-burntorange font-semibold'>Refresh Data</h3>
|
||||||
|
<p className='text-sm text-gray-600'>
|
||||||
|
Refreshes waitlist, course status, and other info with the latest data from
|
||||||
|
UT's site.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='ut-black'
|
||||||
|
icon={RefreshIcon}
|
||||||
|
onClick={() => console.log('Refresh clicked')}
|
||||||
|
disabled={!enableDataRefreshing}
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider size='auto' orientation='horizontal' /> */}
|
||||||
|
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<div className='max-w-xs'>
|
||||||
|
<Text variant='h4' className='text-ut-burntorange font-semibold'>
|
||||||
|
Export Current Schedule
|
||||||
|
</Text>
|
||||||
|
<p className='text-sm text-gray-600'>
|
||||||
|
Backup your active schedule to a portable file
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='ut-burntorange'
|
||||||
|
onClick={() => handleExportJson(activeSchedule.id)}
|
||||||
|
>
|
||||||
|
Export
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider size='auto' orientation='horizontal' />
|
||||||
|
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<div className='max-w-xs'>
|
||||||
|
<Text variant='h4' className='text-ut-burntorange font-semibold'>
|
||||||
|
Import Schedule
|
||||||
|
</Text>
|
||||||
|
<p className='text-sm text-gray-600'>Import from a schedule file</p>
|
||||||
|
</div>
|
||||||
|
<FileUpload
|
||||||
|
variant='filled'
|
||||||
|
color='ut-burntorange'
|
||||||
|
onChange={handleImportClick}
|
||||||
|
accept={MIMEType.JSON}
|
||||||
|
>
|
||||||
|
Import Schedule
|
||||||
|
</FileUpload>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider size='auto' orientation='horizontal' />
|
||||||
|
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<div className='max-w-xs'>
|
||||||
|
<Text variant='h4' className='text-ut-burntorange font-semibold'>
|
||||||
|
Course Conflict Highlight
|
||||||
|
</Text>
|
||||||
|
<p className='text-sm text-gray-600'>
|
||||||
|
Adds a red strikethrough to courses that have conflicting times.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<SwitchButton
|
||||||
|
isChecked={highlightConflicts}
|
||||||
|
onChange={() => {
|
||||||
|
setHighlightConflicts(!highlightConflicts);
|
||||||
|
OptionsStore.set('enableHighlightConflicts', !highlightConflicts);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider size='auto' orientation='horizontal' />
|
||||||
|
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<div className='max-w-xs'>
|
||||||
|
<Text variant='h4' className='text-ut-burntorange font-semibold'>
|
||||||
|
Load All Courses in Course Schedule
|
||||||
|
</Text>
|
||||||
|
<p className='text-sm text-gray-600'>
|
||||||
|
Loads all courses in the Course Schedule site by scrolling, instead of using
|
||||||
|
next/prev page buttons.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<SwitchButton
|
||||||
|
isChecked={loadAllCourses}
|
||||||
|
onChange={() => {
|
||||||
|
setLoadAllCourses(!loadAllCourses);
|
||||||
|
OptionsStore.set('enableScrollToLoad', !loadAllCourses);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<div className='max-w-xs'>
|
||||||
|
<Text variant='h4' className='text-ut-burntorange font-semibold'>
|
||||||
|
Allow more than 10 schedules
|
||||||
|
</Text>
|
||||||
|
<p className='text-sm text-gray-600'>
|
||||||
|
Allow bypassing the 10-schedule limit. Intended for advisors or staff who
|
||||||
|
need to create many schedules on behalf of students.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<SwitchButton
|
||||||
|
isChecked={increaseScheduleLimit}
|
||||||
|
onChange={() => {
|
||||||
|
setIncreaseScheduleLimit(!increaseScheduleLimit);
|
||||||
|
OptionsStore.set('allowMoreSchedules', !increaseScheduleLimit);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider size='auto' orientation='horizontal' />
|
||||||
|
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<div className='max-w-xs'>
|
||||||
|
<Text variant='h4' className='text-ut-burntorange font-semibold'>
|
||||||
|
Always Open Calendar in New Tab
|
||||||
|
</Text>
|
||||||
|
<p className='text-sm text-gray-600'>
|
||||||
|
Always opens the calendar view in a new tab when navigating to the calendar
|
||||||
|
page. May prevent issues where the calendar refuses to open.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<SwitchButton
|
||||||
|
isChecked={calendarNewTab}
|
||||||
|
onChange={() => {
|
||||||
|
setCalendarNewTab(!calendarNewTab);
|
||||||
|
OptionsStore.set('alwaysOpenCalendarInNewTab', !calendarNewTab);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider size='auto' orientation='horizontal' />
|
||||||
|
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<div className='max-w-xs'>
|
||||||
|
<Text variant='h4' className='text-ut-burntorange font-semibold'>
|
||||||
|
Reset All Data
|
||||||
|
</Text>
|
||||||
|
<p className='text-sm text-gray-600'>
|
||||||
|
Erases all schedules and courses you have.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button variant='outline' color='theme-red' icon={Trash} onClick={handleEraseAll}>
|
||||||
|
Erase All
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{DISPLAY_PREVIEWS && (
|
||||||
|
<Preview>
|
||||||
|
<Text
|
||||||
|
variant='h2-course'
|
||||||
|
className={clsx('text-center text-theme-red !font-normal', {
|
||||||
|
'line-through': highlightConflicts,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
01234 MWF 10:00 AM - 11:00 AM UTC 1.234
|
||||||
|
</Text>
|
||||||
|
</Preview>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<Divider size='auto' orientation='horizontal' />
|
<Divider size='auto' orientation='horizontal' />
|
||||||
|
|
||||||
@@ -322,21 +593,17 @@ export default function Settings(): JSX.Element {
|
|||||||
Open Debug Page
|
Open Debug Page
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<Divider size='auto' orientation='horizontal' />
|
<Divider size='auto' orientation='horizontal' />
|
||||||
|
|
||||||
<Button
|
<Button variant='filled' color='ut-black' onClick={() => addCourseByURL(activeSchedule)}>
|
||||||
variant='filled'
|
|
||||||
color='ut-black'
|
|
||||||
onClick={() => addCourseByURL(activeSchedule)}
|
|
||||||
>
|
|
||||||
Add course by link
|
Add course by link
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='filled' color='ut-burntorange' onClick={showMigrationDialog}>
|
<Button variant='filled' color='ut-burntorange' onClick={showMigrationDialog}>
|
||||||
Show Migration Dialog
|
Show Migration Dialog
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -349,43 +616,143 @@ export default function Settings(): JSX.Element {
|
|||||||
</h2>
|
</h2>
|
||||||
<div className='grid grid-cols-2 gap-4 2xl:grid-cols-4 md:grid-cols-3'>
|
<div className='grid grid-cols-2 gap-4 2xl:grid-cols-4 md:grid-cols-3'>
|
||||||
{LONGHORN_DEVELOPERS_ADMINS.map(admin => (
|
{LONGHORN_DEVELOPERS_ADMINS.map(admin => (
|
||||||
<ContributorCard
|
<div
|
||||||
key={admin.githubUsername}
|
key={admin.githubUsername}
|
||||||
name={admin.name}
|
className='border border-gray-300 rounded bg-ut-gray/10 p-4'
|
||||||
githubUsername={admin.githubUsername}
|
>
|
||||||
roles={admin.role}
|
<Text
|
||||||
stats={githubStats?.adminGitHubStats[admin.githubUsername]}
|
variant='p'
|
||||||
showStats={showGitHubStats}
|
className='text-ut-burntorange font-semibold hover:cursor-pointer'
|
||||||
includeMergedPRs={INCLUDE_MERGED_PRS}
|
onClick={() =>
|
||||||
/>
|
window.open(`https://github.com/${admin.githubUsername}`, '_blank')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{admin.name}
|
||||||
|
</Text>
|
||||||
|
{admin.role.map(role => (
|
||||||
|
<p key={admin.githubUsername} className='text-sm text-gray-600'>
|
||||||
|
{role}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
{showGitHubStats && githubStats && (
|
||||||
|
<div className='mt-2'>
|
||||||
|
<p className='text-xs text-gray-500'>GitHub Stats (UTRP repo):</p>
|
||||||
|
{includeMergedPRs && (
|
||||||
|
<p className='text-xs'>
|
||||||
|
Merged PRS:{' '}
|
||||||
|
{githubStats.adminGitHubStats[admin.githubUsername]?.mergedPRs}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<p className='text-xs'>
|
||||||
|
Commits: {githubStats.adminGitHubStats[admin.githubUsername]?.commits}
|
||||||
|
</p>
|
||||||
|
<p className='text-xs text-ut-green'>
|
||||||
|
{githubStats.adminGitHubStats[admin.githubUsername]?.linesAdded} ++
|
||||||
|
</p>
|
||||||
|
<p className='text-xs text-theme-red'>
|
||||||
|
{githubStats.adminGitHubStats[admin.githubUsername]?.linesDeleted} --
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className='my-8'>
|
<section className='my-8'>
|
||||||
<h2 className='mb-4 text-xl text-ut-black font-semibold'>UTRP CONTRIBUTORS</h2>
|
<h2 className='mb-4 text-xl text-ut-black font-semibold'>UTRP CONTRIBUTORS</h2>
|
||||||
<div className='grid grid-cols-2 gap-4 2xl:grid-cols-4 md:grid-cols-3 xl:grid-cols-3'>
|
<div className='grid grid-cols-2 gap-4 2xl:grid-cols-4 md:grid-cols-3 xl:grid-cols-3'>
|
||||||
{sortedContributors.map(swe => (
|
{LONGHORN_DEVELOPERS_SWE.sort(
|
||||||
<ContributorCard
|
(a, b) =>
|
||||||
|
(githubStats?.userGitHubStats[b.githubUsername]?.commits ?? 0) -
|
||||||
|
(githubStats?.userGitHubStats[a.githubUsername]?.commits ?? 0)
|
||||||
|
).map(swe => (
|
||||||
|
<div
|
||||||
key={swe.githubUsername}
|
key={swe.githubUsername}
|
||||||
name={swe.name}
|
className='border border-gray-300 rounded bg-ut-gray/10 p-4'
|
||||||
githubUsername={swe.githubUsername}
|
>
|
||||||
roles={swe.role}
|
<Text
|
||||||
stats={githubStats?.userGitHubStats[swe.githubUsername]}
|
variant='p'
|
||||||
showStats={showGitHubStats}
|
className='text-ut-burntorange font-semibold hover:cursor-pointer'
|
||||||
includeMergedPRs={INCLUDE_MERGED_PRS}
|
onClick={() =>
|
||||||
/>
|
window.open(`https://github.com/${swe.githubUsername}`, '_blank')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{swe.name}
|
||||||
|
</Text>
|
||||||
|
{swe.role.map(role => (
|
||||||
|
<p key={swe.githubUsername} className='text-sm text-gray-600'>
|
||||||
|
{role}
|
||||||
|
</p>
|
||||||
))}
|
))}
|
||||||
{additionalContributors.map(username => (
|
{showGitHubStats && githubStats && (
|
||||||
<ContributorCard
|
<div className='mt-2'>
|
||||||
|
<p className='text-xs text-gray-500'>GitHub Stats (UTRP repo):</p>
|
||||||
|
{includeMergedPRs && (
|
||||||
|
<p className='text-xs'>
|
||||||
|
Merged PRS:{' '}
|
||||||
|
{githubStats.userGitHubStats[swe.githubUsername]?.mergedPRs}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<p className='text-xs'>
|
||||||
|
Commits: {githubStats.userGitHubStats[swe.githubUsername]?.commits}
|
||||||
|
</p>
|
||||||
|
<p className='text-xs text-ut-green'>
|
||||||
|
{githubStats.userGitHubStats[swe.githubUsername]?.linesAdded} ++
|
||||||
|
</p>
|
||||||
|
<p className='text-xs text-theme-red'>
|
||||||
|
{githubStats.userGitHubStats[swe.githubUsername]?.linesDeleted} --
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{githubStats &&
|
||||||
|
Object.keys(githubStats.userGitHubStats)
|
||||||
|
.filter(
|
||||||
|
username =>
|
||||||
|
!LONGHORN_DEVELOPERS_ADMINS.some(
|
||||||
|
admin => admin.githubUsername === username
|
||||||
|
) && !LONGHORN_DEVELOPERS_SWE.some(swe => swe.githubUsername === username)
|
||||||
|
)
|
||||||
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
(githubStats.userGitHubStats[b]?.commits ?? 0) -
|
||||||
|
(githubStats.userGitHubStats[a]?.commits ?? 0)
|
||||||
|
)
|
||||||
|
.map(username => (
|
||||||
|
<div
|
||||||
key={username}
|
key={username}
|
||||||
name={githubStats!.names[username] || username}
|
className='overflow-clip border border-gray-300 rounded bg-ut-gray/10 p-4'
|
||||||
githubUsername={username}
|
>
|
||||||
roles={['Contributor']}
|
<Text
|
||||||
stats={githubStats!.userGitHubStats[username]}
|
variant='p'
|
||||||
showStats={showGitHubStats}
|
className='text-ut-burntorange font-semibold hover:cursor-pointer'
|
||||||
includeMergedPRs={INCLUDE_MERGED_PRS}
|
onClick={() => window.open(`https://github.com/${username}`, '_blank')}
|
||||||
/>
|
>
|
||||||
|
{githubStats.names[username]}
|
||||||
|
</Text>
|
||||||
|
<p className='text-sm text-gray-600'>Contributor</p>
|
||||||
|
{showGitHubStats && (
|
||||||
|
<div className='mt-2'>
|
||||||
|
<p className='text-xs text-gray-500'>GitHub Stats (UTRP repo):</p>
|
||||||
|
{includeMergedPRs && (
|
||||||
|
<p className='text-xs'>
|
||||||
|
Merged PRs:{' '}
|
||||||
|
{githubStats.userGitHubStats[username]?.mergedPRs}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<p className='text-xs'>
|
||||||
|
Commits: {githubStats.userGitHubStats[username]?.commits}
|
||||||
|
</p>
|
||||||
|
<p className='text-xs text-ut-green'>
|
||||||
|
{githubStats.userGitHubStats[username]?.linesAdded} ++
|
||||||
|
</p>
|
||||||
|
<p className='text-xs text-theme-red'>
|
||||||
|
{githubStats.userGitHubStats[username]?.linesDeleted} --
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
export const DISPLAY_PREVIEWS = false;
|
|
||||||
export const PREVIEW_SECTION_DIV_CLASSNAME = DISPLAY_PREVIEWS ? 'w-1/2 space-y-4' : 'flex-grow space-y-4';
|
|
||||||
|
|
||||||
export const STATS_TOGGLE_KEY = 's';
|
|
||||||
export const INCLUDE_MERGED_PRS = false;
|
|
||||||
export const DEV_MODE_CLICK_TARGET = 5;
|
|
||||||
export const DEV_MODE_CLICK_TIMEOUT = 5000;
|
|
||||||
export const DEV_MODE_CLICK_INTERVAL = 500;
|
|
||||||
|
|
||||||
// LHD Birthday: January 9th, 2025
|
|
||||||
export const LHD_BIRTHDAY = { month: 0, day: 9 };
|
|
||||||
export const BIRTHDAY_CELEBRATION_DURATION = 5000;
|
|
||||||
export const BIRTHDAY_CELEBRATION_DEBOUNCE = 2000;
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
import type { Engine, ISourceOptions } from '@tsparticles/engine';
|
|
||||||
import { initParticlesEngine } from '@tsparticles/react';
|
|
||||||
import { loadSlim } from '@tsparticles/slim';
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
||||||
|
|
||||||
import { BIRTHDAY_CELEBRATION_DEBOUNCE, BIRTHDAY_CELEBRATION_DURATION, LHD_BIRTHDAY } from './constants';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom hook for birthday celebration particles
|
|
||||||
*/
|
|
||||||
export const useBirthdayCelebration = () => {
|
|
||||||
const [showParticles, setShowParticles] = useState(false);
|
|
||||||
const [particlesInit, setParticlesInit] = useState(false);
|
|
||||||
const [lastCelebration, setLastCelebration] = useState(0);
|
|
||||||
|
|
||||||
const isBirthday = useMemo(() => {
|
|
||||||
const today = new Date();
|
|
||||||
return today.getMonth() === LHD_BIRTHDAY.month && today.getDate() === LHD_BIRTHDAY.day;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
initParticlesEngine(async (engine: Engine) => {
|
|
||||||
await loadSlim(engine);
|
|
||||||
}).then(() => {
|
|
||||||
setParticlesInit(true);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const triggerCelebration = useCallback(() => {
|
|
||||||
if (!isBirthday) return;
|
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
// Debounce: prevent triggering again within BIRTHDAY_CELEBRATION_DEBOUNCE ms
|
|
||||||
if (now - lastCelebration < BIRTHDAY_CELEBRATION_DEBOUNCE) return;
|
|
||||||
|
|
||||||
setLastCelebration(now);
|
|
||||||
setShowParticles(true);
|
|
||||||
setTimeout(() => setShowParticles(false), BIRTHDAY_CELEBRATION_DURATION);
|
|
||||||
}, [isBirthday, lastCelebration]);
|
|
||||||
|
|
||||||
const particlesOptions: ISourceOptions = useMemo(
|
|
||||||
() => ({
|
|
||||||
fullScreen: { enable: true, zIndex: 1 },
|
|
||||||
particles: {
|
|
||||||
color: { value: ['#BF5700', '#333F48', '#FFFFFF'] }, // UT colors
|
|
||||||
move: {
|
|
||||||
direction: 'bottom',
|
|
||||||
enable: true,
|
|
||||||
outModes: {
|
|
||||||
default: 'out',
|
|
||||||
},
|
|
||||||
size: true,
|
|
||||||
speed: {
|
|
||||||
min: 1,
|
|
||||||
max: 3,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
number: {
|
|
||||||
value: 500,
|
|
||||||
density: {
|
|
||||||
enable: true,
|
|
||||||
area: 800,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
opacity: {
|
|
||||||
value: 1,
|
|
||||||
animation: {
|
|
||||||
enable: false,
|
|
||||||
startValue: 'max',
|
|
||||||
destroy: 'min',
|
|
||||||
speed: 0.3,
|
|
||||||
sync: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rotate: {
|
|
||||||
value: {
|
|
||||||
min: 0,
|
|
||||||
max: 360,
|
|
||||||
},
|
|
||||||
direction: 'random',
|
|
||||||
move: true,
|
|
||||||
animation: {
|
|
||||||
enable: true,
|
|
||||||
speed: 60,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tilt: {
|
|
||||||
direction: 'random',
|
|
||||||
enable: true,
|
|
||||||
move: true,
|
|
||||||
value: {
|
|
||||||
min: 0,
|
|
||||||
max: 360,
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
enable: true,
|
|
||||||
speed: 60,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
shape: {
|
|
||||||
type: ['circle', 'square'],
|
|
||||||
options: {},
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
value: {
|
|
||||||
min: 2,
|
|
||||||
max: 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
roll: {
|
|
||||||
darken: {
|
|
||||||
enable: true,
|
|
||||||
value: 30,
|
|
||||||
},
|
|
||||||
enlighten: {
|
|
||||||
enable: true,
|
|
||||||
value: 30,
|
|
||||||
},
|
|
||||||
enable: true,
|
|
||||||
speed: {
|
|
||||||
min: 15,
|
|
||||||
max: 25,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wobble: {
|
|
||||||
distance: 30,
|
|
||||||
enable: true,
|
|
||||||
move: true,
|
|
||||||
speed: {
|
|
||||||
min: -15,
|
|
||||||
max: 15,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
return { showParticles, particlesInit, particlesOptions, triggerCelebration, isBirthday };
|
|
||||||
};
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import { DEV_MODE_CLICK_INTERVAL, DEV_MODE_CLICK_TIMEOUT } from './constants';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom hook for enabling developer mode via rapid clicking
|
|
||||||
*/
|
|
||||||
export const useDevMode = (targetCount: number): [boolean, () => void] => {
|
|
||||||
const [count, setCount] = useState(0);
|
|
||||||
const [active, setActive] = useState(false);
|
|
||||||
const [lastClick, setLastClick] = useState(0);
|
|
||||||
|
|
||||||
const incrementCount = useCallback(() => {
|
|
||||||
const now = Date.now();
|
|
||||||
if (now - lastClick < DEV_MODE_CLICK_INTERVAL) {
|
|
||||||
setCount(prevCount => {
|
|
||||||
const newCount = prevCount + 1;
|
|
||||||
if (newCount === targetCount) {
|
|
||||||
setActive(true);
|
|
||||||
}
|
|
||||||
return newCount;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setCount(1);
|
|
||||||
}
|
|
||||||
setLastClick(now);
|
|
||||||
}, [lastClick, targetCount]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const timer = setTimeout(() => setCount(0), DEV_MODE_CLICK_TIMEOUT);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}, [count]);
|
|
||||||
|
|
||||||
return [active, incrementCount];
|
|
||||||
};
|
|
||||||
@@ -88,31 +88,6 @@ const fixManifestOptionsPage = (): Plugin => ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function getGitInfo() {
|
|
||||||
// Try environment variables first (for Nix builds)
|
|
||||||
if (process.env.VITE_GIT_BRANCH && process.env.VITE_GIT_COMMIT) {
|
|
||||||
return {
|
|
||||||
gitBranch: process.env.VITE_GIT_BRANCH,
|
|
||||||
gitCommit: process.env.VITE_GIT_COMMIT,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to git commands (for local development)
|
|
||||||
try {
|
|
||||||
return {
|
|
||||||
gitBranch: execSync('git rev-parse --abbrev-ref HEAD').toString().trim(),
|
|
||||||
gitCommit: execSync('git rev-parse --short HEAD').toString().trim(),
|
|
||||||
};
|
|
||||||
} catch {
|
|
||||||
return {
|
|
||||||
gitBranch: 'unknown',
|
|
||||||
gitCommit: 'unknown',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const gitInfo = getGitInfo();
|
|
||||||
|
|
||||||
let config: ResolvedConfig;
|
let config: ResolvedConfig;
|
||||||
let server: ViteDevServer;
|
let server: ViteDevServer;
|
||||||
|
|
||||||
@@ -205,14 +180,12 @@ export default defineConfig({
|
|||||||
'PROD',
|
'PROD',
|
||||||
'VITE_SENTRY_ENVIRONMENT',
|
'VITE_SENTRY_ENVIRONMENT',
|
||||||
'VITE_BETA_BUILD',
|
'VITE_BETA_BUILD',
|
||||||
'VITE_GIT_BRANCH',
|
|
||||||
'VITE_GIT_COMMIT',
|
|
||||||
],
|
],
|
||||||
includeTimestamp: true,
|
includeTimestamp: true,
|
||||||
includeBuildTime: true,
|
includeBuildTime: true,
|
||||||
customMetadata: {
|
customMetadata: {
|
||||||
gitBranch: () => gitInfo.gitBranch,
|
gitBranch: () => execSync('git rev-parse --abbrev-ref HEAD').toString().trim(),
|
||||||
gitCommit: () => gitInfo.gitCommit,
|
gitCommit: () => execSync('git rev-parse --short HEAD').toString().trim(),
|
||||||
nodeVersion: () => process.version,
|
nodeVersion: () => process.version,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user