Compare commits
3 Commits
DereC4-der
...
legacy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a642d0d9d2 | ||
|
|
17e0ac9465 | ||
|
|
8cdd04f0b4 |
@@ -1,9 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
@@ -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
|
||||
217
.eslintrc.cjs
@@ -1,217 +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',
|
||||
'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',
|
||||
'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': [
|
||||
'warn',
|
||||
{
|
||||
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',
|
||||
],
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'@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',
|
||||
},
|
||||
};
|
||||
43
.github/workflows/best-practices.yml
vendored
@@ -1,43 +0,0 @@
|
||||
name: Best Practices
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Run ESLint
|
||||
run: pnpm run lint
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Run Prettier
|
||||
run: pnpm run prettier
|
||||
24
.github/workflows/check-types.yml
vendored
@@ -1,24 +0,0 @@
|
||||
name: Type Check
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
type-check:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Run tests
|
||||
run: pnpm run check-types
|
||||
26
.github/workflows/chromatic.yml
vendored
@@ -1,26 +0,0 @@
|
||||
name: 'Chromatic'
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
chromatic:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Publish to Chromatic
|
||||
uses: chromaui/action@latest
|
||||
with:
|
||||
projectToken: chpt_e8bd07b0b27d8eb
|
||||
exitZeroOnChanges: true
|
||||
autoAcceptChanges: 'main'
|
||||
25
.github/workflows/release.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: Create Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- production
|
||||
- preview
|
||||
jobs:
|
||||
build:
|
||||
name: build extension & create release
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Get file permission
|
||||
run: chmod -R 777 .
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Release with semantic-release
|
||||
id: semantic-release
|
||||
run: npx --no-install semantic-release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
24
.github/workflows/tests.yml
vendored
@@ -1,24 +0,0 @@
|
||||
name: Tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Run tests
|
||||
run: pnpm test
|
||||
43
.github/workflows/validate-pr.yml
vendored
@@ -1,43 +0,0 @@
|
||||
name: Validate PR Title
|
||||
|
||||
# thank you ben limmer for this workflow:
|
||||
# https://github.com/blimmer/semantic-release-demo-2/blob/main/.github/workflows/lint-pr.yml
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v3.2.6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Post Conventional Commit Comment (on failure)
|
||||
uses: jungwinter/comment@v1
|
||||
id: conventional-commit-help
|
||||
with:
|
||||
type: create
|
||||
issue_number: ${{ github.event.pull_request.number }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
body: |
|
||||
Your pull request title did not conform to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) standards. Our upcoming automated release pipeline will automatically determine
|
||||
the proper release version based on your pull request title.
|
||||
**Cheat Sheet**
|
||||
- feat: A new feature
|
||||
- fix: A bug fix
|
||||
- docs: Documentation only changes
|
||||
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
|
||||
- refactor: A code change that neither fixes a bug nor adds a feature
|
||||
- perf: A code change that improves performance
|
||||
- test: Adding missing tests or correcting existing tests
|
||||
- build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
|
||||
- ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
|
||||
- chore: Other changes that don't modify src or test files
|
||||
- revert: Reverts a previous commit
|
||||
if: ${{ failure() }}
|
||||
212
.gitignore
vendored
@@ -1,212 +0,0 @@
|
||||
# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,node,react,storybookjs
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,macos,node,react,storybookjs
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### macOS Patch ###
|
||||
# iCloud generated files
|
||||
*.icloud
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
### Node Patch ###
|
||||
# Serverless Webpack directories
|
||||
.webpack/
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
# SvelteKit build / generate output
|
||||
.svelte-kit
|
||||
|
||||
### react ###
|
||||
.DS_*
|
||||
**/*.backup.*
|
||||
**/*.back.*
|
||||
|
||||
node_modules
|
||||
|
||||
*.sublime*
|
||||
|
||||
psd
|
||||
thumb
|
||||
sketch
|
||||
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
.ionide
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,node,react,storybookjs
|
||||
|
||||
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
|
||||
package-lock.json
|
||||
storybook-static/
|
||||
@@ -1 +0,0 @@
|
||||
npx --no -- commitlint --edit $1
|
||||
@@ -1,19 +0,0 @@
|
||||
*.css
|
||||
# macOS
|
||||
.DS_Store
|
||||
# Webpack-built output
|
||||
/dist
|
||||
|
||||
# Extension archives
|
||||
/build
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Dependency directories and management
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
package.json
|
||||
package-lock.json
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"useTabs": false,
|
||||
"printWidth": 120,
|
||||
"tabWidth": 4,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false,
|
||||
"semi": true,
|
||||
"jsxSingleQuote": true
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"branches": [
|
||||
"production",
|
||||
{
|
||||
"name": "preview",
|
||||
"channel": "alpha",
|
||||
"prerelease": "alpha"
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
"@semantic-release/commit-analyzer",
|
||||
{
|
||||
"preset": "conventionalcommits"
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/release-notes-generator",
|
||||
{
|
||||
"preset": "conventionalcommits"
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/exec",
|
||||
{
|
||||
"prepareCmd": "SEMANTIC_VERSION=${nextRelease.version} npm run build"
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/github",
|
||||
{
|
||||
"assets": "build/**/artifacts/*.*",
|
||||
"failComment": false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import type { StorybookConfig } from '@storybook/react-vite';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
addons: [
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-designs',
|
||||
'@storybook/test',
|
||||
'@chromatic-com/storybook',
|
||||
],
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
options: {
|
||||
builder: {
|
||||
viteConfigPath: '.storybook/vite-storybook.config.ts',
|
||||
},
|
||||
},
|
||||
},
|
||||
docs: {
|
||||
autodocs: 'tag',
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
@@ -1,178 +0,0 @@
|
||||
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
||||
import type { Preview } from '@storybook/react';
|
||||
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
||||
import React from 'react';
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
Story => (
|
||||
<React.StrictMode>
|
||||
<ExtensionRoot>
|
||||
<Story />
|
||||
</ExtensionRoot>
|
||||
</React.StrictMode>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
let localData = {};
|
||||
type ListenerFunction = (
|
||||
changes: { [key: string]: chrome.storage.StorageChange },
|
||||
areaName: chrome.storage.AreaName
|
||||
) => void;
|
||||
const localDataListeners = new Map<
|
||||
ListenerFunction, // key to remove listener
|
||||
(changes: { [key: string]: chrome.storage.StorageChange }) => void
|
||||
>();
|
||||
|
||||
// mock chrome api
|
||||
globalThis.chrome = {
|
||||
storage: {
|
||||
local: {
|
||||
/**
|
||||
* Removes all items from storage.
|
||||
* @param callback Optional.
|
||||
* Callback on success, or on failure (in which case runtime.lastError will be set).
|
||||
*/
|
||||
async clear() {
|
||||
localData = {};
|
||||
},
|
||||
/**
|
||||
* Gets one or more items from storage.
|
||||
* @param keys A single key to get, list of keys to get, or a dictionary specifying default values.
|
||||
* An empty list or object will return an empty result object. Pass in null to get the entire contents of storage.
|
||||
* @return A Promise that resolves with an object containing items
|
||||
*/
|
||||
async get(keys?: string | string[] | { [key: string]: any } | null) {
|
||||
if (keys === null) {
|
||||
return localData;
|
||||
}
|
||||
if (Array.isArray(keys)) {
|
||||
return keys.reduce((acc, key) => {
|
||||
acc[key] = localData[key];
|
||||
return acc;
|
||||
}, {} as string); // funny types
|
||||
}
|
||||
if (typeof keys === 'string') {
|
||||
return { [keys]: localData[keys] };
|
||||
}
|
||||
return keys;
|
||||
},
|
||||
/**
|
||||
* Gets the amount of space (in bytes) being used by one or more items.
|
||||
* @param keys Optional. A single key or list of keys to get the total usage for. An empty list will return 0. Pass in null to get the total usage of all of storage.
|
||||
* @param callback Callback with the amount of space being used by storage, or on failure (in which case runtime.lastError will be set).
|
||||
* Parameter bytesInUse: Amount of space being used in storage, in bytes.
|
||||
*/
|
||||
async getBytesInUse() {
|
||||
return 0;
|
||||
},
|
||||
/**
|
||||
* Removes one or more items from storage.
|
||||
* @param keys A single key or a list of keys for items to remove.
|
||||
* @param callback Optional.
|
||||
* Callback on success, or on failure (in which case runtime.lastError will be set).
|
||||
*/
|
||||
async remove(keys: string | string[]) {
|
||||
if (Array.isArray(keys)) {
|
||||
keys.forEach(key => {
|
||||
for (const listener of localDataListeners.values()) {
|
||||
listener({ [key]: { oldValue: localData[key], newValue: undefined } });
|
||||
}
|
||||
|
||||
delete localData[key];
|
||||
});
|
||||
} else {
|
||||
for (const listener of localDataListeners.values()) {
|
||||
listener({ [keys]: { oldValue: localData[keys], newValue: undefined } });
|
||||
}
|
||||
|
||||
delete localData[keys];
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Sets multiple items.
|
||||
* @param items An object which gives each key/value pair to update storage with. Any other key/value pairs in storage will not be affected.
|
||||
* Primitive values such as numbers will serialize as expected. Values with a typeof "object" and "function" will typically serialize to {}, with the exception of Array (serializes as expected), Date, and Regex (serialize using their String representation).
|
||||
* @param callback Optional.
|
||||
* Callback on success, or on failure (in which case runtime.lastError will be set).
|
||||
*/
|
||||
async set(items: { [key: string]: any }) {
|
||||
for (const key in items) {
|
||||
const oldValue = localData[key];
|
||||
localData[key] = JSON.parse(JSON.stringify(items[key]));
|
||||
|
||||
for (const listener of localDataListeners.values()) {
|
||||
listener({ [key]: { oldValue: oldValue, newValue: localData[key] } });
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
onChanged: {
|
||||
/**
|
||||
* Registers an event listener callback to an event.
|
||||
* @param callback Called when an event occurs. The parameters of this function depend on the type of event.
|
||||
*/
|
||||
addListener(
|
||||
listener: (
|
||||
changes: { [key: string]: chrome.storage.StorageChange },
|
||||
areaName: chrome.storage.AreaName
|
||||
) => void
|
||||
) {
|
||||
localDataListeners.set(listener, (changes: { [key: string]: chrome.storage.StorageChange }) => {
|
||||
listener(changes, 'local');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Deregisters an event listener callback from an event.
|
||||
* @param callback Listener that shall be unregistered.
|
||||
*/
|
||||
removeListener(listener: ListenerFunction) {
|
||||
localDataListeners.delete(listener);
|
||||
},
|
||||
},
|
||||
},
|
||||
runtime: {
|
||||
id: 'fake-id',
|
||||
getManifest(): chrome.runtime.Manifest {
|
||||
return {
|
||||
manifest_version: 3,
|
||||
name: 'fake-name',
|
||||
version: '0.0.0',
|
||||
};
|
||||
},
|
||||
onMessage: {
|
||||
/**
|
||||
* Registers an event listener callback to an event.
|
||||
* @param callback Called when an event occurs. The parameters of this function depend on the type of event.
|
||||
*/
|
||||
addListener<T extends Function>(callback: T) {},
|
||||
|
||||
/**
|
||||
* Deregisters an event listener callback from an event.
|
||||
* @param callback Listener that shall be unregistered.
|
||||
*/
|
||||
removeListener<T extends Function>(callback: T) {},
|
||||
},
|
||||
},
|
||||
} as typeof chrome;
|
||||
|
||||
// set updatedAt dates to be fixed
|
||||
|
||||
UserScheduleStore.get('schedules').then(schedules => {
|
||||
schedules.forEach(schedule => {
|
||||
schedule.updatedAt = new Date('2024-01-01 12:00').getTime();
|
||||
});
|
||||
UserScheduleStore.set('schedules', schedules);
|
||||
});
|
||||
|
||||
export default preview;
|
||||
@@ -1,28 +0,0 @@
|
||||
import react from '@vitejs/plugin-react-swc';
|
||||
import { resolve } from 'path';
|
||||
import UnoCSS from 'unocss/vite';
|
||||
import Icons from 'unplugin-icons/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
const root = resolve(__dirname, '../src');
|
||||
const pagesDir = resolve(root, 'pages');
|
||||
const assetsDir = resolve(root, 'assets');
|
||||
const publicDir = resolve(__dirname, '../public');
|
||||
|
||||
console.log(root);
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), UnoCSS(), Icons({ compiler: 'jsx', jsx: 'react' })],
|
||||
resolve: {
|
||||
alias: {
|
||||
src: root,
|
||||
'@assets': assetsDir,
|
||||
'@pages': pagesDir,
|
||||
'@public': publicDir,
|
||||
'@shared': resolve(root, 'shared'),
|
||||
'@background': resolve(pagesDir, 'background'),
|
||||
'@views': resolve(root, 'views'),
|
||||
},
|
||||
},
|
||||
});
|
||||
9
.vscode/extensions.json
vendored
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"antfu.unocss",
|
||||
"editorconfig.editorconfig",
|
||||
"figma.figma-vscode-extension"
|
||||
]
|
||||
}
|
||||
18
.vscode/launch.json
vendored
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Run current script",
|
||||
"runtimeExecutable": "npx",
|
||||
"runtimeArgs": [
|
||||
"tsx"
|
||||
],
|
||||
"program": "${file}",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
40
.vscode/settings.json
vendored
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[yaml]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[svg]": {
|
||||
"editor.defaultFormatter": "jock.svg"
|
||||
},
|
||||
"material-icon-theme.activeIconPack": "react",
|
||||
"material-icon-theme.folders.associations": {
|
||||
"analytics": "Json",
|
||||
"background": "Delta",
|
||||
"navigation": "Routes",
|
||||
"logging": "log",
|
||||
"popup": "Layout",
|
||||
"storage": "Database",
|
||||
},
|
||||
"material-icon-theme.files.associations": {
|
||||
"tsconfig.extension.json": "tsconfig",
|
||||
"tsconfig.build.json": "tsconfig",
|
||||
"tsconfig.test.json": "tsconfig"
|
||||
},
|
||||
"[html]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
}
|
||||
10
@types/vite-env.d.ts
vendored
@@ -1,10 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_PACKAGE_VERSION: string;
|
||||
readonly VITE_BETA_BUILD?: 'true';
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
21
LICENSE.md
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Sriram Hariharan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
73
README.md
@@ -1,59 +1,40 @@
|
||||
# UT Registration Plus
|
||||
|
||||
We've all been there. 20 tabs of Rate My Professor, Google Spreadsheet, and the UT Course Schedule open and you still don't know what classes to take.
|
||||
This extension, UT Registration Plus (UTRP), tries to streamline most of the unnecessary steps and headaches of registering for classes at UT Austin.
|
||||
# UT Registration Plus
|
||||
## (or Sriram's Sexy Scheduling Script)
|
||||
[Try it for yourself on the Chrome Web Store](https://chrome.google.com/webstore/detail/hboadpjkoaieogjimneceaahlppnipaa)
|
||||
|
||||
- For each class in the UT Course Schedule site, UTRP provides a "breakdown" popup, with quick and easy links to the instructor's RateMyProfessor, Course Evaluation Survey (CES) and past syllabi.
|
||||
|
||||
- Shows the course description with highlighted information on prerequisites, restrictions, etc.
|
||||
We've all been there. 20 tabs of Rate My Professor, Catalyst, and the UT Course Catalog open and you still don't know what classes to take.
|
||||
This extension tries to streamline most of the unnecessary steps and headaches of registering for classes at UT Austin.
|
||||
|
||||
- Shows an aggregate and semesterly graph of the grade distributions for each course.
|
||||
|
||||
- Gives you the ability to add "Add Course" and view them in the extension popup, a quick list of all the courses you have saved and an easy way to copy unique numbers.
|
||||
- For each class on the UT Course Catalog it provides a "breakdown" popup, with quick and easy links to the RateMyProfessor and eCIS pages of the professor, as well as syllabi from when the professor taught the class in the past.
|
||||
|
||||
- Highlights and crosses-out what courses on the UT Course Catalog would conflict with your currently saved courses, making selecting courses that fit with your schedule so much easier.
|
||||
- Gets the course description and highlight the important information like prerequisites, restrictions, etc.
|
||||
|
||||
- Display's a weekly schedule based on your saved courses.
|
||||
- Shows an aggregate graph of the grade distributions for when the professor taught the class in the past.
|
||||
|
||||
- Give you the ability to create multiple schedules to plan for different scenarios.
|
||||
- Gives you the ability to "Save Courses" and view them in the extension popup. This lets you see any schedule conflicts, and makes copy-pasting the unique code much easier when you're actually registering.
|
||||
|
||||
- ... and much more!
|
||||
- Highlights and crosses-out what courses on the UT Course Catalog would conflict with your currently saved courses, making selecting courses that fit with your schedule so much easier.
|
||||
|
||||
## Toolchain
|
||||
- Display's a weekly schedule based on your saved courses
|
||||
|
||||
- React 18
|
||||
- TypeScript
|
||||
- Vite 5
|
||||
- ESLint
|
||||
- Prettier
|
||||
- Storybook
|
||||
- Semantic-Release
|
||||
- Custom Messaging & Storage Wrappers
|
||||
<p align="center">
|
||||
<img src="https://lh3.googleusercontent.com/X5hqHGPU-F2lF3_shT2injxd40eFYXLJfZVxpU1v2w1YvFRW1jQMEXu2yzWHKKpqn5huJL-NEHY=w640-h400-e365">
|
||||
</p>
|
||||
<p align="center">
|
||||
<img src="https://lh3.googleusercontent.com/ZCRxTFKFjpGm5ZRMv2iHzMqdnrQHUx_Ih_XhGhy2O4Yn29YccvU5yXXrWXKuVKsNAmEJJ0As4xc=w640-h400-e365">
|
||||
</p>
|
||||
<p align="center">
|
||||
<img src="https://lh3.googleusercontent.com/3iRi25wDnVqgzc7pnYUXQq1TvdPpAeDjCmIF9hLU-WKmlchEYQUh_xU-XV00fEbKUr2XVKGkOw=w640-h400-e365">
|
||||
</p>
|
||||
<p align="center">
|
||||
<img src="https://lh3.googleusercontent.com/x95blI5D1mseNPLOtHETlLmoVtHm0eeye9uyeWSDd5W6m6fSoZxMMMyQTGUFo5swoTgRivGVyw=w640-h400-e365">
|
||||
</p>
|
||||
<p align="center">
|
||||
<img src="https://lh3.googleusercontent.com/bbey8OGOTtJWUaHGVIU5wewbWg6X6s-gjD15RwXHhvgH_9kax2mE4bcrjem_iZGH-q5z6NT7g94=w640-h400-e365">
|
||||
</p>
|
||||
|
||||
## Development: Getting Started
|
||||
# 2.0 coming soon....
|
||||
|
||||
1. Clone this repo
|
||||
2. This project uses `pnpm` to manage and patch dependencies. Run `pnpm install` to configure the repository for building/development
|
||||
3. Using either of the methods listed below, the extension will build to the `dist/` directory.
|
||||
|
||||
### Development Builds
|
||||
|
||||
- Run `pnpm dev`
|
||||
|
||||
> [!NOTE]
|
||||
> Injected content such as extension content on UT pages is not properly styled, and are missing class stylings. When developing for these pages, use `NODE_ENV='development' pnpm run dev build --mode development -w` to build and watch for changes. This will ensure you are seeing an accurate representation of the extension.
|
||||
|
||||
### Production Builds
|
||||
|
||||
- Run `pnpm build`
|
||||
|
||||
<details>
|
||||
<summary>Beta builds</summary>
|
||||
Use `BETA=true pnpm build` to build a beta build.
|
||||
</details>
|
||||
|
||||
## Development: Loading the Extension Manually
|
||||
|
||||
Open [chrome://extensions](chrome://extensions), ensure you have 'Developer Mode' enabled, and click 'Load unpacked'.
|
||||
|
||||
Navigate to the `dist/` folder, and click 'select' to import the extension.
|
||||
|
||||
42
calendar.html
Normal file
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- This file is the html file serving the "My Schedule / Calendar" page. -->
|
||||
<head>
|
||||
<link rel='stylesheet' href='css/fullcalendar.min.css' />
|
||||
<link rel='stylesheet' href='css/_materialFullCalendar.css' />
|
||||
<script src='js/lib/jquery-3.3.1.min.js'></script>
|
||||
<script src='js/lib/moment.min.js'></script>
|
||||
<script src='js/lib/fullcalendar.min.js'></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style='display:flex'>
|
||||
<div id='calendar' style="flex-grow: 1"></div>
|
||||
<div class="card" id="header"
|
||||
style="text-align:center;margin:5px 0px 0px 15px;display: inline-table;padding-bottom: 5px;">
|
||||
<h1 id='hours'
|
||||
style="font-size:30px;font-weight:500; border-bottom: 3px solid black;display: inline-block;padding-bottom: 5px;margin-bottom: 5px;">
|
||||
0
|
||||
hours</h1>
|
||||
<h1 id='num' style="font-size:20px;font-weight:500; margin: 2px;">0
|
||||
Courses</h1>
|
||||
<br>
|
||||
<div style="margin:5px;display: flex;flex-direction: column;">
|
||||
<button id="clear" class="matbut"
|
||||
style="font-size:medium; background:#4CAF50;margin: 10px;white-space: nowrap;text-align: center;">Clear
|
||||
All</button>
|
||||
<button id="save" class="matbut"
|
||||
style="font-size:medium; background:#FF9800;margin: 10px;white-space: nowrap;text-align: center;">Save
|
||||
as PNG</button>
|
||||
<button id="export" class="matbut"
|
||||
style="font-size:medium; background:#FF0000;margin: 10px;white-space: nowrap;text-align: center;">Export
|
||||
Cal</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script src='js/config.js'></script>
|
||||
<script src='js/lib/html2canvas.min.js'></script>
|
||||
<script src='js/lib/ics.min.js'></script>
|
||||
<script src='js/Template.js'></script>
|
||||
<script src='js/util.js'></script>
|
||||
<script src='js/calendar.js'></script>
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"onlyChanged": true,
|
||||
"projectId": "Project:65c5172964f36dcf207985bf",
|
||||
"zip": true
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
import { RuleConfigCondition, RuleConfigSeverity, TargetCaseType } from '@commitlint/types';
|
||||
|
||||
export default {
|
||||
parserPreset: 'conventional-changelog-conventionalcommits',
|
||||
rules: {
|
||||
'body-leading-blank': [RuleConfigSeverity.Warning, 'always'] as const,
|
||||
'body-max-line-length': [RuleConfigSeverity.Error, 'always', 100] as const,
|
||||
'footer-leading-blank': [RuleConfigSeverity.Warning, 'always'] as const,
|
||||
'footer-max-line-length': [RuleConfigSeverity.Error, 'always', 100] as const,
|
||||
'header-max-length': [RuleConfigSeverity.Error, 'always', 100] as const,
|
||||
'header-trim': [RuleConfigSeverity.Error, 'always'] as const,
|
||||
'subject-case': [
|
||||
RuleConfigSeverity.Error,
|
||||
'never',
|
||||
['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
|
||||
] as [RuleConfigSeverity, RuleConfigCondition, TargetCaseType[]],
|
||||
'subject-empty': [RuleConfigSeverity.Error, 'never'] as const,
|
||||
'subject-full-stop': [RuleConfigSeverity.Error, 'never', '.'] as const,
|
||||
'type-case': [RuleConfigSeverity.Error, 'always', 'lower-case'] as const,
|
||||
'type-empty': [RuleConfigSeverity.Error, 'never'] as const,
|
||||
'type-enum': [
|
||||
RuleConfigSeverity.Error,
|
||||
'always',
|
||||
['build', 'chore', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test'],
|
||||
] as [RuleConfigSeverity, RuleConfigCondition, string[]],
|
||||
},
|
||||
prompt: {
|
||||
questions: {
|
||||
type: {
|
||||
description: "Select the type of change that you're committing",
|
||||
enum: {
|
||||
feat: {
|
||||
description: 'A new feature',
|
||||
title: 'Features',
|
||||
emoji: '✨',
|
||||
},
|
||||
fix: {
|
||||
description: 'A bug fix',
|
||||
title: 'Bug Fixes',
|
||||
emoji: '🐛',
|
||||
},
|
||||
docs: {
|
||||
description: 'Documentation only changes',
|
||||
title: 'Documentation',
|
||||
emoji: '📚',
|
||||
},
|
||||
style: {
|
||||
description:
|
||||
'Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)',
|
||||
title: 'Styles',
|
||||
emoji: '💎',
|
||||
},
|
||||
refactor: {
|
||||
description: 'A code change that neither fixes a bug nor adds a feature',
|
||||
title: 'Code Refactoring',
|
||||
emoji: '📦',
|
||||
},
|
||||
perf: {
|
||||
description: 'A code change that improves performance',
|
||||
title: 'Performance Improvements',
|
||||
emoji: '🚀',
|
||||
},
|
||||
test: {
|
||||
description: 'Adding missing tests or correcting existing tests',
|
||||
title: 'Tests',
|
||||
emoji: '🚨',
|
||||
},
|
||||
build: {
|
||||
description:
|
||||
'Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)',
|
||||
title: 'Builds',
|
||||
emoji: '🛠',
|
||||
},
|
||||
ci: {
|
||||
description:
|
||||
'Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)',
|
||||
title: 'Continuous Integrations',
|
||||
emoji: '⚙️',
|
||||
},
|
||||
chore: {
|
||||
description: "Other changes that don't modify src or test files",
|
||||
title: 'Chores',
|
||||
emoji: '♻️',
|
||||
},
|
||||
revert: {
|
||||
description: 'Reverts a previous commit',
|
||||
title: 'Reverts',
|
||||
emoji: '🗑',
|
||||
},
|
||||
},
|
||||
},
|
||||
scope: {
|
||||
description: 'What is the scope of this change (e.g. component or file name)',
|
||||
},
|
||||
subject: {
|
||||
description: 'Write a short, imperative tense description of the change',
|
||||
},
|
||||
body: {
|
||||
description: 'Provide a longer description of the change',
|
||||
},
|
||||
isBreaking: {
|
||||
description: 'Are there any breaking changes?',
|
||||
},
|
||||
breakingBody: {
|
||||
description:
|
||||
'A BREAKING CHANGE commit requires a body. Please enter a longer description of the commit itself',
|
||||
},
|
||||
breaking: {
|
||||
description: 'Describe the breaking changes',
|
||||
},
|
||||
isIssueAffected: {
|
||||
description: 'Does this change affect any open issues?',
|
||||
},
|
||||
issuesBody: {
|
||||
description:
|
||||
'If issues are closed, the commit requires a body. Please enter a longer description of the commit itself',
|
||||
},
|
||||
issues: {
|
||||
description: 'Add issue references (e.g. "fix #123", "re #123".)',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
344
css/_materialFullCalendar.css
Normal file
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
|
||||
This is the Material Design theme for FullCalendar Weekly Agenda view
|
||||
|
||||
Creation Date: Aug 19th 2015
|
||||
Author: Jacky Liang
|
||||
Version: FullCalendar 2.4.0
|
||||
Tested Using the Following FC Settings:
|
||||
|
||||
editable: false,
|
||||
handleWindowResize: true,
|
||||
weekends: false, // Hide weekends
|
||||
defaultView: 'agendaWeek', // Only show week view
|
||||
header: false, // Hide buttons/titles
|
||||
minTime: '07:30:00', // Start time for the calendar
|
||||
maxTime: '22:00:00', // End time for the calendar
|
||||
columnFormat: {
|
||||
week: 'ddd' // Only show day of the week names
|
||||
},
|
||||
displayEventTime: true,
|
||||
allDayText: 'Online/TBD'
|
||||
|
||||
Note: This has NOT been tested on Monthly or Daily views.
|
||||
|
||||
Colors: Use the following - https://www.google.com/design/spec/style/color.html#color-color-palette
|
||||
at the 700 level. An opacity of 0.65 is automatically applied to the
|
||||
700 level colors to generate a soft and pleasing look.
|
||||
|
||||
Color were applied to each event using the following code:
|
||||
|
||||
events.push({
|
||||
title: 'This is a Material Design event!',
|
||||
start: 'someStartDate',
|
||||
end: 'someEndDate',
|
||||
color: '#C2185B'
|
||||
});
|
||||
|
||||
*/
|
||||
|
||||
.fc-state-highlight {
|
||||
opacity: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Styling for each event from Schedule */
|
||||
|
||||
.fc-time-grid-event.fc-v-event.fc-event {
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
padding: 5px;
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.3);
|
||||
transition: 0.3s;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.html2canvas-container {
|
||||
width: 3000px !important;
|
||||
height: 3000px !important;
|
||||
}
|
||||
|
||||
|
||||
.fc-time-grid-event.fc-v-event.fc-event:hover {
|
||||
box-shadow: 0 8px 12px 0 rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Bolds the name of the event and inherits the font size */
|
||||
|
||||
.fc-event {
|
||||
font-size: small !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
/* Remove the header border from Schedule */
|
||||
|
||||
.fc td,
|
||||
.fc th {
|
||||
border-style: ridge !important;
|
||||
border-width: 1px !important;
|
||||
padding: 4px 3px 0px 3px !important;
|
||||
vertical-align: top !important;
|
||||
border-left-width: 0;
|
||||
|
||||
}
|
||||
|
||||
.fc-row fc-widget-header {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.fc td {
|
||||
border-top-width: 0;
|
||||
padding: 3px !important;
|
||||
}
|
||||
|
||||
.fc-widget-header {
|
||||
background-color: #cc5500;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Inherits background for each event from Schedule. */
|
||||
|
||||
.fc-event .fc-bg {
|
||||
z-index: 1 !important;
|
||||
background: inherit !important;
|
||||
opacity: 0.25 !important;
|
||||
}
|
||||
|
||||
/* Normal font weight for the time in each event */
|
||||
|
||||
.fc-time-grid-event .fc-time {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
/* Apply same opacity to all day events */
|
||||
|
||||
.fc-ltr .fc-h-event.fc-not-end,
|
||||
.fc-rtl .fc-h-event.fc-not-start {
|
||||
opacity: 0.65 !important;
|
||||
margin-left: 12px !important;
|
||||
padding: 5px !important;
|
||||
}
|
||||
|
||||
/* Apply same opacity to all day events */
|
||||
|
||||
.fc-day-grid-event.fc-h-event.fc-event.fc-not-start.fc-end {
|
||||
opacity: 0.65 !important;
|
||||
margin-left: 12px !important;
|
||||
padding: 5px !important;
|
||||
}
|
||||
|
||||
/* Material design button */
|
||||
|
||||
.matbut {
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
margin: 10px 10px 10px 0px;
|
||||
padding: 10px 10px;
|
||||
border-radius: 10px;
|
||||
font-size: medium;
|
||||
font-style: bold;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.matbut {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.matbut:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
opacity: 0;
|
||||
border-radius: 100%;
|
||||
transform: scale(1, 1) translate(-50%);
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
|
||||
@keyframes ripple {
|
||||
0% {
|
||||
transform: scale(0, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: scale(25, 25);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(40, 40);
|
||||
}
|
||||
}
|
||||
|
||||
.matbut:focus:not(:active)::after {
|
||||
animation: ripple 1s ease-out;
|
||||
}
|
||||
|
||||
.fc-button {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
min-height: 36px;
|
||||
min-width: 88px;
|
||||
line-height: 36px;
|
||||
vertical-align: middle;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
box-sizing: border-box;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
outline: none;
|
||||
border: 0;
|
||||
padding: 0 6px;
|
||||
margin: 6px 8px;
|
||||
letter-spacing: 0.01em;
|
||||
background: transparent;
|
||||
color: currentColor;
|
||||
white-space: nowrap;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
font-style: inherit;
|
||||
font-variant: inherit;
|
||||
font-family: inherit;
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
-webkit-transition: box-shadow 0.4s cubic-bezier(0.25, 0.8, 0.25, 1), background-color 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
transition: box-shadow 0.4s cubic-bezier(0.25, 0.8, 0.25, 1), background-color 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
}
|
||||
|
||||
.fc-button:hover {
|
||||
background-color: rgba(158, 158, 158, 0.2);
|
||||
}
|
||||
|
||||
.fc-button:focus,
|
||||
.fc-button:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* The active button box is ugly so the active button will have the same appearance of the hover */
|
||||
|
||||
.fc-state-active {
|
||||
background-color: rgba(158, 158, 158, 0.2);
|
||||
}
|
||||
|
||||
/* Not raised button */
|
||||
|
||||
.fc-state-default {
|
||||
box-shadow: None;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
padding-top: 300px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgb(0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
#classname {
|
||||
font-weight: bold;
|
||||
margin-bottom: -10px;
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
margin: auto;
|
||||
max-height: 85%;
|
||||
overflow-y: auto;
|
||||
padding: 15px;
|
||||
border: 1px solid #888;
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
#prof {
|
||||
font-size: medium;
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
|
||||
#info {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
body a:link,
|
||||
body a:visited {
|
||||
font-weight: bold;
|
||||
color: #3c87a3;
|
||||
}
|
||||
|
||||
.time {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 5px;
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
.fc td,
|
||||
.fc th {
|
||||
border-color: #E7E7E7;
|
||||
|
||||
}
|
||||
|
||||
.fc-time-grid .fc-slats td {
|
||||
height: 1.5em;
|
||||
border-bottom: initial;
|
||||
border-color: #E7E7E7;
|
||||
}
|
||||
|
||||
/* The Close Button */
|
||||
|
||||
.close {
|
||||
color: #aaaaaa;
|
||||
float: right;
|
||||
padding: 5px;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card {
|
||||
transition: 0.3s;
|
||||
margin-bottom: 5px;
|
||||
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.2), 0 4px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
|
||||
|
||||
.cardcontainer {
|
||||
padding: 2px 16px;
|
||||
display: block;
|
||||
transition: width 300ms ease-in-out, height 300ms ease-in-out;
|
||||
}
|
||||
|
||||
|
||||
tbody {
|
||||
border-width: 0px;
|
||||
}
|
||||
5
css/fullcalendar.min.css
vendored
Normal file
81
css/options.css
Normal file
@@ -0,0 +1,81 @@
|
||||
.version {
|
||||
padding: 0px 5px 5px 0px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.creator-tag {
|
||||
margin: 10px 5px 5px 0px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.options-card {
|
||||
width: 400px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
height: auto;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
#version-container {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.options-header {
|
||||
padding: 16px 16px 0px 16px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#contributors_container {
|
||||
text-align: center;
|
||||
margin-top: 30;
|
||||
padding: 5px 20px 20px 20px;
|
||||
width: auto;
|
||||
margin-right: 20%;
|
||||
margin-left: 20%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#contributor-list {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-gap: 1rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
}
|
||||
|
||||
.contributor-card {
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
display: grid;
|
||||
grid-gap: 1rem;
|
||||
}
|
||||
|
||||
.contributor-card img {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.contributor-name {
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.contributor-username {
|
||||
margin: 0 0 5px 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.contributor-title {
|
||||
margin-bottom: 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.open-source-tag {
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
563
css/popup.css
Normal file
@@ -0,0 +1,563 @@
|
||||
.card {
|
||||
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
|
||||
transition: 0.3s;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.2), 0 4px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 3px 16px 3px 12px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding: 10px 0px 0 10px;
|
||||
}
|
||||
|
||||
li {
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
body {
|
||||
min-width: 370px;
|
||||
min-height: 400px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.conflict_message{
|
||||
font-size:small;
|
||||
font-weight:bold;
|
||||
color:red;
|
||||
margin:5px 5px 5px 10px;
|
||||
}
|
||||
|
||||
.course_list {
|
||||
list-style-type: none;
|
||||
padding: 5px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.course_list_card {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.course_list_item {
|
||||
padding: 0px 5px 5px 5px;
|
||||
overflow-y: auto;
|
||||
max-height:400px;
|
||||
}
|
||||
|
||||
.course_list_item_subtext {
|
||||
font-size:medium;
|
||||
}
|
||||
|
||||
.empty_message {
|
||||
font-weight: normal;
|
||||
font-size: large;
|
||||
margin: 60px 30px 200px 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#empty #main {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#empty span {
|
||||
font-size: small;
|
||||
display: table;
|
||||
margin: 0 auto;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.time_line_days {
|
||||
display:inline-block;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.time_line_hours {
|
||||
margin-left:10px;
|
||||
display:inline-block;
|
||||
width: 50%;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.time_line_location {
|
||||
float:right;
|
||||
display:inline-block;
|
||||
text-align:right;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.time_line_location_link {
|
||||
color:#3c87a3;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
.more_info_button{
|
||||
background: #2196F3;
|
||||
}
|
||||
|
||||
.remove_button {
|
||||
background: #F44336;
|
||||
}
|
||||
|
||||
.register_button {
|
||||
background: #4CAF50;
|
||||
}
|
||||
|
||||
.settings_button {
|
||||
margin-right: 2px;
|
||||
border: 0px;
|
||||
border-radius: 50%;
|
||||
transition: 0.3s;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
|
||||
.settings_button:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.settings_button:hover {
|
||||
box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.16), 0 4px 15px 0 rgba(0, 0, 0, 0.12);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.selected {
|
||||
box-shadow: 0 0 0 1pt #FF9800;
|
||||
}
|
||||
|
||||
.settings_button:focus:after:hover {
|
||||
outline: 0;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.copy_button {
|
||||
background-color: transparent;
|
||||
padding: 0px;
|
||||
border: none;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
transition: .2s;
|
||||
}
|
||||
|
||||
.copy_button:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
box-shadow: 0 2px 20px 0 rgba(0, 0, 0, 0.2), 0 4px 20px 0 rgba(0, 0, 0, 0.16);
|
||||
}
|
||||
|
||||
i {
|
||||
padding: 4px 0px;
|
||||
}
|
||||
|
||||
.copy_button_icon {
|
||||
color:white;
|
||||
float:left;
|
||||
border-radius: 50%;
|
||||
padding: 3px;
|
||||
text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.16);
|
||||
font-size: x-large;
|
||||
}
|
||||
|
||||
.header_container{
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.header_buttons {
|
||||
padding: 5px 10px 5px 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header_button {
|
||||
font-size:15px !important;
|
||||
margin: 7px !important;
|
||||
}
|
||||
|
||||
|
||||
.clear_button {
|
||||
background:#4CAF50;
|
||||
}
|
||||
|
||||
.ris_button {
|
||||
background:#FF9800;
|
||||
}
|
||||
|
||||
.schedule_button {
|
||||
background: #FF0000;
|
||||
}
|
||||
|
||||
.settings {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
display: flex;
|
||||
vertical-align: middle;
|
||||
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
|
||||
transition: 0.3s;
|
||||
padding: 7px 5px 5px 7px;
|
||||
margin: 0px 5px 0px 0px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.material_button {
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
margin: 10px 10px 10px 0px;
|
||||
padding: 10px 10px;
|
||||
border-radius: 10px;
|
||||
font-size: medium;
|
||||
font-style: bold;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.course_name_truncate_box {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 80%;
|
||||
color:white;
|
||||
margin:5px;
|
||||
display:inline-block;
|
||||
font-size:large;
|
||||
align-items:center;
|
||||
}
|
||||
|
||||
.settings_divider {
|
||||
margin-bottom: 0px;
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
.search_button {
|
||||
background-color:white;
|
||||
margin-left: 5px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
.course_list_item_options {
|
||||
display: none;
|
||||
}
|
||||
.import_button {
|
||||
background-color:white;
|
||||
}
|
||||
|
||||
.options_button {
|
||||
background-color:white;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.settings_icon {
|
||||
color:#FF9800;
|
||||
}
|
||||
|
||||
.course_list_item_time_box {
|
||||
font-weight:bold;
|
||||
padding:10px;
|
||||
margin:0px 5px 0px 15px;
|
||||
font-size:small;
|
||||
}
|
||||
|
||||
.course_list_item_options_button_container {
|
||||
border-radius:0px;
|
||||
}
|
||||
|
||||
.course_list_item_options_buttons{
|
||||
float:right;
|
||||
margin:5px;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
float:right;
|
||||
font-size:small;
|
||||
display:inline-block;
|
||||
margin-top:10px;
|
||||
color:white;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.material_button:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
opacity: 0;
|
||||
border-radius: 100%;
|
||||
transform: scale(1, 1) translate(-50%);
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
|
||||
.card:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
opacity: 0;
|
||||
border-radius: 100%;
|
||||
transform: scale(1, 1) translate(-50%);
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
padding-top: 100px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgb(0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
margin: auto;
|
||||
max-height: 85%;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #888;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
/* The Close Button */
|
||||
|
||||
.close {
|
||||
color: #aaaaaa;
|
||||
float: right;
|
||||
padding: 5px;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
@keyframes ripple {
|
||||
0% {
|
||||
transform: scale(0, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: scale(25, 25);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(40, 40);
|
||||
}
|
||||
}
|
||||
|
||||
.material_button:focus:not(:active)::after {
|
||||
animation: ripple 1s ease-out;
|
||||
}
|
||||
|
||||
|
||||
.card:focus:not(:active)::after {
|
||||
animation: ripple 1s ease-out;
|
||||
}
|
||||
|
||||
|
||||
input {
|
||||
border-radius: 5px;
|
||||
color: rgba(0, 0, 45, 0.48)
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input[type=number]::-webkit-inner-spin-button,
|
||||
input[type=number]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#search-popup {
|
||||
background: white;
|
||||
color: #747474;
|
||||
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
bottom: 42px;
|
||||
right: 20px;
|
||||
padding: 10px;
|
||||
border-radius: inherit;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.item {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.select-style {
|
||||
border: 1px solid #979797;
|
||||
margin: 5px 0px;
|
||||
border-radius: 5px;
|
||||
display: none;
|
||||
background: transparent no-repeat 90% 50%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.select-style:after {
|
||||
content: '\e5c5';
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
color: rgba(0, 0, 45, 0.48);
|
||||
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.select-style select {
|
||||
padding: 5px 8px;
|
||||
border: none;
|
||||
color: rgba(0, 0, 45, 0.48);
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
background-image: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 120px;
|
||||
word-break: normal;
|
||||
-ms-word-break: normal;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.select-style select:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.class_id_input {
|
||||
display: none;
|
||||
border: 1px solid #979797;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0px;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
.class_id_input::placeholder {
|
||||
color: rgba(0, 0, 45, 0.48);
|
||||
}
|
||||
|
||||
.search-button {
|
||||
float: right;
|
||||
font-size: inherit;
|
||||
padding: 7px 11px;
|
||||
margin: 8px 0px;
|
||||
background-color: #FF9800;
|
||||
}
|
||||
|
||||
#import-export-popup {
|
||||
background: white;
|
||||
color: #747474;
|
||||
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
bottom: 42px;
|
||||
right: 10px;
|
||||
border-radius: inherit;
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
|
||||
.simple-menu-option {
|
||||
display: inline-block;
|
||||
color: rgba(0, 0, 45, 0.48);
|
||||
border: none;
|
||||
background-color: white;
|
||||
font-size: 15px;
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
padding: 10px 0px 5px 5px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.simple-menu-option i {
|
||||
font-size: 19px;
|
||||
vertical-align: middle;
|
||||
margin-top: 0px;
|
||||
margin-left: 0px;
|
||||
margin-right: 3px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
|
||||
.simple-menu-option:hover {
|
||||
background-color: rgba(177, 175, 175, 0.200);
|
||||
transition-duration: 0.4s;
|
||||
/* color: #FF9800; */
|
||||
}
|
||||
|
||||
.simple-menu-option:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.meta{
|
||||
margin:0;
|
||||
color:#FF9800;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
vertical-align: middle;
|
||||
padding: 7px 5px 5px 7px;
|
||||
margin: 0px 5px 0px 5px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.meta-metric{
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.meta-container{
|
||||
margin: 5px 5px 10px 5px;
|
||||
}
|
||||
|
||||
.input-box{
|
||||
color: rgba(0, 0, 45, 0.48);
|
||||
border: 1px solid #8C8C8C;
|
||||
font-size: 11px;
|
||||
padding: 5px;
|
||||
border-radius: 7px;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.input-box::placeholder {
|
||||
color: rgba(0, 0, 45, 0.345);
|
||||
}
|
||||
308
css/styles.css
Normal file
@@ -0,0 +1,308 @@
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
padding-top: 75px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgb(0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
margin: auto;
|
||||
max-height: 85%;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #888;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.loader {
|
||||
border: 10px solid #f3f3f3;
|
||||
border-top: 10px solid #FF9800;
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: none;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.close {
|
||||
color: #aaaaaa;
|
||||
float: right;
|
||||
padding: 5px;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: x-large;
|
||||
font-weight: bold;
|
||||
padding-top: 5px;
|
||||
line-height: 1;
|
||||
padding-left: 5px;
|
||||
margin: 5px 0px 5px 0px;
|
||||
}
|
||||
|
||||
.distButton {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.description {
|
||||
padding: 5px;
|
||||
font-size: 15px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
||||
.chartloader {
|
||||
position: absolute;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 50px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.profname {
|
||||
margin-left: 5px;
|
||||
padding-bottom: 5px;
|
||||
font-size: medium;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.dateTimePlace {
|
||||
margin-left: 5px;
|
||||
margin-bottom: 0px;
|
||||
margin-top: 0px;
|
||||
font-size: smaller;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#chartcontainer {
|
||||
max-width: 100%;
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
#chart {
|
||||
min-width: auto;
|
||||
max-width: 100%;
|
||||
height: 250px;
|
||||
margin: 0 auto;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.card {
|
||||
transition: 0.3s;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.2), 0 4px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
|
||||
.cardcontainer {
|
||||
padding: 2px 16px;
|
||||
transition: width 300ms ease-in-out, height 300ms ease-in-out;
|
||||
}
|
||||
|
||||
.description {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.topbuttons .material-button {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.rmp-button {
|
||||
|
||||
}
|
||||
|
||||
.ecis-button {
|
||||
|
||||
}
|
||||
|
||||
.textbook-button{
|
||||
|
||||
}
|
||||
|
||||
.material-button {
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
margin: 10px 10px 10px 0px;
|
||||
padding: 10px 10px;
|
||||
border-radius: 10px;
|
||||
font-size: medium;
|
||||
font-style: bold;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4);
|
||||
background: #ff9800;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.material-button:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
opacity: 0;
|
||||
border-radius: 100%;
|
||||
transform: scale(1, 1) translate(-50%);
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
|
||||
@keyframes ripple {
|
||||
0% {
|
||||
transform: scale(0, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
15% {
|
||||
transform: scale(25, 25);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(40, 40);
|
||||
}
|
||||
}
|
||||
|
||||
.material-button:focus:not(:active)::after {
|
||||
animation: ripple 1s ease-out;
|
||||
}
|
||||
|
||||
#snackbar {
|
||||
visibility: hidden;
|
||||
min-width: 250px;
|
||||
margin-left: -200px;
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
border-radius: 2px;
|
||||
padding: 16px;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: 50%;
|
||||
bottom: 30px;
|
||||
}
|
||||
|
||||
.descriptionli {
|
||||
padding: 0px 5px 5px 5px;
|
||||
}
|
||||
|
||||
#snackbar.show {
|
||||
visibility: visible;
|
||||
-webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;
|
||||
animation: fadein 0.5s, fadeout 0.5s 2.5s;
|
||||
}
|
||||
|
||||
#semesters {
|
||||
padding: 5px;
|
||||
margin-right: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#semesters:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
|
||||
.tooltip {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tooltip .tooltiptext {
|
||||
visibility: hidden;
|
||||
background-color: black;
|
||||
background:rgba(1,1,1,0.5);
|
||||
color: #fff;
|
||||
text-align: left;
|
||||
border-radius: 6px;
|
||||
font-size: 10px;
|
||||
max-width: 100px;
|
||||
margin-left: 5px;
|
||||
padding: 5px 10px;
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
|
||||
@-webkit-keyframes fadein {
|
||||
from {
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
bottom: 30px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
from {
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
bottom: 30px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadeout {
|
||||
from {
|
||||
bottom: 30px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeout {
|
||||
from {
|
||||
bottom: 30px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
1
docs/_config.yml
Normal file
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-cayman
|
||||
1
docs/departments.json
Normal file
@@ -0,0 +1 @@
|
||||
["ACC","ADV","ASE","AFR","AFS","ASL","AMS","AHC","ANT","ALD","ARA","ARE","ARI","ARC","AED","ARH","ART","AET","AAS","ANS","AST","BSN","BEN","BCH","BIO","BME","BDP","B A","BAX","BGS","CHE","CH","CHI","C E","CLA","C C","CGS","COM","CLD","CMS","CRP","C L","COE","CSE","C S","CON","CTI","CRW","CDI","EDC","CZ","DAN","DSC","D S","DES","DEV","D B","DRS","DCH","ECO","ELP","EDP","E E","ECE","EER","EMA","ENM","E M","E S","E","ESL","ENS","EVE","EVS","EUP","EUS","FIN","F A","FLU","FR","F H","G E","GRG","GEO","GER","GSD","GOV","GRS","GK","GUI","HAR","H S","HCT","HED","HEB","HIN","HIS","HDF","HDO","H E","HMN","ILA","I","ISP","INF","ITD","I B","IRG","ISL","ITL","ITC","JPN","J S","J","KIN","KOR","LAR","LTC","LAT","LAL","LAS","LAW","LEB","L A","LAH","LIN","MAL","MAN","MIS","MFG","MNS","MKT","MSE","M","M E","MDV","MAS","MEL","MES","M S","MOL","MUS","NSC","N S","NEU","NOR","N","NTR","OBO","OPR","O M","ORI","ORG","PER","PRS","PGE","PGS","PHM","PHL","PED","P S","PHY","PIA","POL","POR","PRC","PSY","P A","PBH","P R","RIM","RTF","R E","R S","RHE","R M","RUS","REE","SAN","SAX","STC","STM","S C","SEL","S S","S W","SOC","SPN","SPC","SED","SLH","STA","SDS","SUS","SWE","TAM","TXA","T D","TRO","TRU","TBA","TUR","T C","UKR","UGS","UDN","URB","URD","UTS","UTL","VIA","VIO","V C","VAS","VOI","WGS","WRT","YID","YOR"]
|
||||
BIN
icons/icon128.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
icons/icon16.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
icons/icon32.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
icons/icon48.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
images/disticon.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
196
js/Template.js
Normal file
@@ -0,0 +1,196 @@
|
||||
class Template {}
|
||||
|
||||
Template.Main = class {
|
||||
static modal() {
|
||||
return `<div class=modal id=myModal>
|
||||
<div class=modal-content>
|
||||
<span class=close>×</span>
|
||||
<div class=card>
|
||||
<div class=cardcontainer>
|
||||
<h2 class=title id="title">Computer Fluency (C S 302)</h2>
|
||||
<h2 class=profname id="profname">with <a id="professor_link">Bruce Porter</a></h2>
|
||||
<div id="topbuttons" class=topbuttons>
|
||||
<button class=material-button id="rateMyProf" style="background: #4CAF50;"> RMP </button>
|
||||
<button class=material-button id="eCIS" style="background: #CDDC39;"> eCIS </button>
|
||||
<button class=material-button id="textbook" style="background: #FFC107;"> Textbook </button>
|
||||
<button class=material-button id="Syllabi"> Past Syllabi </button>
|
||||
<button class=material-button id="saveCourse" value="add" style="background: #F44336;"> Save Course +</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class=card>
|
||||
<div class=cardcontainer style="">
|
||||
<div class="chartloader">
|
||||
<div class="loader" id='descload'></div>
|
||||
</div>
|
||||
<ul class=description id="description" style="list-style-type:disc"></ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class=card style='text-align:center'>
|
||||
<select id="semesters" style='text-align-last:center;color:#666666;fill:#666666;'>
|
||||
</select>
|
||||
<div class="chartloader">
|
||||
<div class="loader" id='chartload'></div>
|
||||
</div>
|
||||
<div id="chartcontainer" class=cardcontainer>
|
||||
<div id=chart></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
static extension_button() {
|
||||
return `<td data-th="Plus"><input type="image" class="distButton" id="distButton" width="20" height="20" src='${chrome.extension.getURL("images/disticon.png")}'/></td>`;
|
||||
}
|
||||
};
|
||||
Template.Catalog = class {
|
||||
static loading() {
|
||||
return `<div style="text-align:center">
|
||||
<div class="loader" id='loader'></div>
|
||||
<br>
|
||||
<h1 id="nextlabel"style="color: #FF9800;display:none;">Loading Courses</h1>
|
||||
<h1 id="retrylabel"style="color: #F44336;display:none;">Failed to Load Courses</h1>
|
||||
<br>
|
||||
<button class=material-button id="retry" style="background: #F44336;display:none;">Retry</button>
|
||||
</div>`;
|
||||
}
|
||||
};
|
||||
Template.UTPlanner = class {
|
||||
static modal() {
|
||||
return `<div class=modal id=myModal>
|
||||
<div class=modal-content>
|
||||
<span class=close>×</span>
|
||||
<div class=card>
|
||||
<div class=cardcontainer>
|
||||
<h2 class=title id="title">Computer Fluency (C S 302)</h2>
|
||||
<h2 class=profname id="profname" style="margin-bottom:0px;">with Bruce Porter</h2>
|
||||
<div id="topbuttons" class=topbuttons>
|
||||
<button class=material-button id="moreInfo" style="background: #2196F3;"> More Info </button>
|
||||
<button class=material-button id="textbook" style="background: #FFC107;"> Textbook </button>
|
||||
<button class=material-button id="Syllabi"> Past Syllabi </button>
|
||||
<button class=material-button id="saveCourse" value="add" style="background: #F44336;opacity:.4;"> Unable to Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class=card style='text-align:center'>
|
||||
<select id="semesters" style='text-align-last:center;color:#666666;fill:#666666;'>
|
||||
</select>
|
||||
<div class="chartloader">
|
||||
<div class="loader" id='chartload'></div>
|
||||
</div>
|
||||
<div id="chartcontainer" class=cardcontainer>
|
||||
<div id=chart></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
};
|
||||
Template.Calendar = class {
|
||||
static line(line) {
|
||||
let { days, start_time, end_time, location_link, location_full } = line;
|
||||
return `<p class='time' style='font-size:large;'>
|
||||
<span style='display:inline-block;'>${days}:</span>
|
||||
<span style='margin-left:10px;display:inline-block;text-align:center;'>${start_time} to ${end_time}</span>
|
||||
<span style='float:right;display:inline-block;text-align:right;width: 25%;'>
|
||||
<a target='_blank' style='color:#3c87a3;text-decoration:none;'href='${location_link}'>${location_full}</a>
|
||||
</span>
|
||||
</p>`;
|
||||
}
|
||||
static modal() {
|
||||
return `<div id="myModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<div class="card">
|
||||
<div id="colorStrip" style="height:10px;"></div>
|
||||
<div class="cardcontainer">
|
||||
<div id='header'>
|
||||
<div style="display:flex;">
|
||||
<h2 id="classname">Classname</h2>
|
||||
</div>
|
||||
<p id="prof">Prof</p>
|
||||
</div>
|
||||
<button id="info" class="matbut" style="font-size:medium; margin-right: auto; margin-left:auto; background: #2196F3;">More Info</button>
|
||||
<button id="register" class="matbut" style="font-size:medium; margin-right: auto; margin-left:10px; background: #4CAF50;">Register</button>
|
||||
<button id="remove" class="matbut" style="font-size:medium;margin:10px;background: #FF0000;">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
};
|
||||
Template.Popup = class {
|
||||
static list_item(i, list_tile_color, unique, department, number, profname, list_sub_color, line) {
|
||||
return `<li id='${i}' class='course_list_item'>
|
||||
<div class='card course_list_card'>
|
||||
<div class='container' style='background:${list_tile_color}'>
|
||||
<button class='copy_button' title='Copy Unique #' value='${unique}'>
|
||||
<i id='copyicon' class="material-icons copy_button_icon">content_copy</i>
|
||||
</button>
|
||||
<h4 class='course_name_truncate_box'>
|
||||
<b>${department} ${number} <span class='course_list_item_subtext'> with ${profname} (${unique})</span></b>
|
||||
</h4>
|
||||
<p id='arrow' class='arrow'>►</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id='moreInfo' class='course_list_item_options'>
|
||||
<p style='background-color:${list_sub_color};' class='course_list_item_time_box'>${line}</p>
|
||||
<div id='infoButtons' class='course_list_item_options_button_container'>
|
||||
<button class='material_button course_list_item_options_buttons remove_button' id='listRemove'>Remove</button>
|
||||
<button class='material_button course_list_item_options_buttons register_button' id='register'>Register</button>
|
||||
<button class='material_button course_list_item_options_buttons more_info_button' id='listMoreInfo'>More Info</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>`;
|
||||
}
|
||||
|
||||
static conflict_message(conflict_message) {
|
||||
return `<p id='conflict' class='conflict_message'>${conflict_message}</>`;
|
||||
}
|
||||
|
||||
static line(line) {
|
||||
let { days, start_time, end_time, location_link, location_full } = line;
|
||||
|
||||
return `<span class='time_line_days'>${days}:</span>
|
||||
<span class='time_line_hours'>${start_time} to ${end_time}</span>
|
||||
<span class='time_line_location'>
|
||||
<a target='_blank' class= 'time_line_location_link' href='${location_link}'>${location_full}</a>
|
||||
</span>
|
||||
<br>`;
|
||||
}
|
||||
};
|
||||
Template.Import = class {
|
||||
static import_button() {
|
||||
return `<button class='material-button' id='import' style='margin:15px 0px;'>${Text.button_text_default}</button><br>`;
|
||||
}
|
||||
|
||||
static waitlist_import_button() {
|
||||
return `<button class='material-button' id='import_waitlist' style='margin:0px'>${Text.waitlist_button_text_default}</button><br>`;
|
||||
}
|
||||
|
||||
static store_waitlist_message() {
|
||||
return `<h1 id="nextlabel"style="color: #FF9800;display:none;"></h1>`;
|
||||
}
|
||||
};
|
||||
|
||||
Template.Options = class {
|
||||
static options_row(key, enabled) {
|
||||
let button_text = enabled ? "Turn Off" : "Turn On";
|
||||
let button_color = enabled ? Colors.closed : Colors.open;
|
||||
let label_text = capitalizeString(key.replace(/([A-Z]+)*([A-Z][a-z])/g, "$1 $2"));
|
||||
return `<h2 style="padding: 5px 16px 5px 16px; font-weight: normal;display: inline-block;text-align:left;">
|
||||
${label_text}
|
||||
</h2>
|
||||
<button id="${key}" value=${enabled} class="material-button" style="display:inline-block;font-size:medium; float:right; background:${button_color}">
|
||||
${button_text}
|
||||
</button>
|
||||
<br>`;
|
||||
}
|
||||
|
||||
static contributor_card(username, name, image_url, profile_url) {
|
||||
return `<div class='card contributor-card' id="${username}" data-url="${profile_url}">
|
||||
<img class='contributor-image' src="${image_url}"></img>
|
||||
${name ? `<p class='contributor-name'>${name}</p>` : ""}
|
||||
<p class='contributor-username'>${username}</p>
|
||||
</div>`;
|
||||
}
|
||||
};
|
||||
487
js/background.js
Normal file
@@ -0,0 +1,487 @@
|
||||
console.log(`UT Registration Plus background page: ${window.location.href}`);
|
||||
var grades; // caching the grades database in memory for faster queries
|
||||
var current_semesters = {};
|
||||
var departments = [];
|
||||
var should_open = false; // toggled flag for automatically opening popup on new pages when 'more info' hit
|
||||
|
||||
// these are the default options that the extension currently supports
|
||||
const default_options = {
|
||||
loadAll: true,
|
||||
courseConflictHighlight: true,
|
||||
storeWaitlist: true,
|
||||
};
|
||||
|
||||
onStartup();
|
||||
|
||||
function onStartup() {
|
||||
updateBadge(true);
|
||||
loadDataBase();
|
||||
getCurrentSemesters();
|
||||
getCurrentDepartments();
|
||||
}
|
||||
|
||||
/* Handle messages and their commands from content and popup scripts*/
|
||||
chrome.runtime.onMessage.addListener(function (request, sender, response) {
|
||||
switch (request.command) {
|
||||
case "courseStorage":
|
||||
if (request.action == "add") {
|
||||
add(request, sender, response);
|
||||
}
|
||||
if (request.action == "remove") {
|
||||
remove(request, sender, response);
|
||||
}
|
||||
break;
|
||||
case "isSingleConflict":
|
||||
isSingleConflict(request.dtarr, request.unique, response);
|
||||
break;
|
||||
case "checkConflicts":
|
||||
checkConflicts(response);
|
||||
break;
|
||||
case "updateBadge":
|
||||
updateBadge();
|
||||
break;
|
||||
case "updateStatus":
|
||||
updateStatus(response);
|
||||
break;
|
||||
case "alreadyContains":
|
||||
alreadyContains(request.unique, response);
|
||||
break;
|
||||
case "updateCourseList":
|
||||
updateTabs();
|
||||
break;
|
||||
case "gradesQuery":
|
||||
executeQuery(request.query, response);
|
||||
break;
|
||||
case "currentSemesters":
|
||||
response({ semesters: current_semesters });
|
||||
getCurrentSemesters();
|
||||
break;
|
||||
case "currentDepartments":
|
||||
response({ departments: departments });
|
||||
break;
|
||||
case "setOpen":
|
||||
should_open = true;
|
||||
chrome.tabs.create({ url: request.url });
|
||||
break;
|
||||
case "shouldOpen":
|
||||
response({ open: should_open });
|
||||
should_open = false;
|
||||
break;
|
||||
case "getOptionsValue":
|
||||
getOptionsValue(request.key, response);
|
||||
break;
|
||||
case "setOptionsValue":
|
||||
setOptionsValue(request.key, request.value, response);
|
||||
break;
|
||||
default:
|
||||
const xhr = new XMLHttpRequest();
|
||||
const method = request.method ? request.method.toUpperCase() : "GET";
|
||||
xhr.open(method, request.url, true);
|
||||
console.log(request);
|
||||
xhr.onload = () => {
|
||||
console.log(xhr.responseUrl);
|
||||
response(xhr.responseText);
|
||||
};
|
||||
xhr.onerror = () => response(xhr.statusText);
|
||||
if (method == "POST") {
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
}
|
||||
xhr.send(request.data);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
/* Initially set the course data in storage */
|
||||
chrome.runtime.onInstalled.addListener(function (details) {
|
||||
if (details.reason == "install") {
|
||||
setDefaultOptions();
|
||||
chrome.storage.sync.get("savedCourses", function (data) {
|
||||
if (!data.savedCourses) {
|
||||
chrome.storage.sync.set({
|
||||
savedCourses: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (details.reason == "update") {
|
||||
// if there's been an update, call setDefaultOptions in case their settings have gotten wiped
|
||||
setDefaultOptions();
|
||||
console.log("updated");
|
||||
}
|
||||
});
|
||||
|
||||
chrome.storage.onChanged.addListener(function (changes) {
|
||||
for (key in changes) {
|
||||
if (key === "savedCourses") {
|
||||
updateBadge(false, changes.savedCourses.newValue); // update the extension popup badge whenever the savedCourses have been changed
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// get the value of an option if it exists
|
||||
function getOptionsValue(key, sendResponse) {
|
||||
chrome.storage.sync.get("options", function (data) {
|
||||
if (!data.options) {
|
||||
setDefaultOptions();
|
||||
} else {
|
||||
sendResponse({
|
||||
value: data.options[key],
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// set the value of an option if it exists
|
||||
function setOptionsValue(key, value, sendResponse) {
|
||||
chrome.storage.sync.get("options", function (data) {
|
||||
let new_options = data.options;
|
||||
if (!data.options) {
|
||||
// if there are no options set, set the defaults
|
||||
setDefaultOptions();
|
||||
new_options = default_options;
|
||||
}
|
||||
new_options[key] = value;
|
||||
chrome.storage.sync.set(
|
||||
{
|
||||
options: new_options,
|
||||
},
|
||||
function () {
|
||||
sendResponse({
|
||||
value: new_options[key],
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// set the default options if the options haven't been set before
|
||||
function setDefaultOptions() {
|
||||
chrome.storage.sync.get("options", function (data) {
|
||||
if (!data.options) {
|
||||
chrome.storage.sync.set(
|
||||
{
|
||||
options: default_options,
|
||||
},
|
||||
function () {
|
||||
console.log("default options:", default_options);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function getCurrentSemesters() {
|
||||
let webData;
|
||||
if(Object.keys(current_semesters).length > 0) {
|
||||
chrome.storage.local.set({
|
||||
semesterCache: current_semesters
|
||||
});
|
||||
}
|
||||
async function goFetch(linkend="") {
|
||||
console.log("lk " + linkend)
|
||||
return fetch("https://registrar.utexas.edu/schedules/" + linkend)
|
||||
.then((response) => {
|
||||
return response.text()
|
||||
.then((data) => {
|
||||
return data;
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
await goFetch().then((data) => {webData = data});
|
||||
if(webData == null) {
|
||||
webData = ""
|
||||
}
|
||||
let arr = webData.split("\n");
|
||||
let i = 0
|
||||
for(let row=0; row<arr.length; row++) {
|
||||
let currentRow = arr[row]
|
||||
if(currentRow.startsWith('<li><a href="https://registrar.utexas.edu/schedules/') && currentRow[52] != "a") {
|
||||
let newWebData;
|
||||
|
||||
// let start = currentRow.indexOf('Schedule">')+10;
|
||||
let start = Math.max(currentRow.lastIndexOf('Summer'), Math.max(currentRow.lastIndexOf('Spring'), currentRow.lastIndexOf('Fall')))
|
||||
let end = currentRow.indexOf('</a></li>');
|
||||
console.log(currentRow)
|
||||
console.log(start + " " + end)
|
||||
let name = currentRow.substring(start,end);
|
||||
console.log("my name: " + name)
|
||||
|
||||
let num = currentRow.indexOf('"https://registrar.utexas.edu/schedules/">')+53;
|
||||
let numend = currentRow.indexOf('" target');
|
||||
let short_sem_num = currentRow.substring(num,numend);
|
||||
current_semesters[name] = "code";
|
||||
|
||||
await goFetch(short_sem_num).then((data) => {newWebData = data});
|
||||
arr2 = newWebData.split("\n")
|
||||
|
||||
for(let row2=0; row2<arr2.length; row2++) {
|
||||
if(arr2[row2].startsWith('<div class="gobutton"><a href="')) {
|
||||
let start2 = arr2[row2].indexOf('<div class="gobutton"><a href="')+31;
|
||||
let end2 = arr2[row2].indexOf('" target="');
|
||||
var scheduleLink = arr2[row2].substring(start2,end2);
|
||||
var sem_num = scheduleLink.substring(scheduleLink.lastIndexOf("/") + 1).trim();
|
||||
if (current_semesters[name] != sem_num) {
|
||||
current_semesters[name] = sem_num;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use the utexas review api for getting the list of departments
|
||||
function getCurrentDepartments() {
|
||||
$.get("https://raw.githubusercontent.com/sghsri/UT-Registration-Plus/master/docs/departments.json", function (response) {
|
||||
if (response) {
|
||||
departments = JSON.parse(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// update the badge text to reflect the new changes
|
||||
function updateBadge(first, new_changes) {
|
||||
if (new_changes) {
|
||||
updateBadgeText(first, new_changes);
|
||||
} else {
|
||||
chrome.storage.sync.get("savedCourses", function (data) {
|
||||
let courses = data.savedCourses;
|
||||
updateBadgeText(first, courses);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// update the badge text to show the number of courses that have been saved by the user
|
||||
function updateBadgeText(first, courses) {
|
||||
let badge_text = courses.length > 0 ? `${courses.length}` : "";
|
||||
let flash_time = !first ? 200 : 0;
|
||||
chrome.browserAction.setBadgeText({
|
||||
text: badge_text,
|
||||
});
|
||||
if (!first) {
|
||||
// if isn't the first install of the extension, flash the badge to bring attention to it
|
||||
chrome.browserAction.setBadgeBackgroundColor({
|
||||
color: Colors.badge_flash,
|
||||
});
|
||||
}
|
||||
setTimeout(function () {
|
||||
chrome.browserAction.setBadgeBackgroundColor({
|
||||
color: Colors.badge_default,
|
||||
});
|
||||
}, flash_time);
|
||||
}
|
||||
|
||||
/* Find all the conflicts in the courses and send them out/ if there is even a conflict*/
|
||||
function checkConflicts(sendResponse) {
|
||||
chrome.storage.sync.get("savedCourses", function (data) {
|
||||
var conflicts = [];
|
||||
var courses = data.savedCourses;
|
||||
for (let i = 0; i < courses.length; i++) {
|
||||
for (let j = i + 1; j < courses.length; j++) {
|
||||
let course_a = courses[i];
|
||||
let course_b = courses[j];
|
||||
if (isConflict(course_a.datetimearr, course_b.datetimearr)) conflicts.push([course_a, course_b]);
|
||||
}
|
||||
}
|
||||
sendResponse({
|
||||
isConflict: conflicts.length !== 0,
|
||||
between: conflicts.length ? conflicts : undefined,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* Find if the course at unique and with currdatearr is contained in the saved courses and if it conflicts with any other courses*/
|
||||
function isSingleConflict(currdatearr, unique, sendResponse) {
|
||||
chrome.storage.sync.get("savedCourses", function (data) {
|
||||
var courses = data.savedCourses;
|
||||
var conflict_list = [];
|
||||
var conflict = false;
|
||||
var contains = false;
|
||||
for (let i = 0; i < courses.length; i++) {
|
||||
let course = courses[i];
|
||||
if (isConflict(currdatearr, course.datetimearr)) {
|
||||
conflict = true;
|
||||
conflict_list.push(course);
|
||||
}
|
||||
if (!contains && isSameCourse(course, unique)) {
|
||||
contains = true;
|
||||
}
|
||||
}
|
||||
sendResponse({
|
||||
isConflict: conflict,
|
||||
alreadyContains: contains,
|
||||
conflictList: conflict_list,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* Check if conflict between two date-time-arrs*/
|
||||
function isConflict(adtarr, bdtarr) {
|
||||
for (var i = 0; i < adtarr.length; i++) {
|
||||
var current_day = adtarr[i][0];
|
||||
var current_times = adtarr[i][1];
|
||||
for (var j = 0; j < bdtarr.length; j++) {
|
||||
var next_day = bdtarr[j][0];
|
||||
var next_times = bdtarr[j][1];
|
||||
if (next_day == current_day) {
|
||||
if (current_times[0] < next_times[1] && current_times[1] > next_times[0]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Add the requested course to the storage*/
|
||||
function add(request, sender, sendResponse) {
|
||||
chrome.storage.sync.get("savedCourses", function (data) {
|
||||
var courses = data.savedCourses;
|
||||
if (!contains(courses, request.course.unique)) {
|
||||
courses.push(request.course);
|
||||
console.log(courses);
|
||||
chrome.storage.sync.set({
|
||||
savedCourses: courses,
|
||||
});
|
||||
}
|
||||
sendResponse({
|
||||
done: "Added: (" + request.course.unique + ") " + request.course.coursename,
|
||||
label: "Remove Course -",
|
||||
value: "remove",
|
||||
});
|
||||
});
|
||||
}
|
||||
/* Find and Remove the requested course from the storage*/
|
||||
function remove(request, sender, sendResponse) {
|
||||
chrome.storage.sync.get("savedCourses", function (data) {
|
||||
var courses = data.savedCourses;
|
||||
console.log(courses);
|
||||
var index = 0;
|
||||
while (index < courses.length && courses[index].unique != request.course.unique) {
|
||||
index++;
|
||||
}
|
||||
courses.splice(index, 1);
|
||||
chrome.storage.sync.set({
|
||||
savedCourses: courses,
|
||||
});
|
||||
sendResponse({
|
||||
done: "Removed: (" + request.course.unique + ") " + request.course.coursename,
|
||||
label: "Add Course +",
|
||||
value: "add",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* Find if the unique is already contained within the storage*/
|
||||
function alreadyContains(unique, sendResponse) {
|
||||
chrome.storage.sync.get("savedCourses", function (data) {
|
||||
var courses = data.savedCourses;
|
||||
sendResponse({
|
||||
alreadyContains: contains(courses, unique),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// find if a course with the current unique number exists in the user's saved courses
|
||||
function contains(courses, unique) {
|
||||
var i = 0;
|
||||
while (i < courses.length) {
|
||||
if (isSameCourse(courses[i], unique)) {
|
||||
return true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// does it have the same unique number as provided
|
||||
function isSameCourse(course, unique) {
|
||||
return course.unique == unique;
|
||||
}
|
||||
|
||||
// send a message to every tab open to updateit's course list (and thus recalculate its conflicts highlighting)
|
||||
function updateTabs() {
|
||||
chrome.tabs.query({}, function (tabs) {
|
||||
for (var i = 0; i < tabs.length; i++) {
|
||||
chrome.tabs.sendMessage(tabs[i].id, {
|
||||
command: "updateCourseList",
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// const UPDATE_INTERVAL = 1000 * 60 * 16;
|
||||
// setInterval(updateStatus, UPDATE_INTERVAL);
|
||||
// // updateStatus();
|
||||
|
||||
// function updateStatus(sendResponse) {
|
||||
// chrome.storage.sync.get("savedCourses", function (data) {
|
||||
// var courses = data.savedCourses;
|
||||
// var no_change = true;
|
||||
// for (let i = 0; i < courses.length; i++) {
|
||||
// try {
|
||||
// let c = courses[i];
|
||||
// let old_status = c.status;
|
||||
// let old_link = c.link;
|
||||
// $.ajax({
|
||||
// url: old_link,
|
||||
// success: function (result) {
|
||||
// if (result) {
|
||||
// console.log(result);
|
||||
// var object = $("<div/>").html(result).contents();
|
||||
// let new_status = object.find('[data-th="Status"]').text();
|
||||
// let register_link = object.find('td[data-th="Add"] a');
|
||||
// if (register_link) register_link = register_link.attr("href");
|
||||
// var haschanged = new_status == old_status && register_link == old_link;
|
||||
// if (!haschanged) console.log(c.unique + " updated from " + old_status + " to " + new_status + " and " + old_link + " to " + register_link);
|
||||
// no_change &= haschanged;
|
||||
// c.registerlink = register_link;
|
||||
// c.status = new_status;
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
// } catch (e) {
|
||||
// console.log(e);
|
||||
// console.log("Not logged into UT Coursebook. Could not update class statuses.");
|
||||
// }
|
||||
// }
|
||||
// if (!no_change) {
|
||||
// chrome.storage.sync.set({
|
||||
// savedCourses: courses,
|
||||
// });
|
||||
// console.log("updated status");
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// execute a query on the grades database
|
||||
function executeQuery(query, sendResponse) {
|
||||
var res = grades.exec(query)[0];
|
||||
sendResponse({
|
||||
data: res,
|
||||
});
|
||||
}
|
||||
|
||||
/* Load the database*/
|
||||
function loadDataBase() {
|
||||
sql = window.SQL;
|
||||
loadBinaryFile("grades.db", function (data) {
|
||||
var sqldb = new SQL.Database(data);
|
||||
grades = sqldb;
|
||||
});
|
||||
}
|
||||
/* load the database from file */
|
||||
function loadBinaryFile(path, success) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", chrome.extension.getURL(path), true);
|
||||
xhr.responseType = "arraybuffer";
|
||||
xhr.onload = function () {
|
||||
var data = new Uint8Array(xhr.response);
|
||||
var arr = new Array();
|
||||
for (var i = 0; i != data.length; ++i) arr[i] = String.fromCharCode(data[i]);
|
||||
success(arr.join(""));
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
291
js/calendar.js
Normal file
@@ -0,0 +1,291 @@
|
||||
var color_counter = 0;
|
||||
var {
|
||||
calendar_fade_time,
|
||||
button_delay
|
||||
} = Timing;
|
||||
|
||||
|
||||
|
||||
var saved_courses = [];
|
||||
var curr_course = {}
|
||||
|
||||
$("#calendar").after(Template.Calendar.modal());
|
||||
|
||||
chrome.storage.sync.get("savedCourses", function (data) {
|
||||
// Iterate through each saved course and add to 'event'
|
||||
saved_courses = data.savedCourses;
|
||||
console.log(saved_courses);
|
||||
let event_source = buildEventSource(saved_courses);
|
||||
|
||||
$("#calendar").fullCalendar({
|
||||
editable: false, // Don't allow editing of events
|
||||
handleWindowResize: true,
|
||||
weekends: false, // will hide Saturdays and Sundays
|
||||
slotDuration: "00:30:00", // 15 minute intervals on vertical column
|
||||
slotEventOverlap: false, // No overlapping between events
|
||||
defaultView: "agendaWeek", // Only show week view
|
||||
header: false, // Hide buttons/titles
|
||||
minTime: "08:00:00", // Start time
|
||||
maxTime: "21:00:01", // End time
|
||||
columnHeaderFormat: "ddd", // Only show day of the week names
|
||||
displayEventTime: true, // Display event time
|
||||
allDaySlot: false,
|
||||
Duration: {
|
||||
hours: 1
|
||||
},
|
||||
height: 'auto',
|
||||
events: event_source,
|
||||
slotLabelFormat: [
|
||||
'h:mm A' // lower level of text
|
||||
],
|
||||
eventRender: function (event, element, view) {
|
||||
$(element).css("padding", "5px").css("margin-bottom", "5px");
|
||||
},
|
||||
eventClick: function (data, event, view) {
|
||||
displayModal(data)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function displayModal(data) {
|
||||
$("#myModal").fadeIn(calendar_fade_time);
|
||||
$("#colorStrip").css('background-color', data.color);
|
||||
curr_course = saved_courses[data.index];
|
||||
setUpModal()
|
||||
}
|
||||
|
||||
function setUpModal() {
|
||||
let {
|
||||
coursename,
|
||||
unique,
|
||||
datetimearr,
|
||||
profname,
|
||||
status,
|
||||
registerlink
|
||||
} = curr_course;
|
||||
$("#classname").html(`${coursename} <span style='font-size:small'>(${unique})</span>`);
|
||||
buildTimeTitle(datetimearr);
|
||||
$("#prof").html(`with <span style='font-weight:bold;'>${capitalizeString(profname)}</span>`);
|
||||
setRegisterButton(status, registerlink)
|
||||
}
|
||||
|
||||
function setRegisterButton(status, registerlink) {
|
||||
if (canNotRegister(status, registerlink))
|
||||
$("#register").text("Can't Register").css("background-color", Colors.closed);
|
||||
else if (status.includes("waitlisted"))
|
||||
$("#register").text("Join Waitlist").css("background-color", Colors.waitlisted);
|
||||
else
|
||||
$("#register").text("Register").css("background-color", Colors.open);
|
||||
}
|
||||
|
||||
function buildTimeTitle(datetimearr) {
|
||||
$('#timelines').remove();
|
||||
var arr = convertDateTimeArrToLine(datetimearr)
|
||||
var output = "";
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
let line = arr[i];
|
||||
output += Template.Calendar.line(line);
|
||||
}
|
||||
$("#header").after(`<div id='timelines'>${output}</div`);
|
||||
}
|
||||
|
||||
|
||||
// Iterate through each saved course and add to 'event'
|
||||
function buildEventSource(saved_courses) {
|
||||
color_counter = 0;
|
||||
let event_source = [];
|
||||
var hours = 0;
|
||||
for (let i = 0; i < saved_courses.length; i++) {
|
||||
let {
|
||||
coursename,
|
||||
datetimearr
|
||||
} = saved_courses[i];
|
||||
let number = separateCourseNameParts(coursename).number;
|
||||
let class_length = parseInt(number.charAt(0));
|
||||
let multi_semester_code = number.slice(-1);
|
||||
if (["A","B"].includes(multi_semester_code)) {
|
||||
hours += Math.floor(class_length/2);
|
||||
} else if (["X","Y","Z"].includes(multi_semester_code)) {
|
||||
hours += Math.floor(class_length/3);
|
||||
} else {
|
||||
hours += class_length;
|
||||
}
|
||||
for (let j = 0; j < datetimearr.length; j++) {
|
||||
let session = datetimearr[j]; // One single session for a class
|
||||
let event_obj = setEventForSection(session, color_counter, i);
|
||||
event_source.push(event_obj);
|
||||
}
|
||||
color_counter++;
|
||||
}
|
||||
displayMetaData(hours, saved_courses);
|
||||
return event_source;
|
||||
}
|
||||
|
||||
function displayMetaData(hours, saved_courses) {
|
||||
$("#hours").text(hours + " Hours");
|
||||
$("#num").text(saved_courses.length + " Courses");
|
||||
}
|
||||
|
||||
//create the event object for every section
|
||||
function setEventForSection(session, colorCounter, i) {
|
||||
let full_day = days.get(session[0]);
|
||||
let course = saved_courses[i];
|
||||
let {
|
||||
coursename,
|
||||
profname,
|
||||
} = course;
|
||||
let {
|
||||
department,
|
||||
number
|
||||
} = separateCourseNameParts(coursename)
|
||||
beg_day = calculateBeginningDate(full_day)
|
||||
start_date = formatCalculateDate(beg_day, full_day, session[1][0]);
|
||||
end_date = formatCalculateDate(beg_day, full_day, session[1][1]);
|
||||
|
||||
event_obj = {
|
||||
title: `${department}-${number} with ${capitalizeString(profname)}`,
|
||||
start: start_date,
|
||||
end: end_date,
|
||||
color: Colors.material_colors[colorCounter],
|
||||
building: session[2],
|
||||
index: i,
|
||||
allday: false
|
||||
};
|
||||
return event_obj;
|
||||
}
|
||||
|
||||
function formatCalculateDate(beg_day, full_day, hour) {
|
||||
return beg_day + moment().day(full_day)._d.toString().split(" ")[2] + "T" + hour + ":00";
|
||||
}
|
||||
|
||||
function calculateBeginningDate(full_day) {
|
||||
var year = moment().day(full_day)._d.toString().split(" ")[3];
|
||||
var month_num = moment(moment().day(full_day)._d.toString().split(" ")[1], "MMM").format('MM');
|
||||
return `${year}-${month_num}-`;
|
||||
}
|
||||
|
||||
function updateCalendar() {
|
||||
chrome.storage.sync.get("savedCourses", function (data) {
|
||||
saved_courses = data.savedCourses
|
||||
let event_source = buildEventSource(saved_courses);
|
||||
$('#calendar').fullCalendar('removeEventSources');
|
||||
$("#calendar").fullCalendar('addEventSource', event_source, true);
|
||||
});
|
||||
}
|
||||
chrome.runtime.onMessage.addListener(
|
||||
function (request, sender, sendResponse) {
|
||||
if (request.command == "updateCourseList" || request.command == "courseAdded") {
|
||||
updateCalendar();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
$("#info").click(() => {
|
||||
openMoreInfoWithOpenModal(curr_course.link);
|
||||
});
|
||||
|
||||
|
||||
$("#save").click(() => {
|
||||
takePicture();
|
||||
});
|
||||
|
||||
|
||||
$("#clear").click(() => {
|
||||
/*Clear the list and the storage of courses*/
|
||||
chrome.storage.sync.set({
|
||||
savedCourses: []
|
||||
});
|
||||
updateAllTabsCourseList();
|
||||
updateCalendar();
|
||||
});
|
||||
|
||||
|
||||
$("#remove").click(() => {
|
||||
setTimeout(() => {
|
||||
chrome.runtime.sendMessage({
|
||||
command: "courseStorage",
|
||||
course: curr_course,
|
||||
action: "remove"
|
||||
}, function () {
|
||||
$("#myModal").fadeOut(calendar_fade_time);
|
||||
updateCalendar();
|
||||
updateAllTabsCourseList();
|
||||
});
|
||||
}, button_delay);
|
||||
});
|
||||
|
||||
|
||||
$("#register").click(function () {
|
||||
let {
|
||||
registerlink,
|
||||
status
|
||||
} = curr_course;
|
||||
if (!canNotRegister(status, registerlink)) {
|
||||
setTimeout(() => {
|
||||
window.open(registerlink);
|
||||
}, button_delay);
|
||||
}
|
||||
});
|
||||
|
||||
$("#export").click(function () {
|
||||
var cal = ics();
|
||||
var calendarEvents = $('#calendar').fullCalendar('clientEvents');
|
||||
for (i in calendarEvents) {
|
||||
var event = calendarEvents[i];
|
||||
buildICSFile(cal, event);
|
||||
}
|
||||
cal.download("My_Course_Calendar");
|
||||
});
|
||||
|
||||
|
||||
function buildICSFile(cal, event) {
|
||||
let {
|
||||
title,
|
||||
start,
|
||||
end,
|
||||
building
|
||||
} = event;
|
||||
let class_name = title.split('with')[0];
|
||||
let description = `with ${title.split('with')[1]}`;
|
||||
let time = start._d.toUTCString();
|
||||
cal.addEvent(class_name, description, building, start._i, end._i, {
|
||||
rrule: `RRULE:FREQ=WEEKLY;BYDAY=${time.substring(0, time.indexOf(",") - 1).toUpperCase()};INTERVAL=1`
|
||||
});
|
||||
}
|
||||
|
||||
function takePicture() {
|
||||
var width = $("#calendar").width() * window.devicePixelRatio;
|
||||
var height = $("#calendar").height() * window.devicePixelRatio;
|
||||
let cropper = document.createElement('canvas').getContext('2d');
|
||||
html2canvas(document.querySelector("#calendar"), Export.png_options).then(c => {
|
||||
cropper.canvas.width = width;
|
||||
cropper.canvas.height = height;
|
||||
cropper.drawImage(c, 0, 0);
|
||||
var a = document.createElement('a');
|
||||
a.href = cropper.canvas.toDataURL("image/png");
|
||||
a.download = 'mySchedule.png';
|
||||
a.click();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*Close Modal when hit escape*/
|
||||
$(document).keydown((e) => {
|
||||
if (e.keyCode == 27) {
|
||||
$("#myModal").fadeOut(calendar_fade_time);
|
||||
}
|
||||
});
|
||||
|
||||
$('.close').click(function () {
|
||||
close();
|
||||
});
|
||||
$('#myModal').click(function (event) {
|
||||
if (event.target.id == 'myModal') {
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
function close() {
|
||||
$("#myModal").fadeOut(calendar_fade_time);
|
||||
}
|
||||
93
js/config.js
Normal file
@@ -0,0 +1,93 @@
|
||||
class Timing {}
|
||||
Timing.fade_time = 100;
|
||||
Timing.calendar_fade_time = 100;
|
||||
Timing.button_delay = 75;
|
||||
|
||||
class Colors {}
|
||||
Colors.material_colors = [
|
||||
"#4CAF50",
|
||||
"#CDDC39",
|
||||
"#FFC107",
|
||||
"#2196F3",
|
||||
"#F57C00",
|
||||
"#9C27B0",
|
||||
"#FF5722",
|
||||
"#673AB7",
|
||||
"#FF5252",
|
||||
"#E91E63",
|
||||
"#009688",
|
||||
"#00BCD4",
|
||||
"#4E342E",
|
||||
"#424242",
|
||||
"#9E9E9E",
|
||||
];
|
||||
Colors.open = "#4CAF50";
|
||||
Colors.waitlisted = "#FF9800";
|
||||
Colors.closed = "#FF5722";
|
||||
Colors.no_status = "#607D8B";
|
||||
|
||||
Colors.open_light = "#C8E6C9";
|
||||
Colors.waitlisted_light = "#FFE0B2";
|
||||
Colors.closed_light = "#FFCCBC";
|
||||
Colors.no_status_light = "#CFD8DC";
|
||||
|
||||
Colors.highlight_conflict = "#F44336";
|
||||
Colors.highlight_default = "#333333";
|
||||
Colors.highlight_saved = "#4CAF50";
|
||||
|
||||
Colors.badge_flash = "#FF5722";
|
||||
Colors.badge_default = "#bf5700";
|
||||
|
||||
class Export {}
|
||||
Export.png_options = {
|
||||
foreignObjectRendering: true,
|
||||
logging: true,
|
||||
removeContainer: true,
|
||||
async: true,
|
||||
};
|
||||
|
||||
class Popup {}
|
||||
Popup.num_semesters = 2;
|
||||
|
||||
/*
|
||||
* Funny comments that popup when no classes have been chosen
|
||||
*/
|
||||
class Text {}
|
||||
Text.emptyText = function () {
|
||||
let arr = [
|
||||
"Doesn't Look Like Anything To Me.",
|
||||
"You Can't Fail Classes You're Not In.",
|
||||
"Pro-Tip: Don't Take O-Chem.",
|
||||
"Jendy's Fofofo™",
|
||||
"Fine Dining at Jester City Limits",
|
||||
"Rec Sports is full and it's only 2pm.",
|
||||
"Hope Domino is doing well rn 🥺",
|
||||
"The year is 2055 and Welch still isn't finished.",
|
||||
"Wear a Mask.",
|
||||
"Motivation dropping faster than ur GPA",
|
||||
"No Work Happens On PCL 5th Floor.",
|
||||
"Sophomore But Freshman By Credit.",
|
||||
"Pain is temporary, GPA is forever.",
|
||||
"You've Yee'd Your Last Haw.",
|
||||
"lol everything is already waitlisted.",
|
||||
"At Least You're Not At A&M.",
|
||||
`It's ${moment().format("h:mm")} and OU Still Sucks.`,
|
||||
"TeXAs iS BaCK GuYZ",
|
||||
"'Academically Challenged'",
|
||||
"Does McCombs teach Parseltongue?",
|
||||
"Feel bad if you say Wampus.",
|
||||
"No Cruce Enfrente Del Bus.",
|
||||
"Midterm 1 has been Unmuted",
|
||||
"Omae Wa Mou Shindeiru...",
|
||||
"Bevo Bucks are the new Bitcoin",
|
||||
"Every day, another brick disappears from Speedway",
|
||||
"The GDC will annex the EER one day",
|
||||
"To hike to Kins, or not to hike to Kins...",
|
||||
];
|
||||
let index = Math.floor(Math.random() * arr.length);
|
||||
|
||||
return arr[index];
|
||||
};
|
||||
Text.button_text_default = "<span style='font-size:small'>Import to </span><b>UT Reg +<b>";
|
||||
Text.waitlist_button_text_default = "<span style='font-size:small'>Import Waitlists to </span><b>UT Reg +<b>";
|
||||
Text.button_success = "Courses Saved!";
|
||||
627
js/courseCatalog.js
Normal file
@@ -0,0 +1,627 @@
|
||||
console.log(`UT Registration Plus is running on this page: ${window.location.href}`);
|
||||
|
||||
var curr_course = {}
|
||||
|
||||
var semester_code = new URL(window.location.href).pathname.split('/')[4];
|
||||
var done_loading = true;
|
||||
|
||||
var next = $("#next_nav_link");
|
||||
if (next) {
|
||||
chrome.runtime.sendMessage({
|
||||
command: "getOptionsValue",
|
||||
key: "loadAll",
|
||||
}, function (response) {
|
||||
if(response.value){
|
||||
$('[title*="next listing"]').remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//This extension may be super lit, but you know what's even more lit?
|
||||
//Matthew Tran's twitter and insta: @MATTHEWTRANN and @matthew.trann
|
||||
|
||||
if (document.querySelector('#fos_fl')) {
|
||||
let params = (new URL(document.location)).searchParams;
|
||||
let dep = params.get("fos_fl");
|
||||
let level = params.get("level");
|
||||
if (dep && level) {
|
||||
if (dep.length == 3 && (level == 'U' || level == 'L' || level == 'G')) {
|
||||
document.querySelector('#fos_fl').value = dep;
|
||||
document.querySelector('#level').value = level;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//make heading and modal
|
||||
if (!$("#kw_results_table").length) {
|
||||
$("table").after(Template.Catalog.loading());
|
||||
$("#container").prepend(Template.Main.modal());
|
||||
$("#myModal").prepend("<div id='snackbar'>save course popup...</div>");
|
||||
|
||||
// now add to the table
|
||||
$("table thead th:last-child").after('<th scope=col>Plus</th>');
|
||||
$('table').find('tr').each(function () {
|
||||
if (!($(this).find('td').hasClass("course_header")) && $(this).has('th').length == 0) {
|
||||
$(this).append(Template.Main.extension_button());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(isIndividualCoursePage()){
|
||||
chrome.runtime.sendMessage({
|
||||
command: "shouldOpen",
|
||||
}, function (response) {
|
||||
if(response.open){
|
||||
$("#distButton").click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateListConflictHighlighting();
|
||||
|
||||
$("body").on('click', '#distButton', function () {
|
||||
var row = $(this).closest('tr');
|
||||
$('.modal-content').stop().animate({
|
||||
scrollTop: 0
|
||||
}, 500);
|
||||
$(this).blur();
|
||||
curr_course = getCourseInfo(row);
|
||||
getDistribution(curr_course);
|
||||
});
|
||||
|
||||
|
||||
function updateLinks(course_info, first_name) {
|
||||
let {
|
||||
prof_name,
|
||||
number
|
||||
} = course_info;
|
||||
course_info["first_name"] = first_name;
|
||||
course_info["links"]["rate_my_prof"] = `http://www.ratemyprofessors.com/search.jsp?queryBy=teacherName&schoolName=university+of+texas+at+austin&queryoption=HEADER&query=${first_name} ${prof_name};&facetSearch=true`;
|
||||
course_info["links"]["ecis"] = profname ? `http://utdirect.utexas.edu/ctl/ecis/results/index.WBX?&s_in_action_sw=S&s_in_search_type_sw=N&s_in_search_name=${prof_name}%2C%20${first_name}` :
|
||||
`http://utdirect.utexas.edu/ctl/ecis/results/index.WBX?s_in_action_sw=S&s_in_search_type_sw=C&s_in_max_nbr_return=10&s_in_search_course_dept=${department}&s_in_search_course_num=${number}`;
|
||||
}
|
||||
|
||||
|
||||
function buildCourseLinks(course_info) {
|
||||
let {
|
||||
department,
|
||||
number,
|
||||
unique,
|
||||
prof_name
|
||||
} = course_info
|
||||
links = {
|
||||
"textbook": `https://www.universitycoop.com/adoption-search-results?sn=${semester_code}__${department}__${number}__${unique}`,
|
||||
"syllabi": `https://utdirect.utexas.edu/apps/student/coursedocs/nlogon/?year=&semester=&department=${department}&course_number=${number}&course_title=&unique=&instructor_first=&instructor_last=${prof_name}&course_type=In+Residence&search=Search`,
|
||||
//default ones (before first name can be used)
|
||||
"rate_my_prof": "http://www.ratemyprofessors.com/campusRatings.jsp?sid=1255",
|
||||
"ecis": "http://utdirect.utexas.edu/ctl/ecis/results/index.WBX?"
|
||||
}
|
||||
course_info["links"] = links;
|
||||
return course_info;
|
||||
}
|
||||
|
||||
function buildBasicCourseInfo(row, course_name, individual) {
|
||||
let {
|
||||
name,
|
||||
department,
|
||||
number
|
||||
} = separateCourseNameParts(course_name);
|
||||
let instructor_text = $(row).find('td[data-th="Instructor"]').text();
|
||||
let has_initial = instructor_text.indexOf(',') > 0;
|
||||
course_info = {
|
||||
"full_name": course_name,
|
||||
"name": name,
|
||||
"department": department,
|
||||
"number": number,
|
||||
"individual": individual ? individual : $(row).find('td[data-th="Unique"] a').prop('href'),
|
||||
"register": $(row).find('td[data-th="Add"] a').prop('href'),
|
||||
"unique": $(row).find('td[data-th="Unique"]').text(),
|
||||
"status": $(row).find('td[data-th="Status"]').text(),
|
||||
"prof_name": instructor_text ? has_initial ? capitalizeString(instructor_text.split(', ')[0]) : capitalizeString(instructor_text) : "Undecided",
|
||||
"initial": instructor_text && has_initial ? instructor_text.split(', ')[1].substring(0, 1) : "",
|
||||
"time_data": {
|
||||
"days": $(row).find('td[data-th="Days"]>span').toArray().map(x => $(x).text().trim()),
|
||||
"times": $(row).find('td[data-th="Hour"]>span').toArray().map(x => $(x).text().trim()),
|
||||
"places": $(row).find('td[data-th="Room"]>span').toArray().map(x => $(x).text().trim())
|
||||
},
|
||||
"links": {}
|
||||
}
|
||||
return buildCourseLinks(course_info);
|
||||
}
|
||||
|
||||
/*For a row, get all the course information and add the date-time-lines*/
|
||||
function getCourseInfo(row) {
|
||||
let course_name = "";
|
||||
let course_row = {}
|
||||
let individual = undefined;
|
||||
if (isIndividualCoursePage()) {
|
||||
course_name = $("#details h2").text();
|
||||
course_row = $('table');
|
||||
individual = document.URL;
|
||||
} else {
|
||||
$('table').find('tr').each(function () {
|
||||
if ($(this).find('td').hasClass("course_header")) {
|
||||
course_name = $(this).find('td').text() + "";
|
||||
}
|
||||
if ($(this).is(row)) {
|
||||
course_row = row;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
curr_course = buildBasicCourseInfo(course_row, course_name, individual);
|
||||
getDescription(curr_course);
|
||||
return curr_course;
|
||||
}
|
||||
|
||||
function saveCourse() {
|
||||
console.log(curr_course);
|
||||
console.log(JSON.stringify(curr_course));
|
||||
let {
|
||||
full_name,
|
||||
unique,
|
||||
prof_name,
|
||||
status,
|
||||
individual,
|
||||
register
|
||||
} = curr_course;
|
||||
let dtarr = getDayTimeArray(undefined, curr_course);
|
||||
|
||||
var c = new Course(full_name, unique, prof_name, dtarr, status, individual, register);
|
||||
chrome.runtime.sendMessage({
|
||||
command: "courseStorage",
|
||||
course: c,
|
||||
action: $("#saveCourse").val()
|
||||
}, function (response) {
|
||||
$("#saveCourse").text(response.label);
|
||||
$("#saveCourse").val(response.value);
|
||||
$("#snackbar").text(response.done);
|
||||
toggleSnackbar();
|
||||
chrome.runtime.sendMessage({
|
||||
command: "updateCourseList"
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* Update the course list to show if the row contains a course that conflicts with the saved course is one of the saved courses */
|
||||
function updateListConflictHighlighting(start = 0) {
|
||||
chrome.runtime.sendMessage({
|
||||
command: "getOptionsValue",
|
||||
key: "courseConflictHighlight",
|
||||
}, function (response) {
|
||||
let canHighlight = response.value;
|
||||
$('table').find('tr').each(function (i) {
|
||||
if (i >= start) {
|
||||
if (!($(this).find('td').hasClass("course_header")) && $(this).has('th').length == 0) {
|
||||
var unique = $(this).find('td[data-th="Unique"]').text();
|
||||
chrome.runtime.sendMessage({
|
||||
command: "isSingleConflict",
|
||||
dtarr: getDayTimeArray(this),
|
||||
unique: unique
|
||||
}, (response) => {
|
||||
let {
|
||||
isConflict,
|
||||
alreadyContains,
|
||||
conflictList
|
||||
} = response
|
||||
updateTextHighlighting($(this).find('td'), canHighlight, isConflict, alreadyContains, conflictList, $(this), unique);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateTextHighlighting(tds, canHighlight, isConflict, alreadyContains, conflictList, row, unique) {
|
||||
conflict_texts = row.find('.tooltiptext');
|
||||
let unique_list = conflictList.filter(function(course){
|
||||
if(course.unique != unique){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}).map(function(course){
|
||||
let { name, department, number} = separateCourseNameParts(course.coursename);
|
||||
return `${department} ${number} (${course.unique})`;
|
||||
});
|
||||
if(isConflict && unique_list.length){
|
||||
if(conflict_texts){
|
||||
row.find('.tooltiptext').remove();
|
||||
}
|
||||
row.addClass('tooltip');
|
||||
row.append(`<span class='tooltiptext'><span style='text-decoration: underline;'>Conflicts:<br></span> ${unique_list.join('<br>')}</span>`);
|
||||
} else {
|
||||
row.removeClass('tooltip');
|
||||
conflict_texts.remove();
|
||||
}
|
||||
let current_color = rgb2hex(tds.css('color'));
|
||||
if (isConflict && canHighlight && !alreadyContains) {
|
||||
if (current_color != Colors.highlight_conflict){
|
||||
tds.css('color', Colors.highlight_conflict).css('text-decoration', 'line-through').css('font-weight', 'normal')
|
||||
}
|
||||
|
||||
} else if (!alreadyContains) {
|
||||
if (tds.css('color') != Colors.highlight_default)
|
||||
tds.css('color', Colors.highlight_default).css('text-decoration', 'none').css('font-weight', 'normal');
|
||||
}
|
||||
if (alreadyContains) {
|
||||
if (tds.css('color') != Colors.highlight_saved)
|
||||
tds.css('color', Colors.highlight_saved).css('text-decoration', 'none').css('font-weight', 'bold');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* For a row, get the date-time-array for checking conflicts*/
|
||||
function getDayTimeArray(row, course_info) {
|
||||
var day_time_array = []
|
||||
let days = course_info ? course_info["time_data"]["days"] : $(row).find('td[data-th="Days"]>span').toArray().map(x => $(x).text().trim());
|
||||
let times = course_info ? course_info["time_data"]["times"] : $(row).find('td[data-th="Hour"]>span').toArray().map(x => $(x).text().trim());
|
||||
let places = course_info ? course_info["time_data"]["places"] : $(row).find('td[data-th="Room"]>span').toArray().map(x => $(x).text().trim());
|
||||
for (var i = 0; i < days.length; i++) {
|
||||
let date = days[i];
|
||||
let time = times[i];
|
||||
let place = places[i];
|
||||
for (var j = 0; j < date.length; j++) {
|
||||
let letter = date.charAt(j);
|
||||
if (letter == "T" && j < date.length - 1 && date.charAt(j + 1) == "H") {
|
||||
day_time_array.push(["TH", convertTime(time), place]);
|
||||
} else {
|
||||
if (letter != "H")
|
||||
day_time_array.push([letter, convertTime(time), place]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return day_time_array;
|
||||
}
|
||||
|
||||
function convertDateTimeArrToLine(date, time, place) {
|
||||
let arr = separateDays(date)
|
||||
let output = prettifyDaysText(arr)
|
||||
let building = place.substring(0, place.search(/\d/) - 1);
|
||||
building = building ? building : "Undecided Location";
|
||||
return `${output} at ${time.replace(/\./g, '').replace(/\-/g, ' to ')} in <a style='font-size:medium' target='_blank' href='https://maps.utexas.edu/buildings/UTM/${building}'>${building}</>`;
|
||||
}
|
||||
|
||||
function badData(course_data, res) {
|
||||
return typeof res == 'undefined' || course_data["prof_name"] == "Undecided";
|
||||
}
|
||||
|
||||
/*Query the grades database*/
|
||||
function getDistribution(course_data, sem) {
|
||||
toggleChartLoading(true);
|
||||
let query = buildQuery(course_data, sem);
|
||||
chrome.runtime.sendMessage({
|
||||
command: "gradesQuery",
|
||||
query: query
|
||||
}, function (response) {
|
||||
var res = response.data;
|
||||
if (!sem) {
|
||||
openDialog(course_data, res);
|
||||
} else {
|
||||
var data = badData(course_data, res) ? [] : res.values[0];
|
||||
setChart(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildTitle(course_data) {
|
||||
return `${course_data["name"]} (${course_data["department"]} ${course_data["number"]})`
|
||||
}
|
||||
|
||||
function buildTimeTitle(course_info) {
|
||||
$("h2.dateTimePlace").remove();
|
||||
let {
|
||||
days,
|
||||
times,
|
||||
places
|
||||
} = course_info["time_data"]
|
||||
var lines = [];
|
||||
for (let i = 0; i < days.length; i++) {
|
||||
var date = days[i];
|
||||
var time = times[i];
|
||||
var place = places[i];
|
||||
lines.push($(`<h2 class="dateTimePlace">${convertDateTimeArrToLine(date, time, place)}</th>`));
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function buildProfTitle(course_data) {
|
||||
const {
|
||||
initial,
|
||||
prof_name
|
||||
} = course_data;
|
||||
return `with ${initial?initial+". ":""}${prof_name}`;
|
||||
}
|
||||
|
||||
|
||||
function buildSemestersDropdown(course_data, res) {
|
||||
$("#semesters").empty();
|
||||
if (badData(course_data, res)) {
|
||||
$("#semesters").append("<option>No Data</option>")
|
||||
} else {
|
||||
var semesters = res.values[0][18].split(",");
|
||||
semesters.sort(semesterSort);
|
||||
semesters.reverse().unshift('Aggregate');
|
||||
var sems = [];
|
||||
for (var i = 0; i < semesters.length; i++) {
|
||||
sems.push($(`<option value="${semesters[i]}">${semesters[i]}</option>`));
|
||||
}
|
||||
$("#semesters").append(sems);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function displayBasicCourseInfo(course_info){
|
||||
$("#title").text(buildTitle(course_info))
|
||||
$("#topbuttons").before(buildTimeTitle(course_info));
|
||||
$("#profname").text(buildProfTitle(course_info));
|
||||
$("#myModal").fadeIn(Timing.fade_time);
|
||||
console.log(course_info);
|
||||
}
|
||||
|
||||
/*Open the modal and show all the data*/
|
||||
function openDialog(course_info, res) {
|
||||
displayBasicCourseInfo(course_info);
|
||||
//initial text on the "save course button"
|
||||
chrome.runtime.sendMessage({
|
||||
command: "alreadyContains",
|
||||
unique: course_info["unique"]
|
||||
}, function (response) {
|
||||
|
||||
let button_text = response.alreadyContains ? "Remove Course -" : "Add Course +";
|
||||
let button_val = response.alreadyContains ? "remove" : "add";
|
||||
$("#saveCourse").text(button_text);
|
||||
$("#saveCourse").val(button_val);
|
||||
});
|
||||
buildSemestersDropdown(course_info, res)
|
||||
var data = []
|
||||
if (!badData(course_info, res))
|
||||
data = res.values[0];
|
||||
allowClosing();
|
||||
setChart(data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function setChart(data) {
|
||||
// set up the chart
|
||||
toggleChartLoading(false);
|
||||
Highcharts.chart('chart', buildChartConfig(data), function (chart) { // on complete
|
||||
if (data.length == 0) {
|
||||
//if no data, then show the message and hide the series
|
||||
chart.renderer.text('Could not find data for this Instructor teaching this Course.', 100, 120)
|
||||
.css({
|
||||
fontSize: '20px',
|
||||
width: '300px',
|
||||
align: 'center',
|
||||
left: '160px'
|
||||
})
|
||||
.add();
|
||||
$.each(chart.series, function (i, ser) {
|
||||
ser.hide();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var error_message = "<p style='color:red;font-style:bold'>You have been logged out. Please refresh the page and log back in using your UT EID and password.</p>";
|
||||
|
||||
function buildFormattedDescription(description_lines) {
|
||||
let description = ""
|
||||
for (let i in description_lines) {
|
||||
let sentence = description_lines[i];
|
||||
if (sentence.indexOf("Prerequisite") == 0)
|
||||
sentence = `<li style='font-weight: bold;' class='descriptionli'>${sentence}</li>`;
|
||||
else if (sentence.indexOf("May be") >= 0)
|
||||
sentence = `<li style='font-style: italic;' class='descriptionli'>${sentence}</li>`;
|
||||
else if (sentence.indexOf("Restricted to") == 0)
|
||||
sentence = `<li style='color:red;' class='descriptionli'>${sentence}</li>`;
|
||||
else
|
||||
sentence = `<li class='descriptionli'>${sentence}</li>`;
|
||||
description += sentence;
|
||||
}
|
||||
if (!description)
|
||||
description = error_message;
|
||||
return description;
|
||||
}
|
||||
|
||||
function extractFirstName(response_node) {
|
||||
let full_name = response_node.find('td[data-th="Instructor"]').text().split(', ');
|
||||
let first = full_name[full_name.length - 1];
|
||||
first = first.indexOf(' ') > 0 ? first.split(' ')[0] : first;
|
||||
return capitalizeString(first);
|
||||
}
|
||||
|
||||
function displayDescription(description) {
|
||||
toggleDescriptionLoading(false);
|
||||
$("#description").animate({
|
||||
'opacity': 0
|
||||
}, 200, function () {
|
||||
$(this).html(description).animate({
|
||||
'opacity': 1
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
|
||||
/*Get the course description from the profurl and highlight the important elements, as well as set the eCIS, and rmp links.*/
|
||||
function getDescription(course_info) {
|
||||
toggleDescriptionLoading(true);
|
||||
$.ajax({
|
||||
url: course_info["individual"],
|
||||
success: function (response) {
|
||||
if (response) {
|
||||
let response_node = htmlToNode(response);
|
||||
description_lines = response_node.find('#details > p').toArray().map(x => $(x).text());
|
||||
displayDescription(buildFormattedDescription(description_lines));
|
||||
let first_name = extractFirstName(response_node);
|
||||
updateLinks(course_info, first_name);
|
||||
} else {
|
||||
displayDescription(error_message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadNextPages(num_pages) {
|
||||
if (num_pages === undefined) num_pages = 1;
|
||||
if (num_pages == 0) return;
|
||||
chrome.runtime.sendMessage({
|
||||
command: "getOptionsValue",
|
||||
key: "loadAll",
|
||||
}, function (response) {
|
||||
if(response.value){
|
||||
let link = next.prop('href');
|
||||
if (done_loading && next && link) {
|
||||
toggleLoadingPage(true);
|
||||
$.get(link, function (response) {
|
||||
if (response) {
|
||||
var next_page = htmlToNode(response);
|
||||
var current = $('tbody');
|
||||
var old_length = $('tbody tr').length;
|
||||
var last = current.find('.course_header>h2:last').text();
|
||||
next = next_page.find("#next_nav_link");
|
||||
toggleLoadingPage(false);
|
||||
var new_rows = [];
|
||||
next_page.find('tbody>tr').each(function () {
|
||||
let has_course_header = $(this).find('td').hasClass("course_header");
|
||||
if (!(has_course_header && $(this).has('th').length == 0))
|
||||
$(this).append(Template.Main.extension_button());
|
||||
if (!(has_course_header && last == $(this).find('td').text()))
|
||||
new_rows.push($(this));
|
||||
});
|
||||
current.append(new_rows);
|
||||
updateListConflictHighlighting(old_length + 1)
|
||||
}
|
||||
loadNextPages(num_pages-1);
|
||||
}).fail(function () {
|
||||
toggleLoadingPage(false);
|
||||
$("#retrylabel").css('display', 'inline-block');
|
||||
$('#retry').css('display', 'inline-block');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$("#myModal").on('click', '#saveCourse', function () {
|
||||
setTimeout(function () {
|
||||
saveCourse();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
$("#Syllabi").click(function () {
|
||||
setTimeout(function () {
|
||||
window.open(curr_course["links"]["syllabi"]);
|
||||
}, Timing.button_delay);
|
||||
});
|
||||
$("#rateMyProf").click(function () {
|
||||
setTimeout(function () {
|
||||
window.open(curr_course["links"]["rate_my_prof"]);
|
||||
}, Timing.button_delay);
|
||||
});
|
||||
$("#eCIS").click(function () {
|
||||
setTimeout(function () {
|
||||
window.open(curr_course["links"]["ecis"]);
|
||||
}, Timing.button_delay);
|
||||
});
|
||||
$("#textbook").click(function () {
|
||||
setTimeout(function () {
|
||||
window.open(curr_course["links"]["textbook"]);
|
||||
}, Timing.button_delay);
|
||||
});
|
||||
$("#semesters").on('change', function () {
|
||||
let sem = $(this).val();
|
||||
sem = sem == "Aggregate" ? undefined : sem;
|
||||
getDistribution(curr_course, sem);
|
||||
});
|
||||
|
||||
$("#retry").click(function () {
|
||||
$("#retrylabel").hide();
|
||||
$(this).hide();
|
||||
loadNextPages();
|
||||
});
|
||||
|
||||
function toggleLoadingPage(loading) {
|
||||
if (loading) {
|
||||
done_loading = false;
|
||||
$('#loader').css('display', 'inline-block');
|
||||
$("#nextlabel").css('display', 'inline-block');
|
||||
} else {
|
||||
done_loading = true;
|
||||
$('#loader').hide();
|
||||
$("#nextlabel").hide();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleChartLoading(loading) {
|
||||
if (loading) {
|
||||
$('#chartload').css('display', 'inline-block');
|
||||
$("#chart").hide();
|
||||
} else {
|
||||
$('#chartload').hide();
|
||||
$("#chart").show();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDescriptionLoading(loading) {
|
||||
if (loading) {
|
||||
$('#descload').css('display', 'inline-block');
|
||||
} else {
|
||||
$('#descload').hide();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSnackbar() {
|
||||
setTimeout(function () {
|
||||
$("#snackbar").attr("class", "show");
|
||||
}, 200);
|
||||
setTimeout(function () {
|
||||
$("#snackbar").attr("class", "");
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
|
||||
function allowClosing() {
|
||||
$('.close').click(function () {
|
||||
close();
|
||||
});
|
||||
$('#myModal').click(function (event) {
|
||||
if (event.target.id == 'myModal') {
|
||||
close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function close() {
|
||||
$("#myModal").fadeOut(Timing.fade_time);
|
||||
$("#snackbar").attr("class", "");
|
||||
}
|
||||
|
||||
/*Listen for update mssage coming from popup or calendar or other course catalog pages*/
|
||||
chrome.runtime.onMessage.addListener(
|
||||
function (request, sender, sendResponse) {
|
||||
if (request.command == "updateCourseList") {
|
||||
updateListConflictHighlighting(0);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$(document).keydown(function (e) {
|
||||
/*Close Modal when hit escape*/
|
||||
if (e.keyCode == 27) {
|
||||
close();
|
||||
} else if (e.keyCode == 13 && $('#myModal').is(':visible')) {
|
||||
saveCourse();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$(window).scroll(function () {
|
||||
if ($(document).height() <= $(window).scrollTop() + $(window).height() + 150)
|
||||
loadNextPages();
|
||||
});
|
||||
|
||||
|
||||
$(window).on('load', function () {
|
||||
loadNextPages(3);
|
||||
});
|
||||
164
js/import.js
Normal file
@@ -0,0 +1,164 @@
|
||||
var waitlist;
|
||||
var sem;
|
||||
$(function () {
|
||||
waitlist = !(window.location.href.includes('https://utdirect.utexas.edu/registration/classlist.WBX'));
|
||||
sem = waitlist ? $('[name="s_ccyys"]').val() : $("option[selected='selected']").val();
|
||||
if (waitlist) {
|
||||
$("[href='#top']").before(Template.Import.import_button());
|
||||
$("[name='wl_see_my_waitlists']").before(Template.Import.store_waitlist_message());
|
||||
$("[name='wl_see_my_waitlists']").after(Template.Import.waitlist_import_button());
|
||||
extractWaitlistInfo();
|
||||
} else {
|
||||
$("table").after(Template.Import.import_button());
|
||||
}
|
||||
$("#import").prepend("<div id='snackbar'>import snackbar..</div>");
|
||||
|
||||
$("#import").click(function () {
|
||||
search_nodes = waitlist ? $(".tbg").last().find(".tbon>td:first-child") : $("tr>td:first-child");
|
||||
$(search_nodes).each(function () {
|
||||
importCourse($(this), true);
|
||||
})
|
||||
importButtonAnimation($(this));
|
||||
});
|
||||
|
||||
$("#import_waitlist").click(function () {
|
||||
search_nodes = $("tr.tb span:first-child");
|
||||
$(search_nodes).each(function () {
|
||||
importCourse($(this), false);
|
||||
})
|
||||
importButtonAnimation($(this));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function extractWaitlistInfo(){
|
||||
let class_boxes = $("[name='wl_see_my_waitlists']>table");
|
||||
let waitlist_info = [];
|
||||
$(class_boxes).each(function(){
|
||||
let data = $(this).find('tr.tb span');
|
||||
let unique_num = $(data[0]).text().trim();
|
||||
let class_name = $(data[1]).text().trim().split('\n').filter(part => part.trim() != '').map(part => part.trim()).join(' ');
|
||||
let waitlist_size = $(this).find('tr.tbon:eq(2) td:eq(1)').text().trim().split(' of ')[1];
|
||||
|
||||
waitlist_info.push({
|
||||
"id": unique_num,
|
||||
"class": class_name,
|
||||
"wait": waitlist_size,
|
||||
"time": moment().format('DD-MM-YYYY HH:mm:ss')
|
||||
});
|
||||
});
|
||||
console.log(waitlist_info);
|
||||
return waitlist_info;
|
||||
}
|
||||
|
||||
|
||||
function importButtonAnimation(button) {
|
||||
let is_waitlisted_button = $(button).attr('id') == "import_waitlist";
|
||||
let return_text = is_waitlisted_button ? Text.waitlist_button_text_default : Text.button_text_default;
|
||||
$(button).text(Text.button_success).css("background-color", Colors.open);
|
||||
setTimeout(function () {
|
||||
$(button).html(return_text).css('background-color', Colors.waitlisted);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function importCourse(unique_node, force) {
|
||||
let unique = $(unique_node).text().replace(/\s/g, '').substring(0,5);
|
||||
link = `https://utdirect.utexas.edu/apps/registrar/course_schedule/${sem}/${unique}/`;
|
||||
buildAddCourse(link, force)
|
||||
}
|
||||
|
||||
|
||||
function buildAddCourse(link, force) {
|
||||
$.get(link, function (response) {
|
||||
if (response) {
|
||||
let simp_course = buildSimplifiedCourseObject(response, link, force);
|
||||
chrome.runtime.sendMessage({
|
||||
command: "courseStorage",
|
||||
course: simp_course,
|
||||
action: "add"
|
||||
}, function () {
|
||||
chrome.runtime.sendMessage({
|
||||
command: "updateCourseList"
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function buildSimplifiedCourseObject(response, link, force) {
|
||||
let imported_course = getCourseObject(htmlToNode(response), link);
|
||||
let {
|
||||
full_name,
|
||||
unique,
|
||||
prof_name,
|
||||
individual,
|
||||
status,
|
||||
register
|
||||
} = curr_course;
|
||||
let dtarr = getDayTimeArray(undefined, curr_course);
|
||||
if(force === true) {
|
||||
status = "open" //forces the green status for courses a user is already registered for
|
||||
}
|
||||
return new Course(full_name, unique, prof_name, dtarr, status, individual, register);
|
||||
}
|
||||
|
||||
/*For a row, get all the course information and add the date-time-lines*/
|
||||
function getCourseObject(response_node, individual) {
|
||||
let course_name = $(response_node).find("#details h2").text();
|
||||
let course_row = $(response_node).find('table');
|
||||
curr_course = buildBasicCourseInfo(course_row, course_name, individual);
|
||||
}
|
||||
|
||||
|
||||
function buildBasicCourseInfo(row, course_name, individual) {
|
||||
let {
|
||||
name,
|
||||
department,
|
||||
number
|
||||
} = separateCourseNameParts(course_name);
|
||||
let instructor_text = $(row).find('td[data-th="Instructor"]').text();
|
||||
let has_initial = instructor_text.indexOf(',') > 0;
|
||||
course_info = {
|
||||
"full_name": course_name,
|
||||
"name": name,
|
||||
"department": department,
|
||||
"number": number,
|
||||
"individual": individual ? individual : $(row).find('td[data-th="Unique"] a').prop('href'),
|
||||
"register": $(row).find('td[data-th="Add"] a').prop('href'),
|
||||
"unique": $(row).find('td[data-th="Unique"]').text(),
|
||||
"status": $(row).find('td[data-th="Status"]').text(),
|
||||
"prof_name": instructor_text ? has_initial ? capitalizeString(instructor_text.split(', ')[0]) : capitalizeString(instructor_text) : "Undecided",
|
||||
"initial": instructor_text && has_initial ? instructor_text.split(', ')[1].substring(0, 1) : "",
|
||||
"time_data": {
|
||||
"days": $(row).find('td[data-th="Days"]>span').toArray().map(x => $(x).text().trim()),
|
||||
"times": $(row).find('td[data-th="Hour"]>span').toArray().map(x => $(x).text().trim()),
|
||||
"places": $(row).find('td[data-th="Room"]>span').toArray().map(x => $(x).text().trim())
|
||||
},
|
||||
"links": {}
|
||||
}
|
||||
return course_info;
|
||||
}
|
||||
|
||||
/* For a row, get the date-time-array for checking conflicts*/
|
||||
function getDayTimeArray(row, course_info) {
|
||||
var day_time_array = []
|
||||
let days = course_info ? course_info["time_data"]["days"] : $(row).find('td[data-th="Days"]>span').toArray().map(x => $(x).text().trim());
|
||||
let times = course_info ? course_info["time_data"]["times"] : $(row).find('td[data-th="Hour"]>span').toArray().map(x => $(x).text().trim());
|
||||
let places = course_info ? course_info["time_data"]["places"] : $(row).find('td[data-th="Room"]>span').toArray().map(x => $(x).text().trim());
|
||||
for (var i = 0; i < days.length; i++) {
|
||||
let date = days[i];
|
||||
let time = times[i];
|
||||
let place = places[i];
|
||||
for (var j = 0; j < date.length; j++) {
|
||||
let letter = date.charAt(j);
|
||||
if (letter == "T" && j < date.length - 1 && date.charAt(j + 1) == "H") {
|
||||
day_time_array.push(["TH", convertTime(time), place]);
|
||||
} else {
|
||||
if (letter != "H")
|
||||
day_time_array.push([letter, convertTime(time), place]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return day_time_array;
|
||||
}
|
||||
1
js/lib/fullcalendar.min.js
vendored
Normal file
8737
js/lib/highcharts.js
Normal file
6
js/lib/html2canvas.min.js
vendored
Normal file
231
js/lib/ics.min.js
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
/*! ics.js Wed Aug 20 2014 17:23:02 */
|
||||
var saveAs = saveAs || function (e) {
|
||||
"use strict";
|
||||
if (typeof e === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
|
||||
return
|
||||
}
|
||||
var t = e.document,
|
||||
n = function () {
|
||||
return e.URL || e.webkitURL || e
|
||||
},
|
||||
r = t.createElementNS("http://www.w3.org/1999/xhtml", "a"),
|
||||
o = "download" in r,
|
||||
a = function (e) {
|
||||
var t = new MouseEvent("click");
|
||||
e.dispatchEvent(t)
|
||||
},
|
||||
i = /constructor/i.test(e.HTMLElement) || e.safari,
|
||||
f = /CriOS\/[\d]+/.test(navigator.userAgent),
|
||||
u = function (t) {
|
||||
(e.setImmediate || e.setTimeout)(function () {
|
||||
throw t
|
||||
}, 0)
|
||||
},
|
||||
s = "application/octet-stream",
|
||||
d = 1e3 * 40,
|
||||
c = function (e) {
|
||||
var t = function () {
|
||||
if (typeof e === "string") {
|
||||
n().revokeObjectURL(e)
|
||||
} else {
|
||||
e.remove()
|
||||
}
|
||||
};
|
||||
setTimeout(t, d)
|
||||
},
|
||||
l = function (e, t, n) {
|
||||
t = [].concat(t);
|
||||
var r = t.length;
|
||||
while (r--) {
|
||||
var o = e["on" + t[r]];
|
||||
if (typeof o === "function") {
|
||||
try {
|
||||
o.call(e, n || e)
|
||||
} catch (a) {
|
||||
u(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
p = function (e) {
|
||||
if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)) {
|
||||
return new Blob([String.fromCharCode(65279), e], {
|
||||
type: e.type
|
||||
})
|
||||
}
|
||||
return e
|
||||
},
|
||||
v = function (t, u, d) {
|
||||
if (!d) {
|
||||
t = p(t)
|
||||
}
|
||||
var v = this,
|
||||
w = t.type,
|
||||
m = w === s,
|
||||
y, h = function () {
|
||||
l(v, "writestart progress write writeend".split(" "))
|
||||
},
|
||||
S = function () {
|
||||
if ((f || m && i) && e.FileReader) {
|
||||
var r = new FileReader;
|
||||
r.onloadend = function () {
|
||||
var t = f ? r.result : r.result.replace(/^data:[^;]*;/, "data:attachment/file;");
|
||||
var n = e.open(t, "_blank");
|
||||
if (!n) e.location.href = t;
|
||||
t = undefined;
|
||||
v.readyState = v.DONE;
|
||||
h()
|
||||
};
|
||||
r.readAsDataURL(t);
|
||||
v.readyState = v.INIT;
|
||||
return
|
||||
}
|
||||
if (!y) {
|
||||
y = n().createObjectURL(t)
|
||||
}
|
||||
if (m) {
|
||||
e.location.href = y
|
||||
} else {
|
||||
var o = e.open(y, "_blank");
|
||||
if (!o) {
|
||||
e.location.href = y
|
||||
}
|
||||
}
|
||||
v.readyState = v.DONE;
|
||||
h();
|
||||
c(y)
|
||||
};
|
||||
v.readyState = v.INIT;
|
||||
if (o) {
|
||||
y = n().createObjectURL(t);
|
||||
setTimeout(function () {
|
||||
r.href = y;
|
||||
r.download = u;
|
||||
a(r);
|
||||
h();
|
||||
c(y);
|
||||
v.readyState = v.DONE
|
||||
});
|
||||
return
|
||||
}
|
||||
S()
|
||||
},
|
||||
w = v.prototype,
|
||||
m = function (e, t, n) {
|
||||
return new v(e, t || e.name || "download", n)
|
||||
};
|
||||
if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
|
||||
return function (e, t, n) {
|
||||
t = t || e.name || "download";
|
||||
if (!n) {
|
||||
e = p(e)
|
||||
}
|
||||
return navigator.msSaveOrOpenBlob(e, t)
|
||||
}
|
||||
}
|
||||
w.abort = function () {};
|
||||
w.readyState = w.INIT = 0;
|
||||
w.WRITING = 1;
|
||||
w.DONE = 2;
|
||||
w.error = w.onwritestart = w.onprogress = w.onwrite = w.onabort = w.onerror = w.onwriteend = null;
|
||||
return m
|
||||
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content);
|
||||
if (typeof module !== "undefined" && module.exports) {
|
||||
module.exports.saveAs = saveAs
|
||||
} else if (typeof define !== "undefined" && define !== null && define.amd !== null) {
|
||||
define("FileSaver.js", function () {
|
||||
return saveAs
|
||||
})
|
||||
}
|
||||
|
||||
var ics = function (e, t) {
|
||||
"use strict"; {
|
||||
if (!(navigator.userAgent.indexOf("MSIE") > -1 && -1 == navigator.userAgent.indexOf("MSIE 10"))) {
|
||||
void 0 === e && (e = "default"), void 0 === t && (t = "Calendar");
|
||||
var r = -1 !== navigator.appVersion.indexOf("Win") ? "\r\n" : "\n",
|
||||
n = [],
|
||||
i = ["BEGIN:VCALENDAR", "PRODID:" + t, "VERSION:2.0"].join(r),
|
||||
o = r + "END:VCALENDAR",
|
||||
a = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"];
|
||||
return {
|
||||
events: function () {
|
||||
return n
|
||||
},
|
||||
calendar: function () {
|
||||
return i + r + n.join(r) + o
|
||||
},
|
||||
addEvent: function (t, i, o, l, u, s) {
|
||||
if (void 0 === t || void 0 === i || void 0 === o || void 0 === l || void 0 === u) return !1;
|
||||
if (s && !s.rrule) {
|
||||
if ("YEARLY" !== s.freq && "MONTHLY" !== s.freq && "WEEKLY" !== s.freq && "DAILY" !== s.freq) throw "Recurrence rrule frequency must be provided and be one of the following: 'YEARLY', 'MONTHLY', 'WEEKLY', or 'DAILY'";
|
||||
if (s.until && isNaN(Date.parse(s.until))) throw "Recurrence rrule 'until' must be a valid date string";
|
||||
if (s.interval && isNaN(parseInt(s.interval))) throw "Recurrence rrule 'interval' must be an integer";
|
||||
if (s.count && isNaN(parseInt(s.count))) throw "Recurrence rrule 'count' must be an integer";
|
||||
if (void 0 !== s.byday) {
|
||||
if ("[object Array]" !== Object.prototype.toString.call(s.byday)) throw "Recurrence rrule 'byday' must be an array";
|
||||
if (s.byday.length > 7) throw "Recurrence rrule 'byday' array must not be longer than the 7 days in a week";
|
||||
s.byday = s.byday.filter(function (e, t) {
|
||||
return s.byday.indexOf(e) == t
|
||||
});
|
||||
for (var c in s.byday)
|
||||
if (a.indexOf(s.byday[c]) < 0) throw "Recurrence rrule 'byday' values must include only the following: 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'"
|
||||
}
|
||||
}
|
||||
var g = new Date(l),
|
||||
d = new Date(u),
|
||||
f = new Date,
|
||||
S = ("0000" + g.getFullYear().toString()).slice(-4),
|
||||
E = ("00" + (g.getMonth() + 1).toString()).slice(-2),
|
||||
v = ("00" + g.getDate().toString()).slice(-2),
|
||||
y = ("00" + g.getHours().toString()).slice(-2),
|
||||
A = ("00" + g.getMinutes().toString()).slice(-2),
|
||||
T = ("00" + g.getSeconds().toString()).slice(-2),
|
||||
b = ("0000" + d.getFullYear().toString()).slice(-4),
|
||||
D = ("00" + (d.getMonth() + 1).toString()).slice(-2),
|
||||
N = ("00" + d.getDate().toString()).slice(-2),
|
||||
h = ("00" + d.getHours().toString()).slice(-2),
|
||||
I = ("00" + d.getMinutes().toString()).slice(-2),
|
||||
R = ("00" + d.getMinutes().toString()).slice(-2),
|
||||
M = ("0000" + f.getFullYear().toString()).slice(-4),
|
||||
w = ("00" + (f.getMonth() + 1).toString()).slice(-2),
|
||||
L = ("00" + f.getDate().toString()).slice(-2),
|
||||
O = ("00" + f.getHours().toString()).slice(-2),
|
||||
p = ("00" + f.getMinutes().toString()).slice(-2),
|
||||
Y = ("00" + f.getMinutes().toString()).slice(-2),
|
||||
U = "",
|
||||
V = "";
|
||||
y + A + T + h + I + R != 0 && (U = "T" + y + A + T, V = "T" + h + I + R);
|
||||
var B, C = S + E + v + U,
|
||||
j = b + D + N + V,
|
||||
m = M + w + L + ("T" + O + p + Y);
|
||||
if (s)
|
||||
if (s.rrule) B = s.rrule;
|
||||
else {
|
||||
if (B = "rrule:FREQ=" + s.freq, s.until) {
|
||||
var x = new Date(Date.parse(s.until)).toISOString();
|
||||
B += ";UNTIL=" + x.substring(0, x.length - 13).replace(/[-]/g, "") + "000000Z"
|
||||
}
|
||||
s.interval && (B += ";INTERVAL=" + s.interval), s.count && (B += ";COUNT=" + s.count), s.byday && s.byday.length > 0 && (B += ";BYDAY=" + s.byday.join(","))
|
||||
}(new Date).toISOString();
|
||||
var H = ["BEGIN:VEVENT", "UID:" + n.length + "@" + e, "CLASS:PUBLIC", "DESCRIPTION:" + i, "DTSTAMP;VALUE=DATE-TIME:" + m, "DTSTART;VALUE=DATE-TIME:" + C, "DTEND;VALUE=DATE-TIME:" + j, "LOCATION:" + o, "SUMMARY;LANGUAGE=en-us:" + t, "TRANSP:TRANSPARENT", "END:VEVENT"];
|
||||
return B && H.splice(4, 0, B), H = H.join(r), n.push(H), H
|
||||
},
|
||||
download: function (e, t) {
|
||||
if (n.length < 1) return !1;
|
||||
t = void 0 !== t ? t : ".ics", e = void 0 !== e ? e : "calendar";
|
||||
var a, l = i + r + n.join(r) + o;
|
||||
if (-1 === navigator.userAgent.indexOf("MSIE 10")) a = new Blob([l]);
|
||||
else {
|
||||
var u = new BlobBuilder;
|
||||
u.append(l), a = u.getBlob("text/x-vCalendar;charset=" + document.characterSet)
|
||||
}
|
||||
return saveAs(a, e + t), l
|
||||
},
|
||||
build: function () {
|
||||
return !(n.length < 1) && i + r + n.join(r) + o
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log("Unsupported Browser")
|
||||
}
|
||||
};
|
||||
2
js/lib/jquery-3.3.1.min.js
vendored
Normal file
1
js/lib/jquery.initialize.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(function($){"use strict";var combinators=[" ",">","+","~"];var fraternisers=["+","~"];var complexTypes=["ATTR","PSEUDO","ID","CLASS"];function grok(msobserver){if(!$.find.tokenize){msobserver.isCombinatorial=true;msobserver.isFraternal=true;msobserver.isComplex=true;return}msobserver.isCombinatorial=false;msobserver.isFraternal=false;msobserver.isComplex=false;var token=$.find.tokenize(msobserver.selector);for(var i=0;i<token.length;i++){for(var j=0;j<token[i].length;j++){if(combinators.indexOf(token[i][j].type)!=-1)msobserver.isCombinatorial=true;if(fraternisers.indexOf(token[i][j].type)!=-1)msobserver.isFraternal=true;if(complexTypes.indexOf(token[i][j].type)!=-1)msobserver.isComplex=true}}}var MutationSelectorObserver=function(selector,callback,options){this.selector=selector.trim();this.callback=callback;this.options=options;grok(this)};var msobservers=[];msobservers.initialize=function(selector,callback,options){var seen=[];var callbackOnce=function(){if(seen.indexOf(this)==-1){seen.push(this);$(this).each(callback)}};$(options.target).find(selector).each(callbackOnce);var msobserver=new MutationSelectorObserver(selector,callbackOnce,options);this.push(msobserver);var observer=new MutationObserver(function(mutations){var matches=[];for(var m=0;m<mutations.length;m++){if(mutations[m].type=="attributes"){if(mutations[m].target.matches(msobserver.selector))matches.push(mutations[m].target);if(msobserver.isFraternal)matches.push.apply(matches,mutations[m].target.parentElement.querySelectorAll(msobserver.selector));else matches.push.apply(matches,mutations[m].target.querySelectorAll(msobserver.selector))}if(mutations[m].type=="childList"){for(var n=0;n<mutations[m].addedNodes.length;n++){if(!(mutations[m].addedNodes[n]instanceof Element))continue;if(mutations[m].addedNodes[n].matches(msobserver.selector))matches.push(mutations[m].addedNodes[n]);if(msobserver.isFraternal)matches.push.apply(matches,mutations[m].addedNodes[n].parentElement.querySelectorAll(msobserver.selector));else matches.push.apply(matches,mutations[m].addedNodes[n].querySelectorAll(msobserver.selector))}}}for(var i=0;i<matches.length;i++)$(matches[i]).each(msobserver.callback)});var defaultObeserverOpts={childList:true,subtree:true,attributes:msobserver.isComplex};observer.observe(options.target,options.observer||defaultObeserverOpts);return observer};$.fn.initialize=function(callback,options){return msobservers.initialize(this.selector,callback,$.extend({},$.initialize.defaults,options))};$.initialize=function(selector,callback,options){return msobservers.initialize(selector,callback,$.extend({},$.initialize.defaults,options))};$.initialize.defaults={target:document.documentElement,observer:null}})(jQuery);
|
||||
1
js/lib/moment.min.js
vendored
Normal file
28
js/lib/sql-memory-growth.js
Normal file
60
js/options.js
Normal file
@@ -0,0 +1,60 @@
|
||||
var manifestData = chrome.runtime.getManifest();
|
||||
$("#version").text(manifestData.version);
|
||||
|
||||
chrome.storage.sync.get("options", function (data) {
|
||||
if (data.options) {
|
||||
console.log(data.options);
|
||||
Object.keys(data.options).forEach(key => {
|
||||
let enabled = data.options[key];
|
||||
$("#options_container").append(Template.Options.options_row(key, enabled));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("body").on("click", "button", function () {
|
||||
let key = $(this).attr("id");
|
||||
let old_status = $(this).val() === "true";
|
||||
let new_status = !old_status;
|
||||
chrome.runtime.sendMessage(
|
||||
{
|
||||
command: "setOptionsValue",
|
||||
key: key,
|
||||
value: new_status,
|
||||
},
|
||||
function (response) {
|
||||
console.log(response.value);
|
||||
toggle(key, response.value);
|
||||
updateAllTabsCourseList();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$.get("https://api.github.com/repos/sghsri/UT-Registration-Plus/stats/contributors", data => {
|
||||
data = data.sort((a, b) => b.total - a.total);
|
||||
console.log("data", data);
|
||||
for (var contributorData of data) {
|
||||
$.get(`https://api.github.com/users/${contributorData.author.login}`, userData => {
|
||||
let fullData = { ...contributorData, ...userData };
|
||||
let { login, avatar_url, html_url, name } = fullData;
|
||||
if(name){
|
||||
$("#contributor-list").append(Template.Options.contributor_card(login, name, avatar_url, html_url));
|
||||
}
|
||||
else{
|
||||
$("#contributor-list").append(Template.Options.contributor_card("", login, avatar_url, html_url));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("body").on("click", ".contributor-card", function () {
|
||||
console.log("hello world");
|
||||
window.open($(this).data("url"), "_blank");
|
||||
});
|
||||
|
||||
function toggle(key, value) {
|
||||
let button_text = value ? "Turn Off" : "Turn On";
|
||||
let button_color = value ? Colors.closed : Colors.open;
|
||||
$(`#${key}`).text(button_text);
|
||||
$(`#${key}`).css("background", button_color);
|
||||
$(`#${key}`).val(value);
|
||||
}
|
||||
437
js/popup.js
Normal file
@@ -0,0 +1,437 @@
|
||||
var courses;
|
||||
|
||||
setCourseList();
|
||||
getSemesters();
|
||||
getDepartments();
|
||||
|
||||
var can_remove = true;
|
||||
|
||||
function setCourseList() {
|
||||
$("#courseList").empty();
|
||||
chrome.storage.sync.get("savedCourses", function (data) {
|
||||
updateConflicts();
|
||||
courses = data.savedCourses;
|
||||
handleEmpty();
|
||||
let hours = 0;
|
||||
// build and append the course list element
|
||||
for (var i = 0; i < courses.length; i++) {
|
||||
let { coursename, unique, profname, status, datetimearr } = courses[i];
|
||||
profname = capitalizeString(profname);
|
||||
let line = buildTimeLines(datetimearr);
|
||||
let list_tile_color = getStatusColor(status);
|
||||
let list_sub_color = getStatusColor(status, true);
|
||||
let { department, number } = separateCourseNameParts(coursename);
|
||||
let class_length = parseInt(number.charAt(0));
|
||||
let multi_semester_code = number.slice(-1);
|
||||
if (["A", "B"].includes(multi_semester_code)) {
|
||||
hours += Math.floor(class_length / 2);
|
||||
} else if (["X", "Y", "Z"].includes(multi_semester_code)) {
|
||||
hours += Math.floor(class_length / 3);
|
||||
} else {
|
||||
hours += class_length;
|
||||
}
|
||||
let list_html = Template.Popup.list_item(i, list_tile_color, unique, department, number, profname, list_sub_color, line);
|
||||
$("#courseList").append(list_html);
|
||||
}
|
||||
$("#meta-metric").text(hours);
|
||||
});
|
||||
}
|
||||
|
||||
/* convert from the dtarr and maek the time lines*/
|
||||
function buildTimeLines(datetimearr) {
|
||||
let lines = convertDateTimeArrToLine(datetimearr);
|
||||
let output = "";
|
||||
if (lines.length == 0) {
|
||||
output = "<span style='font-size:medium;'>This class has no meeting times.</span>";
|
||||
} else {
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
output += Template.Popup.line(line);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/* Update the conflict messages */
|
||||
function updateConflicts() {
|
||||
chrome.runtime.sendMessage(
|
||||
{
|
||||
command: "checkConflicts",
|
||||
},
|
||||
function (response) {
|
||||
console.log("updateConflicts -> response", response);
|
||||
if (response.isConflict) {
|
||||
var between = response.between;
|
||||
let conflict_message = "";
|
||||
for (var i = 0; i < between.length; i++) {
|
||||
let courseA = between[i][0];
|
||||
let courseB = between[i][1];
|
||||
conflict_message += `CONFLICT: ${formatShortenedCourseName(courseA)} and ${formatShortenedCourseName(courseB)}`;
|
||||
if (i != between.length - 1) conflict_message += "<br>";
|
||||
}
|
||||
$(Template.Popup.conflict_message(conflict_message)).prependTo("#courseList").hide().fadeIn(200);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/* prettify the name for the conflict messages*/
|
||||
function formatShortenedCourseName(course) {
|
||||
let { number, department } = separateCourseNameParts(course.coursename);
|
||||
return `${department} ${number} (${course.unique})`;
|
||||
}
|
||||
|
||||
$(document).click(function (event) {
|
||||
$target = $(event.target);
|
||||
|
||||
// If we're not clicking on search button or search popup, and popup is visible, hide it
|
||||
if (!$target.closest("#search").length && !$target.closest("#search-popup").length && $("#search-popup").is(":visible")) {
|
||||
hideSearchPopup();
|
||||
}
|
||||
|
||||
// If we're not clicking on import/export button or imp/exp popup, and popup is visible, hide it
|
||||
if (!$target.closest("#impexp").length && !$target.closest("#import-export-popup").length && $("#import-export-popup").is(":visible")) {
|
||||
hideImportExportPopup();
|
||||
}
|
||||
});
|
||||
|
||||
$("#clear").click(function () {
|
||||
chrome.storage.sync.set({
|
||||
savedCourses: [],
|
||||
});
|
||||
$("#courseList").empty();
|
||||
updateAllTabsCourseList();
|
||||
showEmpty();
|
||||
});
|
||||
|
||||
$("#RIS").click(function () {
|
||||
chrome.tabs.create({
|
||||
url: "https://utdirect.utexas.edu/registrar/ris.WBX",
|
||||
});
|
||||
});
|
||||
|
||||
$("#calendar").click(function () {
|
||||
chrome.tabs.create({
|
||||
url: "calendar.html",
|
||||
});
|
||||
});
|
||||
|
||||
$("#impexp").click(function () {
|
||||
if ($("#impexp>i").text() == "close") {
|
||||
hideImportExportPopup();
|
||||
} else {
|
||||
if ($("#search>i").text() == "close") {
|
||||
hideSearchPopup();
|
||||
}
|
||||
showImportExportPopup();
|
||||
}
|
||||
});
|
||||
|
||||
$("#search").click(function () {
|
||||
if ($("#search>i").text() == "close") {
|
||||
hideSearchPopup();
|
||||
} else {
|
||||
if ($("#impexp>i").text() == "close") {
|
||||
hideImportExportPopup();
|
||||
}
|
||||
showSearchPopup();
|
||||
}
|
||||
});
|
||||
|
||||
$("#import-class").click(function () {
|
||||
$("#import_input").click();
|
||||
console.log("back to improting");
|
||||
});
|
||||
|
||||
function isImportedValid(imported_courses) {
|
||||
return imported_courses && imported_courses.length && (imported_courses.length == 0 || validateCourses(imported_courses));
|
||||
}
|
||||
|
||||
$("#import_input").change(function (e) {
|
||||
console.log("hello");
|
||||
var files = e.target.files;
|
||||
var reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
try {
|
||||
var imported_courses = JSON.parse(this.result);
|
||||
if (isImportedValid(imported_courses)) {
|
||||
chrome.storage.sync.set({
|
||||
savedCourses: imported_courses,
|
||||
});
|
||||
updateAllTabsCourseList();
|
||||
setCourseList();
|
||||
hideImportExportPopup();
|
||||
$("#import_input").val("");
|
||||
} else {
|
||||
Alert("There was an error.");
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
reader.readAsText(files[0]);
|
||||
});
|
||||
|
||||
function exportCourses(url) {
|
||||
var exportlink = document.createElement("a");
|
||||
exportlink.setAttribute("href", url);
|
||||
exportlink.setAttribute("download", "my_courses.json");
|
||||
exportlink.click();
|
||||
}
|
||||
|
||||
function createBlob(export_courses) {
|
||||
return new Blob([JSON.stringify(export_courses, null, 4)], {
|
||||
type: "octet/stream",
|
||||
});
|
||||
}
|
||||
|
||||
$("#export-class").click(function () {
|
||||
chrome.storage.sync.get("savedCourses", function (data) {
|
||||
let export_courses = data.savedCourses;
|
||||
if (export_courses.length > 0) {
|
||||
let url = window.URL.createObjectURL(createBlob(export_courses));
|
||||
exportCourses(url);
|
||||
} else {
|
||||
alert("No Saved Courses to Export.");
|
||||
}
|
||||
hideImportExportPopup();
|
||||
});
|
||||
});
|
||||
|
||||
function openSearch(semester, department, level, courseCode) {
|
||||
var link = "";
|
||||
if (courseCode) {
|
||||
link = `https://utdirect.utexas.edu/apps/registrar/course_schedule/${semester}/results/?search_type_main=COURSE&fos_cn=${department}&course_number=${courseCode}`;
|
||||
} else {
|
||||
link = `https://utdirect.utexas.edu/apps/registrar/course_schedule/${semester}/results/?fos_fl=${department}&level=${level}&search_type_main=FIELD`;
|
||||
}
|
||||
chrome.tabs.create({ url: link });
|
||||
}
|
||||
|
||||
$("#search-class").click(() => {
|
||||
let semester = $("#semesters").find(":selected").val();
|
||||
let department = $("#department").find(":selected").val();
|
||||
let level = $("#level").find(":selected").val();
|
||||
let courseCode = $("#courseCode").val();
|
||||
openSearch(semester, department, level, courseCode);
|
||||
});
|
||||
|
||||
$("#options_button").click(function () {
|
||||
chrome.tabs.create({
|
||||
url: "options.html",
|
||||
});
|
||||
});
|
||||
|
||||
$("#courseList")
|
||||
.on("mouseover", ".copy_button", function () {
|
||||
$(this).addClass("shadow");
|
||||
})
|
||||
.on("mouseleave", ".copy_button", function () {
|
||||
$(this).removeClass("shadow");
|
||||
});
|
||||
|
||||
$("#courseList").on("click", ".copy_button", function (e) {
|
||||
e.stopPropagation();
|
||||
copyButtonAnimation($(this));
|
||||
let unique = $(this).val();
|
||||
copyUnique(unique);
|
||||
});
|
||||
|
||||
function copyUnique(unique) {
|
||||
var temp = $("<input>");
|
||||
$("body").append(temp);
|
||||
temp.val(unique).select();
|
||||
document.execCommand("copy");
|
||||
temp.remove();
|
||||
}
|
||||
|
||||
$("#courseList").on("click", "li", function () {
|
||||
let clicked_item = $(this).closest("li");
|
||||
let curr_course = courses[$(clicked_item).attr("id")];
|
||||
handleMoreInfo(clicked_item, curr_course);
|
||||
handleRegister(clicked_item, curr_course);
|
||||
handleRemove(clicked_item, curr_course);
|
||||
toggleTimeDropdown(clicked_item);
|
||||
});
|
||||
|
||||
function handleRegister(clicked_item, curr_course) {
|
||||
let { status, registerlink } = curr_course;
|
||||
let register_button = $(clicked_item).find("#register");
|
||||
let can_not_register = canNotRegister(status, registerlink);
|
||||
let register_text = can_not_register ? "Can't Register" : status.includes("waitlisted") ? "Join Waitlist" : "Register";
|
||||
let register_color = can_not_register ? Colors.closed : status.includes("waitlisted") ? Colors.waitlisted : Colors.open;
|
||||
|
||||
if (!status) {
|
||||
register_text = "No Status";
|
||||
register_color = Colors.no_status;
|
||||
}
|
||||
|
||||
$(register_button).text(register_text).css("background-color", register_color);
|
||||
|
||||
if (!can_not_register) {
|
||||
$(register_button).click(function () {
|
||||
setCurrentTabUrl(registerlink);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleRemove(clicked_item, curr_course) {
|
||||
let list = $(clicked_item).closest("ul");
|
||||
$(clicked_item)
|
||||
.find("#listRemove")
|
||||
.click(function () {
|
||||
if (can_remove) {
|
||||
can_remove = false;
|
||||
$(list)
|
||||
.find("#conflict")
|
||||
.fadeOut(300, function () {
|
||||
$(clicked_item).remove();
|
||||
});
|
||||
subtractHours(curr_course);
|
||||
chrome.runtime.sendMessage(
|
||||
{
|
||||
command: "courseStorage",
|
||||
course: curr_course,
|
||||
action: "remove",
|
||||
},
|
||||
() => {
|
||||
$(clicked_item).fadeOut(200);
|
||||
if ($(list).children(":visible").length === 1) showEmpty();
|
||||
can_remove = true;
|
||||
updateConflicts();
|
||||
updateAllTabsCourseList();
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function subtractHours(curr_course) {
|
||||
let curr_total_hours = parseInt($("#meta-metric").text());
|
||||
let curr_course_number = separateCourseNameParts(curr_course.coursename).number;
|
||||
let class_length = parseInt(curr_course_number.charAt(0));
|
||||
let multi_semester_code = curr_course_number.slice(-1);
|
||||
if (["A", "B"].includes(multi_semester_code)) {
|
||||
$("#meta-metric").text(curr_total_hours - Math.floor(class_length / 2));
|
||||
} else if (["X", "Y", "Z"].includes(multi_semester_code)) {
|
||||
$("#meta-metric").text(curr_total_hours - Math.floor(class_length / 3));
|
||||
} else {
|
||||
$("#meta-metric").text(curr_total_hours - class_length);
|
||||
}
|
||||
}
|
||||
|
||||
function handleMoreInfo(clicked_item, curr_course) {
|
||||
$(clicked_item)
|
||||
.find("#listMoreInfo")
|
||||
.click(function () {
|
||||
openMoreInfoWithOpenModal(curr_course.link);
|
||||
});
|
||||
}
|
||||
|
||||
function handleEmpty() {
|
||||
if (courses.length != 0) {
|
||||
$("#empty").hide();
|
||||
$("#courseList").show();
|
||||
} else {
|
||||
showEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
function copyButtonAnimation(copy_button) {
|
||||
$(copy_button).find("i").text("check");
|
||||
$(copy_button).stop(true, false).removeAttr("style").removeClass("shadow", {
|
||||
duration: 200,
|
||||
});
|
||||
$(copy_button)
|
||||
.find("i")
|
||||
.delay(400)
|
||||
.queue(function (n) {
|
||||
$(this).text("content_copy");
|
||||
$(this).parent().removeClass("shadow");
|
||||
if ($(this).parent().is(":hover")) {
|
||||
$(this).parent().addClass("shadow");
|
||||
}
|
||||
n();
|
||||
});
|
||||
}
|
||||
|
||||
function toggleTimeDropdown(clicked_item) {
|
||||
let more_info_button = $(clicked_item).find("#moreInfo");
|
||||
let arrow = $(clicked_item).find("#arrow");
|
||||
if ($(more_info_button).is(":hidden")) {
|
||||
$(more_info_button).fadeIn(200);
|
||||
$(arrow).css("transform", "rotate(90deg)");
|
||||
} else {
|
||||
$(more_info_button).fadeOut(200);
|
||||
$(arrow).css("transform", "");
|
||||
}
|
||||
}
|
||||
|
||||
function showEmpty() {
|
||||
$("#courseList").hide();
|
||||
$("#empty").fadeIn(200);
|
||||
$("#main").html(Text.emptyText());
|
||||
$("#meta-metric").text("0");
|
||||
}
|
||||
|
||||
function hideSearchPopup() {
|
||||
$("#search>i").text("search");
|
||||
$("#semcon").hide();
|
||||
$("#depcon").hide();
|
||||
$("#semesters").hide();
|
||||
$("#levcon").hide();
|
||||
$("#search-popup").addClass("hide");
|
||||
}
|
||||
|
||||
function showSearchPopup() {
|
||||
$("#search>i").text("close");
|
||||
$("#class_id_input").show();
|
||||
$("#semesters").show();
|
||||
$("#semcon").show();
|
||||
$("#depcon").show();
|
||||
$("#levcon").show();
|
||||
$("#search-popup").removeClass("hide");
|
||||
}
|
||||
|
||||
function hideImportExportPopup() {
|
||||
$("#import-export-popup").addClass("hide");
|
||||
$("#impexp>i").text("import_export");
|
||||
}
|
||||
|
||||
function showImportExportPopup() {
|
||||
$("#impexp>i").text("close");
|
||||
$("#import-export-popup").removeClass("hide");
|
||||
}
|
||||
|
||||
function getSemesters() {
|
||||
chrome.runtime.sendMessage(
|
||||
{
|
||||
command: "currentSemesters",
|
||||
},
|
||||
function (response) {
|
||||
let { semesters } = response;
|
||||
let semester_names = Object.keys(semesters);
|
||||
for (let i = 0; i < semester_names.length; i++) {
|
||||
let name = semester_names[i];
|
||||
$("#semesters").append(`<option value='${semesters[name]}'>${name}</option>`);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getDepartments() {
|
||||
chrome.runtime.sendMessage(
|
||||
{
|
||||
command: "currentDepartments",
|
||||
},
|
||||
function (response) {
|
||||
let { departments } = response;
|
||||
console.log(departments);
|
||||
for (let i = 0; i < departments.length; i++) {
|
||||
let abv = departments[i];
|
||||
$("#department").append(`<option value='${abv}'>${abv}</option>`);
|
||||
}
|
||||
// $("#department").val('C S');
|
||||
}
|
||||
);
|
||||
}
|
||||
242
js/utPlanner.js
Normal file
@@ -0,0 +1,242 @@
|
||||
let semester_code = "";
|
||||
curr_course = {}
|
||||
|
||||
chrome.runtime.sendMessage({
|
||||
command: "currentSemesters"
|
||||
}, function(response){
|
||||
let semester_text = $('.row:contains(Semester)').find('span').text();
|
||||
let key = semester_text.split(' ').reverse().join(' ');
|
||||
semester_code = response.semesters[key];
|
||||
});
|
||||
|
||||
$.initialize("table.section-detail-grid", function () {
|
||||
$(this).find('thead>tr').append('<th> Plus</th')
|
||||
$(this).find('tbody>tr').each(function () {
|
||||
$(this).append(Template.Main.extension_button());
|
||||
})
|
||||
});
|
||||
|
||||
$("body").prepend(Template.UTPlanner.modal());
|
||||
$("body").on('click', '#distButton', function () {
|
||||
var row = $(this).closest('tr');
|
||||
$('.modal-content').stop().animate({ scrollTop: 0 }, 500);
|
||||
$(this).blur();
|
||||
getCourseInfo(row)
|
||||
});
|
||||
|
||||
|
||||
function getCourseInfo(row) {
|
||||
let rowdata = $(row).find('td').slice(3).toArray().map(x => $(x).text().trim());
|
||||
let [uniquenum, department, coursenum, coursename, profname, notes, rawtime] = rowdata
|
||||
let profinit = ""
|
||||
if (profname !== undefined && profname != "Staff") {
|
||||
profinit = profname.split(',')[1].trim();
|
||||
profname = profname.split(',')[0].trim();
|
||||
}
|
||||
let times = rawtime.split('\n').map(x => x.trim());
|
||||
var course_data = {
|
||||
"unique": uniquenum,
|
||||
"department": department,
|
||||
"number": coursenum,
|
||||
"name": coursename,
|
||||
"prof_name": profname,
|
||||
"initial": profinit,
|
||||
"notes": notes,
|
||||
"individual": `https://utdirect.utexas.edu/apps/registrar/course_schedule/${semester_code}/${uniquenum}/`,
|
||||
"times": times,
|
||||
}
|
||||
curr_course = buildCourseLinks(course_data);
|
||||
getDistribution(course_data);
|
||||
var modal = document.getElementById('myModal');
|
||||
window.onclick = function (event) {
|
||||
if (event.target == modal) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildCourseLinks(course_info) {
|
||||
console.log(semester_code);
|
||||
let {
|
||||
department,
|
||||
number,
|
||||
unique,
|
||||
prof_name
|
||||
} = course_info
|
||||
links = {
|
||||
"textbook": `https://www.universitycoop.com/adoption-search-results?sn=${semester_code}__${department}__${number}__${unique}`,
|
||||
"syllabi": `https://utdirect.utexas.edu/apps/student/coursedocs/nlogon/?year=&semester=&department=${department}&course_number=${number}&course_title=&unique=&instructor_first=&instructor_last=${prof_name}&course_type=In+Residence&search=Search`,
|
||||
}
|
||||
course_info["links"] = links;
|
||||
return course_info;
|
||||
}
|
||||
|
||||
function badData(course_data, res) {
|
||||
return typeof res == 'undefined' || course_data["prof_name"] == "Staff";
|
||||
}
|
||||
|
||||
$("#semesters").on('change', function () {
|
||||
var sem = $(this).val();
|
||||
sem = sem == "Aggregate" ? undefined : sem;
|
||||
getDistribution(curr_course, sem);
|
||||
});
|
||||
|
||||
$("#Syllabi").click(function () {
|
||||
setTimeout(function () {
|
||||
window.open(curr_course["links"]["syllabi"]);
|
||||
}, Timing.button_delay);
|
||||
});
|
||||
$("#textbook").click(function () {
|
||||
setTimeout(function () {
|
||||
window.open(curr_course["links"]["textbook"]);
|
||||
}, Timing.button_delay);
|
||||
});
|
||||
|
||||
$("#moreInfo").click(function () {
|
||||
openMoreInfoWithOpenModal(curr_course["individual"]);
|
||||
});
|
||||
|
||||
|
||||
function toggleChartLoading(loading) {
|
||||
if (loading) {
|
||||
$('#chartload').css('display', 'inline-block');
|
||||
$("#chart").hide();
|
||||
} else {
|
||||
$('#chartload').hide();
|
||||
$("#chart").show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function openDialog(course_data, res) {
|
||||
console.log(course_data);
|
||||
$("#title").text(buildTitle(course_data))
|
||||
$("#topbuttons").before(buildTimeTitle(course_data["times"]));
|
||||
$("#profname").text(buildProfTitle(course_data));
|
||||
$("#myModal").fadeIn(Timing.fade_time);
|
||||
buildSemestersDropdown(course_data, res)
|
||||
var data = []
|
||||
if (!badData(course_data, res))
|
||||
data = res.values[0];
|
||||
setChart(data);
|
||||
allowClosing();
|
||||
}
|
||||
|
||||
function buildProfTitle(course_data) {
|
||||
const {
|
||||
initial,
|
||||
prof_name
|
||||
} = course_data;
|
||||
return `with ${initial?initial+". ":""}${prof_name}`;
|
||||
}
|
||||
|
||||
function buildSemestersDropdown(course_data, res) {
|
||||
$("#semesters").empty();
|
||||
if (badData(course_data, res)) {
|
||||
$("#semesters").append("<option>No Data</option>")
|
||||
} else {
|
||||
var semesters = res.values[0][18].split(",");
|
||||
semesters.sort(semesterSort);
|
||||
semesters.reverse().unshift('Aggregate');
|
||||
var sems = [];
|
||||
for (var i = 0; i < semesters.length; i++) {
|
||||
sems.push($(`<option value="${semesters[i]}">${semesters[i]}</option>`));
|
||||
}
|
||||
$("#semesters").append(sems);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*Query the grades database*/
|
||||
function getDistribution(course_data, sem) {
|
||||
toggleChartLoading(true);
|
||||
let query = buildQuery(course_data, sem);
|
||||
chrome.runtime.sendMessage({
|
||||
command: "gradesQuery",
|
||||
query: query
|
||||
}, function (response) {
|
||||
var res = response.data;
|
||||
if (!sem) {
|
||||
openDialog(course_data, res);
|
||||
} else {
|
||||
var data = badData(course_data, res) ? [] : res.values[0];
|
||||
setChart(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function buildTitle(course_data) {
|
||||
return `${course_data["name"]} (${course_data["department"]} ${course_data["number"]})`
|
||||
}
|
||||
|
||||
|
||||
function buildTimeTitle(times) {
|
||||
$("h2.dateTimePlace").remove();
|
||||
var lines = []
|
||||
for (var i = 0; i < times.length; i++) {
|
||||
date = times[i].substring(0, times[i].indexOf(' ')).toUpperCase();
|
||||
time = times[i].substring(times[i].indexOf(' ') + 1, times[i].lastIndexOf('-')).trim();
|
||||
place = times[i].substring(times[i].lastIndexOf('-') + 1).trim();
|
||||
lines.push($(`<h2 class="dateTimePlace">${makeLine(date, time, place)}</th>`));
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
function makeLine(date, time, place) {
|
||||
var arr = separateDays(date)
|
||||
var output = prettifyDaysText(arr)
|
||||
var building = place.substring(0, place.search(/\d/) - 1);
|
||||
building = building == "" ? "Undecided Location" : building;
|
||||
return `${output} at ${time.replace(/\./g, '').replace(/\-/g, ' to ')} in <a style='font-size:medium' target='_blank' href='https://maps.utexas.edu/buildings/UTM/${building}'>${building}</>`;
|
||||
}
|
||||
|
||||
function setChart(data) {
|
||||
//set up the chart
|
||||
toggleChartLoading(false);
|
||||
chart = Highcharts.chart('chart', buildChartConfig(data), function (chart) { // on complete
|
||||
if (data.length == 0) {
|
||||
//if no data, then show the message and hide the series
|
||||
chart.renderer.text('Could not find data for this Instructor teaching this Course.', 100, 120)
|
||||
.css({
|
||||
fontSize: '20px',
|
||||
width: '300px',
|
||||
align: 'center',
|
||||
left: '160px'
|
||||
})
|
||||
.add();
|
||||
$.each(chart.series, function (i, ser) {
|
||||
ser.hide();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function standardizeName(department, number, name){
|
||||
return `${department} ${number} ${name}`
|
||||
|
||||
}
|
||||
|
||||
function allowClosing() {
|
||||
$('.close').click(function () {
|
||||
close();
|
||||
});
|
||||
$('#myModal').click(function (event) {
|
||||
if (event.target.id == 'myModal') {
|
||||
close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function close() {
|
||||
$("#myModal").fadeOut(Timing.fade_time);
|
||||
$("#snackbar").attr("class", "");
|
||||
}
|
||||
|
||||
|
||||
$(document).keydown(function (e) {
|
||||
/*Close Modal when hit escape*/
|
||||
if (e.keyCode == 27) {
|
||||
close();
|
||||
}
|
||||
});
|
||||
386
js/util.js
Normal file
@@ -0,0 +1,386 @@
|
||||
const days = new Map([
|
||||
["M", "Monday"],
|
||||
["T", "Tuesday"],
|
||||
["W", "Wednesday"],
|
||||
["TH", "Thursday"],
|
||||
["F", "Friday"]
|
||||
]);
|
||||
|
||||
function getStatusColor(status, sub = false) {
|
||||
let color = "black";
|
||||
if (status.includes("open")) {
|
||||
color = sub ? Colors.open_light : Colors.open;
|
||||
} else if (status.includes("waitlisted")) {
|
||||
color = sub ? Colors.waitlisted_light : Colors.waitlisted;
|
||||
} else if (status.includes("closed") || status.includes("cancelled")) {
|
||||
color = sub ? Colors.closed_light : Colors.closed;
|
||||
} else {
|
||||
color = sub ? Colors.no_status_light : Colors.no_status;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
function buildQuery(course_data, sem) {
|
||||
let query = !sem ? "select * from agg" : "select * from grades";
|
||||
query += " where dept like '%" + course_data["department"] + "%'";
|
||||
query += " and prof like '%" + course_data["prof_name"].replace(/'/g, "") + "%'";
|
||||
query += " and course_nbr like '%" + course_data["number"] + "%'";
|
||||
if (sem) {
|
||||
query += "and sem like '%" + sem + "%'";
|
||||
}
|
||||
return query + "order by a1+a2+a3+b1+b2+b3+c1+c2+c3+d1+d2+d3+f desc";
|
||||
}
|
||||
|
||||
/*Course object for passing to background*/
|
||||
function Course(coursename, unique, profname, datetimearr, status, link, registerlink) {
|
||||
this.coursename = coursename;
|
||||
this.unique = unique;
|
||||
this.profname = profname;
|
||||
this.datetimearr = datetimearr;
|
||||
this.status = status;
|
||||
this.link = link;
|
||||
this.registerlink = registerlink;
|
||||
}
|
||||
|
||||
function capitalizeString(string) {
|
||||
//if one word, and if multiple words:
|
||||
let output = "";
|
||||
words = string.split(/[. ,\/ -]/);
|
||||
for (let i in words) {
|
||||
word = words[i];
|
||||
capitalizedWord = word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
||||
output += capitalizedWord + " ";
|
||||
}
|
||||
return output.trim();
|
||||
}
|
||||
|
||||
function separateCourseNameParts(name) {
|
||||
let num_index = name.search(/\d/);
|
||||
department = name.substring(0, num_index).trim();
|
||||
number = name.substring(num_index, name.indexOf(" ", num_index)).trim();
|
||||
name = capitalizeString(name.substring(name.indexOf(" ", num_index)).trim());
|
||||
return {
|
||||
name: name,
|
||||
department: department,
|
||||
number: number
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function separateDays(date, simple=false) {
|
||||
let arr = [];
|
||||
for (var i = 0; i < date.length; i++) {
|
||||
let letter = date.charAt(i);
|
||||
let separated_letter = letter;
|
||||
if (letter == "T" && i < date.length - 1 && date.charAt(i + 1) == "H") {
|
||||
arr.push(simple ? "TH" : days.get("TH"));
|
||||
} else {
|
||||
if (letter != "H") {
|
||||
arr.push(simple ? letter : days.get(letter));
|
||||
}
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
/*Convert time to 24hour format*/
|
||||
function convertTime(time) {
|
||||
var converted = time.replace(/\./g, '').split("-");
|
||||
for (var i = 0; i < 2; i++) {
|
||||
converted[i] = moment(converted[i], ["h:mm A"]).format("HH:mm");
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
function prettifyDaysText(arr) {
|
||||
var output = "";
|
||||
if (arr.length > 2) {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
if (i < arr.length - 1)
|
||||
output += arr[i] + ", "
|
||||
if (i == arr.length - 2)
|
||||
output += "and ";
|
||||
if (i == arr.length - 1)
|
||||
output += arr[i];
|
||||
}
|
||||
} else if (arr.length == 2) {
|
||||
output = arr[0] + " and " + arr[1];
|
||||
} else {
|
||||
output = arr[0];
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
function isIndividualCoursePage(){
|
||||
return $("#textbook_button").length != 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function updateAllTabsCourseList() {
|
||||
chrome.tabs.query({}, function (tabs) {
|
||||
for (var i = 0; i < tabs.length; i++) {
|
||||
chrome.tabs.sendMessage(tabs[i].id, {
|
||||
command: "updateCourseList"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function htmlToNode(response) {
|
||||
return $('<div/>').html(response).contents();
|
||||
}
|
||||
|
||||
function setCurrentTabUrl(link) {
|
||||
chrome.tabs.query({
|
||||
currentWindow: true,
|
||||
active: true
|
||||
}, function (tab) {
|
||||
chrome.tabs.update(tab.id, {
|
||||
url: link
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function openMoreInfoWithOpenModal(link){
|
||||
chrome.runtime.sendMessage({ command: "setOpen", url: link });
|
||||
}
|
||||
|
||||
|
||||
function semesterSort(semA, semB) {
|
||||
let semOrder = {
|
||||
"Spring": 0,
|
||||
"Fall": 1,
|
||||
"Summer": 2,
|
||||
"Winter": 3
|
||||
}
|
||||
let aName = semA.split(' ')[0];
|
||||
let aYear = parseInt(semA.split(' ')[1]);
|
||||
let bName = semB.split(' ')[0];
|
||||
let bYear = parseInt(semB.split(' ')[1]);
|
||||
if (aYear < bYear)
|
||||
return -1;
|
||||
if (aYear > bYear)
|
||||
return 1;
|
||||
if (semOrder[aName] < semOrder[bName])
|
||||
return -1;
|
||||
if (semOrder[aName] > semOrder[bName])
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* convert from the dtarr and maek the time lines*/
|
||||
function convertDateTimeArrToLine(datetimearr) {
|
||||
var output = [];
|
||||
var dtmap = makeDateTimeMap(datetimearr);
|
||||
var timearr = Array.from(dtmap.keys());
|
||||
var temporary = Array.from(dtmap.values())
|
||||
var dayarr = []
|
||||
var locarr = []
|
||||
for(x in temporary) {
|
||||
dayarr.push(temporary[x][0])
|
||||
locarr.push(temporary[x][1])
|
||||
}
|
||||
|
||||
for (var i = 0; i < dayarr.length; i++) {
|
||||
//var place = findLocation(dayarr[i], timearr[i], datetimearr);
|
||||
var place = locarr[i]
|
||||
var building = place.substring(0, place.search(/\d/)).trim();
|
||||
building = building ? building : "Undecided Location"
|
||||
var timearrsplit = timearr[i].split(',')
|
||||
output.push({
|
||||
"days": dayarr[i],
|
||||
"start_time": timearrsplit[0],
|
||||
"end_time": timearrsplit[1],
|
||||
"location_link": `https://maps.utexas.edu/buildings/UTM/${building}`,
|
||||
"location_full": place
|
||||
})
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function makeDateTimeMap(datetimearr) {
|
||||
var dtmap = new Map([]);
|
||||
for (var i = 0; i < datetimearr.length; i++) {
|
||||
datetimearr[i][1][0] = moment(datetimearr[i][1][0], ["HH:mm A"]).format("h:mm A");
|
||||
datetimearr[i][1][1] = moment(datetimearr[i][1][1], ["HH:mm A"]).format("h:mm A");
|
||||
}
|
||||
for (var i = 0; i < datetimearr.length; i++) {
|
||||
var instance = datetimearr[i]
|
||||
var day = String(instance[0])
|
||||
var timeslot = String(instance[1])
|
||||
var location = String(instance[2])
|
||||
var key = timeslot + "," + location
|
||||
if (dtmap.has(key) && dtmap.get(key)[1] === location) {
|
||||
dtmap.set(key, [dtmap.get(key)[0] + day, location]);
|
||||
} else {
|
||||
dtmap.set(key, [day, location]);
|
||||
}
|
||||
}
|
||||
return dtmap
|
||||
}
|
||||
//find the location of a class given its days and timearrs.
|
||||
function findLocation(day, timearr, datetimearr) {
|
||||
for (let i = 0; i < datetimearr.length; i++) {
|
||||
var dtl = datetimearr[i];
|
||||
if (day.includes(dtl[0])) {
|
||||
if (JSON.stringify(timearr) == JSON.stringify(reformatDateTime(dtl[1]))) {
|
||||
return dtl[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function validateCourses(courses) {
|
||||
for (var i = 0; i < courses.length; i++) {
|
||||
if (!validateCourseObject(courses[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateCourseObject(course) {
|
||||
var is_valid = true;
|
||||
var props = ["coursename", "datetimearr", "link", "profname", "status", "unique"];
|
||||
for (let j = 0; j < props.length; j++) {
|
||||
is_valid &= course.hasOwnProperty(props[j]);
|
||||
}
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
|
||||
function reformatDateTime(dtl1) {
|
||||
let output = "";
|
||||
for (let i = 0; i < dtl1.length; i++) {
|
||||
output += dtl1[i];
|
||||
if (i != dtl1.length - 1) {
|
||||
output += ",";
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function rgb2hex(rgb) {
|
||||
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
|
||||
|
||||
function hex(x) {
|
||||
return ("0" + parseInt(x).toString(16)).slice(-2);
|
||||
}
|
||||
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
|
||||
}
|
||||
|
||||
function buildChartConfig(data) {
|
||||
return {
|
||||
chart: {
|
||||
type: 'column',
|
||||
backgroundColor: ' #fefefe',
|
||||
spacingLeft: 10
|
||||
},
|
||||
title: {
|
||||
text: null
|
||||
},
|
||||
subtitle: {
|
||||
text: null
|
||||
},
|
||||
legend: {
|
||||
enabled: false
|
||||
},
|
||||
xAxis: {
|
||||
title: {
|
||||
text: 'Grades'
|
||||
},
|
||||
categories: [
|
||||
'A',
|
||||
'A-',
|
||||
'B+',
|
||||
'B',
|
||||
'B-',
|
||||
'C+',
|
||||
'C',
|
||||
'C-',
|
||||
'D+',
|
||||
'D',
|
||||
'D-',
|
||||
'F'
|
||||
],
|
||||
crosshair: true
|
||||
},
|
||||
yAxis: {
|
||||
min: 0,
|
||||
title: {
|
||||
text: 'Students'
|
||||
}
|
||||
},
|
||||
credits: {
|
||||
enabled: false
|
||||
},
|
||||
lang: {
|
||||
noData: "The professor hasn't taught this class :("
|
||||
},
|
||||
tooltip: {
|
||||
headerFormat: '<span style="font-size:small; font-weight:bold">{point.key}</span><table>',
|
||||
pointFormat: '<td style="color:{black};padding:0;font-size:small; font-weight:bold;"><b>{point.y:.0f} Students</b></td>',
|
||||
footerFormat: '</table>',
|
||||
shared: true,
|
||||
useHTML: true
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
pointPadding: 0.2,
|
||||
borderWidth: 0
|
||||
},
|
||||
series: {
|
||||
animation: {
|
||||
duration: 700
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: 'Grades',
|
||||
data: [{
|
||||
y: data[6],
|
||||
color: '#4CAF50'
|
||||
}, {
|
||||
y: data[7],
|
||||
color: '#8BC34A'
|
||||
}, {
|
||||
y: data[8],
|
||||
color: '#CDDC39'
|
||||
}, {
|
||||
y: data[9],
|
||||
color: '#FFEB3B'
|
||||
}, {
|
||||
y: data[10],
|
||||
color: '#FFC107'
|
||||
}, {
|
||||
y: data[11],
|
||||
color: '#FFA000'
|
||||
}, {
|
||||
y: data[12],
|
||||
color: '#F57C00'
|
||||
}, {
|
||||
y: data[13],
|
||||
color: '#FF5722'
|
||||
}, {
|
||||
y: data[14],
|
||||
color: '#FF5252'
|
||||
}, {
|
||||
y: data[15],
|
||||
color: '#E64A19'
|
||||
}, {
|
||||
y: data[16],
|
||||
color: '#F44336'
|
||||
}, {
|
||||
y: data[17],
|
||||
color: '#D32F2F'
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
function canNotRegister(status, register_link) {
|
||||
return status.includes("closed") || status.includes("cancelled") || !status || !register_link
|
||||
}
|
||||
70
manifest.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "UT Registration Plus",
|
||||
"version": "1.2.2.7",
|
||||
"options_page": "options.html",
|
||||
"description": "Improves the course registration process at the University of Texas at Austin!",
|
||||
"permissions": [
|
||||
"storage",
|
||||
"*://*.utdirect.utexas.edu/apps/registrar/course_schedule/*",
|
||||
"*://*.utexas.collegescheduler.com/*",
|
||||
"*://*.catalog.utexas.edu/ribbit/",
|
||||
"*://*.registrar.utexas.edu/schedules/*",
|
||||
"*://*.login.utexas.edu/login/*"
|
||||
],
|
||||
"content_scripts": [
|
||||
{
|
||||
"css": ["css/styles.css"],
|
||||
"js": [
|
||||
"js/config.js",
|
||||
"js/lib/moment.min.js",
|
||||
"js/lib/highcharts.js",
|
||||
"js/lib/jquery-3.3.1.min.js",
|
||||
"js/lib/jquery.initialize.min.js",
|
||||
"js/util.js",
|
||||
"js/Template.js",
|
||||
"js/courseCatalog.js"
|
||||
],
|
||||
"matches": ["https://utdirect.utexas.edu/apps/registrar/course_schedule/*"]
|
||||
},
|
||||
{
|
||||
"css": ["css/styles.css"],
|
||||
"js": [
|
||||
"js/config.js",
|
||||
"js/lib/moment.min.js",
|
||||
"js/lib/highcharts.js",
|
||||
"js/lib/jquery-3.3.1.min.js",
|
||||
"js/lib/jquery.initialize.min.js",
|
||||
"js/util.js",
|
||||
"js/Template.js",
|
||||
"js/utPlanner.js"
|
||||
],
|
||||
"matches": ["https://utexas.collegescheduler.com/*"]
|
||||
},
|
||||
{
|
||||
"css": ["css/styles.css"],
|
||||
"js": ["js/config.js", "js/lib/moment.min.js", "js/lib/highcharts.js", "js/lib/jquery-3.3.1.min.js", "js/Template.js", "js/util.js", "js/import.js"],
|
||||
"matches": ["https://utdirect.utexas.edu/registrar/waitlist/wl_see_my_waitlists.WBX", "https://utdirect.utexas.edu/registration/classlist.WBX*"]
|
||||
}
|
||||
],
|
||||
"web_accessible_resources": ["grades.db", "images/disticon.png"],
|
||||
"background": {
|
||||
"scripts": ["js/lib/jquery-3.3.1.min.js", "js/lib/sql-memory-growth.js", "js/lib/moment.min.js", "js/config.js", "js/util.js", "js/background.js"],
|
||||
"persistent": true
|
||||
},
|
||||
"browser_action": {
|
||||
"default_popup": "popup.html",
|
||||
"default_icon": {
|
||||
"16": "icons/icon16.png",
|
||||
"32": "icons/icon32.png",
|
||||
"48": "icons/icon48.png",
|
||||
"128": "icons/icon128.png"
|
||||
}
|
||||
},
|
||||
"icons": {
|
||||
"16": "icons/icon16.png",
|
||||
"32": "icons/icon32.png",
|
||||
"48": "icons/icon48.png",
|
||||
"128": "icons/icon128.png"
|
||||
}
|
||||
}
|
||||
30
options.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!-- This file is serving as the template for the options page -->
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
<link rel="stylesheet" href="css/options.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="version-container">
|
||||
<p class="version">(v<span id="version"></span>)</p>
|
||||
</div>
|
||||
<div class="card options-card" id="header">
|
||||
<h2 class="options-header"><u>Options</u></h2>
|
||||
<div id="options_container"></div>
|
||||
<p class="creator-tag"><a href="https://sghsri.github.io">Sriram Hariharan</a> (2018)</p>
|
||||
</div>
|
||||
<div class="card options-card" id="contributors_container">
|
||||
<h3 class="contributor-title">Amazing people who've contributed to the extension!</h3>
|
||||
<p class="creator-tag open-source-tag">Code is open source here <a href="https://github.com/sghsri/UT-Registration-Plus">here</a> :)</p>
|
||||
<div id="contributor-list"></div>
|
||||
</div>
|
||||
<script src="js/config.js"></script>
|
||||
<script src="js/lib/jquery-3.3.1.min.js"></script>
|
||||
<script src="js/util.js"></script>
|
||||
<script src="js/Template.js"></script>
|
||||
<script src="js/options.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
123
package.json
@@ -1,123 +0,0 @@
|
||||
{
|
||||
"name": "ut-registration-plus",
|
||||
"displayName": "UT Registration Plus",
|
||||
"version": "2.0.0-beta4",
|
||||
"description": "UT Registration Plus is a Chrome extension that allows students to easily register for classes.",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/Longhorn-Developers/UT-Registration-Plus",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"prettier": "prettier src --check",
|
||||
"prettier:fix": "prettier src --write",
|
||||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives",
|
||||
"lint:fix": "eslint src --ext ts,tsx --report-unused-disable-directives --fix",
|
||||
"check-types": "tsc --noEmit",
|
||||
"test": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"coverage": "vitest run --coverage",
|
||||
"preview": "vite preview",
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^2.0.3",
|
||||
"@hello-pangea/dnd": "^16.5.0",
|
||||
"@unocss/vite": "^0.58.6",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"chrome-extension-toolkit": "^0.0.54",
|
||||
"clsx": "^2.1.0",
|
||||
"highcharts": "^11.3.0",
|
||||
"highcharts-react-official": "^3.2.1",
|
||||
"html-to-image": "^1.11.11",
|
||||
"husky": "^9.0.11",
|
||||
"nanoid": "^5.0.6",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"sass": "^1.71.1",
|
||||
"sql.js": "1.10.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^1.4.0",
|
||||
"@commitlint/cli": "^18.6.1",
|
||||
"@commitlint/config-conventional": "^18.6.2",
|
||||
"@commitlint/types": "^19.0.3",
|
||||
"@crxjs/vite-plugin": "2.0.0-beta.21",
|
||||
"@iconify-json/bi": "^1.1.23",
|
||||
"@iconify-json/material-symbols": "^1.1.73",
|
||||
"@iconify-json/ri": "^1.1.20",
|
||||
"@storybook/addon-designs": "^8.0.1",
|
||||
"@storybook/addon-essentials": "^8.1.1",
|
||||
"@storybook/addon-links": "^8.1.1",
|
||||
"@storybook/blocks": "^8.1.1",
|
||||
"@storybook/react": "^8.1.1",
|
||||
"@storybook/react-vite": "^8.1.1",
|
||||
"@storybook/test": "^8.1.1",
|
||||
"@svgr/core": "^8.1.0",
|
||||
"@svgr/plugin-jsx": "^8.1.0",
|
||||
"@types/chrome": "^0.0.268",
|
||||
"@types/node": "^20.12.12",
|
||||
"@types/prompts": "^2.4.9",
|
||||
"@types/react": "^18.3.2",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/sql.js": "^1.4.9",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@unocss/eslint-config": "^0.58.6",
|
||||
"@unocss/postcss": "^0.58.6",
|
||||
"@unocss/preset-uno": "^0.58.6",
|
||||
"@unocss/preset-web-fonts": "^0.58.6",
|
||||
"@unocss/reset": "^0.58.6",
|
||||
"@unocss/transformer-directives": "^0.58.6",
|
||||
"@unocss/transformer-variant-group": "^0.58.6",
|
||||
"@vitejs/plugin-react-swc": "^3.6.0",
|
||||
"@vitest/coverage-v8": "^1.3.1",
|
||||
"@vitest/ui": "^1.3.1",
|
||||
"chromatic": "^11.3.5",
|
||||
"cssnano": "^6.0.5",
|
||||
"cssnano-preset-advanced": "^6.0.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"es-module-lexer": "^1.4.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-import-essentials": "^0.2.1",
|
||||
"eslint-plugin-jsdoc": "^48.2.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-prefer-function-component": "^3.3.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.5",
|
||||
"eslint-plugin-simple-import-sort": "^12.0.0",
|
||||
"eslint-plugin-storybook": "^0.8.0",
|
||||
"path": "^0.12.7",
|
||||
"postcss": "^8.4.35",
|
||||
"prettier": "^3.2.5",
|
||||
"react-dev-utils": "^12.0.1",
|
||||
"storybook": "^8.1.1",
|
||||
"typescript": "^5.4.3",
|
||||
"unocss": "^0.58.6",
|
||||
"unocss-preset-primitives": "0.0.2-beta.0",
|
||||
"unplugin-icons": "^0.18.5",
|
||||
"vite": "^5.1.4",
|
||||
"vite-plugin-inspect": "^0.8.3",
|
||||
"vitest": "^1.3.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"@crxjs/vite-plugin@2.0.0-beta.21": "patches/@crxjs__vite-plugin@2.0.0-beta.21.patch",
|
||||
"@unocss/vite@0.58.6": "patches/@unocss__vite@0.58.6.patch"
|
||||
},
|
||||
"overrides": {
|
||||
"es-module-lexer": "^1.4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
diff --git a/dist/index.mjs b/dist/index.mjs
|
||||
index 5c3f6291168987c56b816428080e6f1fe9de7107..abaf6290fe9454ae036a81eacbe7dc3be2fdfbc3 100644
|
||||
--- a/dist/index.mjs
|
||||
+++ b/dist/index.mjs
|
||||
@@ -499,16 +499,43 @@ ${sourceMap}
|
||||
}),
|
||||
mergeMap(async ({ target, code, deps }) => {
|
||||
await lexer.init;
|
||||
- const [imports] = lexer.parse(code, fileName);
|
||||
+ const [imports, exports] = lexer.parse(code, fileName);
|
||||
const depSet = new Set(deps);
|
||||
const magic = new MagicString(code);
|
||||
- for (const i of imports)
|
||||
+ for (const i of imports) {
|
||||
if (i.n) {
|
||||
depSet.add(i.n);
|
||||
const fileName2 = getFileName({ type: "module", id: i.n });
|
||||
const fullImport = code.substring(i.s, i.e);
|
||||
- magic.overwrite(i.s, i.e, fullImport.replace(i.n, `/${fileName2}`));
|
||||
+ const hmrTimestamp = fullImport.match(/\bt=\d{13}&?\b/);
|
||||
+ magic.overwrite(
|
||||
+ i.s,
|
||||
+ i.e,
|
||||
+ fullImport.replace(
|
||||
+ i.n,
|
||||
+ `/${fileName2}${hmrTimestamp ? `?${hmrTimestamp[0]}` : ""}`
|
||||
+ )
|
||||
+ );
|
||||
+ }
|
||||
+ }
|
||||
+ for (const e of exports) {
|
||||
+ if (e.n === "default") {
|
||||
+ const regex = /\s+['"](.*)['"]/y;
|
||||
+ regex.lastIndex = e.e;
|
||||
+ const fullExport = regex.exec(code)?.[1];
|
||||
+ if (!fullExport)
|
||||
+ continue;
|
||||
+ const start = regex.lastIndex - fullExport.length - 1;
|
||||
+ const end = regex.lastIndex - 1;
|
||||
+ if (fullExport.startsWith("/node_modules")) {
|
||||
+ magic.overwrite(
|
||||
+ start,
|
||||
+ end,
|
||||
+ `http://localhost:5173${fullExport}`
|
||||
+ );
|
||||
+ }
|
||||
}
|
||||
+ }
|
||||
return { target, source: magic.toString(), deps: [...depSet] };
|
||||
})
|
||||
);
|
||||
@@ -1229,10 +1256,14 @@ const pluginHMR = () => {
|
||||
handleHotUpdate({ modules, server }) {
|
||||
const { root } = server.config;
|
||||
const relFiles = /* @__PURE__ */ new Set();
|
||||
- for (const m of modules)
|
||||
+ function getRelFile(file) {
|
||||
+ return file.startsWith(root) ? file.slice(server.config.root.length) : file;
|
||||
+ }
|
||||
+ for (const m of modules) {
|
||||
if (m.id?.startsWith(root)) {
|
||||
relFiles.add(m.id.slice(server.config.root.length));
|
||||
}
|
||||
+ }
|
||||
if (inputManifestFiles.background.length) {
|
||||
const background = prefix$1("/", inputManifestFiles.background[0]);
|
||||
if (relFiles.has(background) || modules.some(isImporter(join(server.config.root, background)))) {
|
||||
@@ -1244,7 +1275,14 @@ const pluginHMR = () => {
|
||||
for (const [key, script] of contentScripts)
|
||||
if (key === script.id) {
|
||||
if (relFiles.has(script.id) || modules.some(isImporter(join(server.config.root, script.id)))) {
|
||||
- relFiles.forEach((relFile) => update(relFile));
|
||||
+ modules.filter((mod) => mod.id?.startsWith(root)).forEach((mod) => {
|
||||
+ update(getRelFile(mod.id));
|
||||
+ if (mod.file?.endsWith(".scss")) {
|
||||
+ mod.importers.forEach((imp) => {
|
||||
+ update(getRelFile(imp.id));
|
||||
+ });
|
||||
+ }
|
||||
+ });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1882,7 +1920,7 @@ const pluginWebAccessibleResources = () => {
|
||||
if (contentScripts.size > 0) {
|
||||
const viteManifest = parseJsonAsset(
|
||||
bundle,
|
||||
- "manifest.json"
|
||||
+ ".vite/manifest.json"
|
||||
);
|
||||
const viteFiles = /* @__PURE__ */ new Map();
|
||||
for (const [, file] of Object.entries(viteManifest))
|
||||
diff --git a/package.json b/package.json
|
||||
index e0c47ae66ff399ad3a78abf38d8d93d1f038c55d..f84eb09ffbb5c41094935dd06e04ffe831e2d05a 100644
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -70,7 +70,7 @@
|
||||
"connect-injector": "^0.4.4",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"debug": "^4.3.3",
|
||||
- "es-module-lexer": "^0.10.0",
|
||||
+ "es-module-lexer": "^1.4.1",
|
||||
"fast-glob": "^3.2.11",
|
||||
"fs-extra": "^10.0.1",
|
||||
"jsesc": "^3.0.2",
|
||||
@@ -1,108 +0,0 @@
|
||||
diff --git a/dist/index.cjs b/dist/index.cjs
|
||||
index 560f423a07f21b0c47abd494d77654de4c874481..35ae1fdca8bd5546f7e40a23edacb1dbbbd34b58 100644
|
||||
--- a/dist/index.cjs
|
||||
+++ b/dist/index.cjs
|
||||
@@ -35,15 +35,15 @@ const VIRTUAL_ENTRY_ALIAS = [
|
||||
/^(?:virtual:)?uno(?::(.+))?\.css(\?.*)?$/
|
||||
];
|
||||
const LAYER_MARK_ALL = "__ALL__";
|
||||
-const RESOLVED_ID_WITH_QUERY_RE = /[\/\\]__uno(?:(_.*?))?\.css(\?.*)?$/;
|
||||
-const RESOLVED_ID_RE = /[\/\\]__uno(?:_(.*?))?\.css$/;
|
||||
+const RESOLVED_ID_WITH_QUERY_RE = /[\/\\]uno(?:(_.*?))?\.css(\?.*)?$/;
|
||||
+const RESOLVED_ID_RE = /[\/\\]uno(?:_(.*?))?\.css$/;
|
||||
function resolveId(id) {
|
||||
if (id.match(RESOLVED_ID_WITH_QUERY_RE))
|
||||
return id;
|
||||
for (const alias of VIRTUAL_ENTRY_ALIAS) {
|
||||
const match = id.match(alias);
|
||||
if (match) {
|
||||
- return match[1] ? `/__uno_${match[1]}.css` : "/__uno.css";
|
||||
+ return match[1] ? `/uno_${match[1]}.css` : "/uno.css";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -745,7 +745,7 @@ function GlobalModeDevPlugin({ uno, tokens, tasks, flushTasks, affectedModules,
|
||||
const { hash, css } = await generateCSS(layer);
|
||||
return {
|
||||
// add hash to the chunk of CSS that it will send back to client to check if there is new CSS generated
|
||||
- code: `__uno_hash_${hash}{--:'';}${css}`,
|
||||
+ code: `uno_hash_${hash}{--:'';}${css}`,
|
||||
map: { mappings: "" }
|
||||
};
|
||||
},
|
||||
@@ -764,7 +764,7 @@ function GlobalModeDevPlugin({ uno, tokens, tasks, flushTasks, affectedModules,
|
||||
if (layer && code.includes("import.meta.hot")) {
|
||||
let hmr = `
|
||||
try {
|
||||
- let hash = __vite__css.match(/__uno_hash_(\\w{${HASH_LENGTH}})/)
|
||||
+ let hash = __vite__css.match(/uno_hash_(\\w{${HASH_LENGTH}})/)
|
||||
hash = hash && hash[1]
|
||||
if (!hash)
|
||||
console.warn('[unocss-hmr]', 'failed to get unocss hash, hmr might not work')
|
||||
diff --git a/dist/index.mjs b/dist/index.mjs
|
||||
index bbbccb7cad7421cbdb97223a451ec5853c0476cb..4bf6a08d94e562090a530308c0ab8337afdf8243 100644
|
||||
--- a/dist/index.mjs
|
||||
+++ b/dist/index.mjs
|
||||
@@ -1,17 +1,17 @@
|
||||
-import process$1 from 'node:process';
|
||||
+import remapping from '@ampproject/remapping';
|
||||
+import { createFilter } from '@rollup/pluginutils';
|
||||
+import { loadConfig } from '@unocss/config';
|
||||
+import { BetterMap, createGenerator, cssIdRE, notNull, toEscapedSelector } from '@unocss/core';
|
||||
import UnocssInspector from '@unocss/inspector';
|
||||
-import { resolve, isAbsolute, dirname } from 'node:path';
|
||||
-import fs from 'node:fs/promises';
|
||||
import fg from 'fast-glob';
|
||||
import MagicString from 'magic-string';
|
||||
-import remapping from '@ampproject/remapping';
|
||||
-import { createHash } from 'node:crypto';
|
||||
-import { cssIdRE, createGenerator, BetterMap, notNull, toEscapedSelector } from '@unocss/core';
|
||||
import { Buffer } from 'node:buffer';
|
||||
-import { createFilter } from '@rollup/pluginutils';
|
||||
+import { createHash } from 'node:crypto';
|
||||
import fs$1 from 'node:fs';
|
||||
+import fs from 'node:fs/promises';
|
||||
+import { dirname, isAbsolute, resolve } from 'node:path';
|
||||
+import process$1 from 'node:process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
-import { loadConfig } from '@unocss/config';
|
||||
|
||||
const defaultPipelineExclude = [cssIdRE];
|
||||
const defaultPipelineInclude = [/\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html)($|\?)/];
|
||||
@@ -20,15 +20,15 @@ const VIRTUAL_ENTRY_ALIAS = [
|
||||
/^(?:virtual:)?uno(?::(.+))?\.css(\?.*)?$/
|
||||
];
|
||||
const LAYER_MARK_ALL = "__ALL__";
|
||||
-const RESOLVED_ID_WITH_QUERY_RE = /[\/\\]__uno(?:(_.*?))?\.css(\?.*)?$/;
|
||||
-const RESOLVED_ID_RE = /[\/\\]__uno(?:_(.*?))?\.css$/;
|
||||
+const RESOLVED_ID_WITH_QUERY_RE = /[\/\\]uno(?:(_.*?))?\.css(\?.*)?$/;
|
||||
+const RESOLVED_ID_RE = /[\/\\]uno(?:_(.*?))?\.css$/;
|
||||
function resolveId(id) {
|
||||
if (id.match(RESOLVED_ID_WITH_QUERY_RE))
|
||||
return id;
|
||||
for (const alias of VIRTUAL_ENTRY_ALIAS) {
|
||||
const match = id.match(alias);
|
||||
if (match) {
|
||||
- return match[1] ? `/__uno_${match[1]}.css` : "/__uno.css";
|
||||
+ return match[1] ? `/uno_${match[1]}.css` : "/uno.css";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -730,7 +730,7 @@ function GlobalModeDevPlugin({ uno, tokens, tasks, flushTasks, affectedModules,
|
||||
const { hash, css } = await generateCSS(layer);
|
||||
return {
|
||||
// add hash to the chunk of CSS that it will send back to client to check if there is new CSS generated
|
||||
- code: `__uno_hash_${hash}{--:'';}${css}`,
|
||||
+ code: `uno_hash_${hash}{--:'';}${css}`,
|
||||
map: { mappings: "" }
|
||||
};
|
||||
},
|
||||
@@ -749,7 +749,7 @@ function GlobalModeDevPlugin({ uno, tokens, tasks, flushTasks, affectedModules,
|
||||
if (layer && code.includes("import.meta.hot")) {
|
||||
let hmr = `
|
||||
try {
|
||||
- let hash = __vite__css.match(/__uno_hash_(\\w{${HASH_LENGTH}})/)
|
||||
+ let hash = __vite__css.match(/uno_hash_(\\w{${HASH_LENGTH}})/)
|
||||
hash = hash && hash[1]
|
||||
if (!hash)
|
||||
console.warn('[unocss-hmr]', 'failed to get unocss hash, hmr might not work')
|
||||
14024
pnpm-lock.yaml
generated
90
popup.html
Normal file
@@ -0,0 +1,90 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!-- This file is serving as the page for the browser action popup -->
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="css/popup.css">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="card" id="card-header" class='header_container'>
|
||||
<div id="buttons" class="header_buttons">
|
||||
<button id="clear" class="material_button header_button clear_button">Clear All</button>
|
||||
<button id="RIS" class="material_button header_button ris_button">Registrar Info </button>
|
||||
<button id="calendar" class="material_button header_button schedule_button">My Schedule</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<ul id="courseList" class='course_list'></ul>
|
||||
<h2 id="empty" class='empty_message'>
|
||||
<div id="main">Doesn't Look Like Anything To Me.</div>
|
||||
<span>(No Courses Saved)</span>
|
||||
</h2>
|
||||
<div class="settings_divider"></div>
|
||||
<input type="file" id="import_input" accept=".json" class="hide" />
|
||||
<div>
|
||||
<div id="meta-data" class="meta-container">
|
||||
<p class="meta"> <span class="meta-metric" id="meta-metric">17</span> hr</p>
|
||||
</div>
|
||||
<div class="settings">
|
||||
<button title='Search' class="settings_button search_button" id='search'>
|
||||
<i class="material-icons settings_icon">search</i>
|
||||
</button>
|
||||
<div id="search-popup" class="hide">
|
||||
<div class="flex-container">
|
||||
<div id='semcon' class="select-style item">
|
||||
<label>
|
||||
<select id="semesters"></select>
|
||||
</label>
|
||||
</div>
|
||||
<div id='depcon' class="select-style item">
|
||||
<label>
|
||||
<select id="department"></select>
|
||||
</label>
|
||||
</div>
|
||||
<div id='levcon' class="select-style item">
|
||||
<label>
|
||||
<select id="level">
|
||||
<option value="L">Lower</option>
|
||||
<option value="U">Upper</option>
|
||||
<option value="G">Grad</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input class = "input-box" placeholder="Course # (optional)" type="text" id="courseCode"></input>
|
||||
</div>
|
||||
</div>
|
||||
<button id="search-class" class="material_button search-button">Search</button>
|
||||
</div>
|
||||
<button title='Import/Export' class="settings_button import_button" id='impexp'>
|
||||
<i class="material-icons settings_icon">import_export</i>
|
||||
</button>
|
||||
<div id="import-export-popup" class="hide">
|
||||
<div class="flex-container">
|
||||
<button id="import-class" class="simple-menu-option">
|
||||
<i class="material-icons">file_upload</i>Import Classes
|
||||
</button>
|
||||
<button id="export-class" class="simple-menu-option">
|
||||
<i class="material-icons">file_download</i>Export Classes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button title='Options' class="settings_button options_button" id='options_button'>
|
||||
<i class="material-icons settings_icon">settings</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/lib/jquery-3.3.1.min.js"></script>
|
||||
<script src="js/lib/moment.min.js"></script>
|
||||
<script src="js/Template.js"></script>
|
||||
<script src="js/config.js"></script>
|
||||
<script src="js/util.js"></script>
|
||||
<script src="js/popup.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,10 +0,0 @@
|
||||
/* eslint-disable global-require */
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
cssnano: process.env.NODE_ENV !== 'development' ? {} : false,
|
||||
// '@unocss/postcss': {},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Beta" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: none;
|
||||
stroke: #fff;
|
||||
stroke-width: 70.06px;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #005f86;
|
||||
}
|
||||
|
||||
.cls-2, .cls-3 {
|
||||
stroke-width: 0px;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-2" width="1024" height="1024"/>
|
||||
<g>
|
||||
<circle class="cls-1" cx="512" cy="512" r="362"/>
|
||||
<rect class="cls-3" x="466.29" y="283.46" width="91.41" height="457.07"/>
|
||||
<rect class="cls-3" x="283.46" y="466.29" width="457.07" height="91.41"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 684 B |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 429 B |
|
Before Width: | Height: | Size: 791 B |
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Development" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: none;
|
||||
stroke: #fff;
|
||||
stroke-width: 70.06px;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #bf2178;
|
||||
}
|
||||
|
||||
.cls-2, .cls-3 {
|
||||
stroke-width: 0px;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-2" x="0" width="1024" height="1024"/>
|
||||
<g>
|
||||
<circle class="cls-1" cx="512" cy="512" r="362"/>
|
||||
<rect class="cls-3" x="466.29" y="283.46" width="91.41" height="457.07"/>
|
||||
<rect class="cls-3" x="283.46" y="466.29" width="457.07" height="91.41"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 697 B |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 449 B |
|
Before Width: | Height: | Size: 729 B |
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Production" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #bf5700;
|
||||
}
|
||||
|
||||
.cls-1, .cls-2 {
|
||||
stroke-width: 0px;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: none;
|
||||
stroke: #fff;
|
||||
stroke-width: 70.06px;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-1" width="1024" height="1024"/>
|
||||
<g>
|
||||
<circle class="cls-3" cx="512" cy="512" r="362"/>
|
||||
<rect class="cls-2" x="466.29" y="283.46" width="91.41" height="457.07"/>
|
||||
<rect class="cls-2" x="283.46" y="466.29" width="457.07" height="91.41"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 690 B |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 421 B |
|
Before Width: | Height: | Size: 706 B |
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,221 +0,0 @@
|
||||
[
|
||||
"ACC",
|
||||
"ADV",
|
||||
"ASE",
|
||||
"AFR",
|
||||
"AFS",
|
||||
"ASL",
|
||||
"AMS",
|
||||
"AHC",
|
||||
"ANT",
|
||||
"ALD",
|
||||
"ARA",
|
||||
"ARE",
|
||||
"ARI",
|
||||
"ARC",
|
||||
"AED",
|
||||
"ARH",
|
||||
"ART",
|
||||
"AET",
|
||||
"AAS",
|
||||
"ANS",
|
||||
"AST",
|
||||
"BSN",
|
||||
"BEN",
|
||||
"BCH",
|
||||
"BIO",
|
||||
"BME",
|
||||
"BDP",
|
||||
"B A",
|
||||
"BAX",
|
||||
"BGS",
|
||||
"CHE",
|
||||
"CH",
|
||||
"CHI",
|
||||
"C E",
|
||||
"CLA",
|
||||
"C C",
|
||||
"CGS",
|
||||
"COM",
|
||||
"CLD",
|
||||
"CMS",
|
||||
"CRP",
|
||||
"C L",
|
||||
"COE",
|
||||
"CSE",
|
||||
"C S",
|
||||
"CON",
|
||||
"CTI",
|
||||
"CRW",
|
||||
"CDI",
|
||||
"EDC",
|
||||
"CZ",
|
||||
"DAN",
|
||||
"DSC",
|
||||
"D S",
|
||||
"DES",
|
||||
"DEV",
|
||||
"D B",
|
||||
"DRS",
|
||||
"DCH",
|
||||
"ECO",
|
||||
"ELP",
|
||||
"EDP",
|
||||
"E E",
|
||||
"ECE",
|
||||
"EER",
|
||||
"EMA",
|
||||
"ENM",
|
||||
"E M",
|
||||
"E S",
|
||||
"E",
|
||||
"ESL",
|
||||
"ENS",
|
||||
"EVE",
|
||||
"EVS",
|
||||
"EUP",
|
||||
"EUS",
|
||||
"FIN",
|
||||
"F A",
|
||||
"FLU",
|
||||
"FR",
|
||||
"F H",
|
||||
"G E",
|
||||
"GRG",
|
||||
"GEO",
|
||||
"GER",
|
||||
"GSD",
|
||||
"GOV",
|
||||
"GRS",
|
||||
"GK",
|
||||
"GUI",
|
||||
"HAR",
|
||||
"H S",
|
||||
"HCT",
|
||||
"HED",
|
||||
"HEB",
|
||||
"HIN",
|
||||
"HIS",
|
||||
"HDF",
|
||||
"HDO",
|
||||
"H E",
|
||||
"HMN",
|
||||
"ILA",
|
||||
"I",
|
||||
"ISP",
|
||||
"INF",
|
||||
"ITD",
|
||||
"I B",
|
||||
"IRG",
|
||||
"ISL",
|
||||
"ITL",
|
||||
"ITC",
|
||||
"JPN",
|
||||
"J S",
|
||||
"J",
|
||||
"KIN",
|
||||
"KOR",
|
||||
"LAR",
|
||||
"LTC",
|
||||
"LAT",
|
||||
"LAL",
|
||||
"LAS",
|
||||
"LAW",
|
||||
"LEB",
|
||||
"L A",
|
||||
"LAH",
|
||||
"LIN",
|
||||
"MAL",
|
||||
"MAN",
|
||||
"MIS",
|
||||
"MFG",
|
||||
"MNS",
|
||||
"MKT",
|
||||
"MSE",
|
||||
"M",
|
||||
"M E",
|
||||
"MDV",
|
||||
"MAS",
|
||||
"MEL",
|
||||
"MES",
|
||||
"M S",
|
||||
"MOL",
|
||||
"MUS",
|
||||
"NSC",
|
||||
"N S",
|
||||
"NEU",
|
||||
"NOR",
|
||||
"N",
|
||||
"NTR",
|
||||
"OBO",
|
||||
"OPR",
|
||||
"O M",
|
||||
"ORI",
|
||||
"ORG",
|
||||
"PER",
|
||||
"PRS",
|
||||
"PGE",
|
||||
"PGS",
|
||||
"PHM",
|
||||
"PHL",
|
||||
"PED",
|
||||
"P S",
|
||||
"PHY",
|
||||
"PIA",
|
||||
"POL",
|
||||
"POR",
|
||||
"PRC",
|
||||
"PSY",
|
||||
"P A",
|
||||
"PBH",
|
||||
"P R",
|
||||
"RIM",
|
||||
"RTF",
|
||||
"R E",
|
||||
"R S",
|
||||
"RHE",
|
||||
"R M",
|
||||
"RUS",
|
||||
"REE",
|
||||
"SAN",
|
||||
"SAX",
|
||||
"STC",
|
||||
"STM",
|
||||
"S C",
|
||||
"SEL",
|
||||
"S S",
|
||||
"S W",
|
||||
"SOC",
|
||||
"SPN",
|
||||
"SPC",
|
||||
"SED",
|
||||
"SLH",
|
||||
"STA",
|
||||
"SDS",
|
||||
"SUS",
|
||||
"SWE",
|
||||
"TAM",
|
||||
"TXA",
|
||||
"T D",
|
||||
"TRO",
|
||||
"TRU",
|
||||
"TBA",
|
||||
"TUR",
|
||||
"T C",
|
||||
"UKR",
|
||||
"UGS",
|
||||
"UDN",
|
||||
"URB",
|
||||
"URD",
|
||||
"UTS",
|
||||
"UTL",
|
||||
"VIA",
|
||||
"VIO",
|
||||
"V C",
|
||||
"VAS",
|
||||
"VOI",
|
||||
"WGS",
|
||||
"WRT",
|
||||
"YID",
|
||||
"YOR"
|
||||
]
|
||||
@@ -1,54 +0,0 @@
|
||||
const splashText: string[] = [
|
||||
"You can't fail classes you're not in, that's for sure.",
|
||||
'Steer clear of O-Chem, unless you fancy a challenge.',
|
||||
'Rec Sports fills up fast, even before the sun reaches its peak.',
|
||||
"Ah, Jendy's! A taste ever so refined.",
|
||||
'Fine dining at Jester City Limits, eh?',
|
||||
'Rec Sports fills up fast, even before the sun reaches its peak.',
|
||||
'RIP Domino, you beloved campus feline.',
|
||||
"The year is 2055 and Welch still isn't finished.",
|
||||
'Motivation dropping faster than ur GPA',
|
||||
'No Work Happens On PCL 5th Floor.',
|
||||
'I may be a sophomore in name, but my credit count screams freshman!',
|
||||
'Pain is temporary, GPA is forever.',
|
||||
"You've Yee'd Your Last Haw.",
|
||||
'lol everything is already waitlisted.',
|
||||
"At Least You're Not At A&M.",
|
||||
'TeXAs iS BaCK GuYZ',
|
||||
'mAke iT yOuR tExAS',
|
||||
"'Academically Challenged'",
|
||||
'Does McCombs teach Parseltongue?',
|
||||
'No Cruce Enfrente Del Bus.',
|
||||
'Omae Wa Mou Shindeiru...',
|
||||
'Bevo Bucks are the new Bitcoin',
|
||||
'Every day another brick disappears from Speedway',
|
||||
'The GDC will annex the EER one day',
|
||||
'Just you wait. Our CNS operatives will topple the EER regime',
|
||||
'To hike to Kins or not to hike to Kins...',
|
||||
'The road to Kinsolving is long, but their delicacies makes it worth every step.',
|
||||
"C'mon you Longhorns! You want to study forever?",
|
||||
'HOW BOUT A NICE CUP OF LIBER TEA',
|
||||
"It's called the quiet floor of the PCL for a reason",
|
||||
"'Whose car is this and why is it attempting to enter Welch?'",
|
||||
"'I really like one of my TAs and I wanna ask her out after this semester ends'",
|
||||
'CaN YoU aSk OuT a tA aFtEr tHe SeMeStEr Is oVeR AnD gRaDeS ArE DoNe?',
|
||||
"The Block of Butter incident of '22",
|
||||
"Arrows of Christ vs Church of Scientology was the crossover we didn't know we needed",
|
||||
'THE WALK SIGN IS ON TO CROSS GUADALUPE AND 21ST',
|
||||
'Days since last GDC door alarm: 0',
|
||||
'Finding a parking spot is like winning the lottery... if the lottery required parallel parking skills.',
|
||||
'The squirrels are more ambitious than most freshmen during finals week.',
|
||||
'The line at the on-campus Starbucks is longer than your course waitlist.',
|
||||
'The weather changes more often than your class schedule.',
|
||||
"'Sorry, the PCL is full' is the most heartbreaking message you'll ever receive.",
|
||||
'Getting to class on time is like navigating a maze of construction zones.',
|
||||
"'studying' often means refreshing Canvas every five minutes to see if the professor posted lecture slides.",
|
||||
"'I'll just skip this lecture' often turns into a semester-long habit.",
|
||||
'The libraries are full of students pretending to study but actually napping with their eyes open.',
|
||||
"The 'campus loop' refers to both the bus route and the endless cycle of trying to find your way around campus.",
|
||||
'The squirrels have mastered the art of begging for food better than most students during finals week.',
|
||||
"'going for a run' often means 'running to get food from food trucks'.",
|
||||
'The struggle to fit all your classes into one schedule is as real as the struggle to fit all your groceries into a mini-fridge.',
|
||||
];
|
||||
|
||||
export default splashText;
|
||||
@@ -1,3 +0,0 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -1,157 +0,0 @@
|
||||
import { DevStore } from '@shared/storage/DevStore';
|
||||
import React, { useEffect } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
const manifest = chrome.runtime.getManifest();
|
||||
|
||||
/**
|
||||
* Handles editing the storage for a specific area.
|
||||
*
|
||||
* @param {string} areaName - The name of the storage area.
|
||||
* @returns {Function} - A function that accepts changes and sets them in the storage.
|
||||
*/
|
||||
const handleEditStorage = (areaName: 'local' | 'sync' | 'session') => (changes: Record<string, unknown>) => {
|
||||
chrome.storage[areaName].set(changes);
|
||||
};
|
||||
|
||||
interface JSONEditorProps {
|
||||
data: unknown;
|
||||
onChange: ReturnType<typeof handleEditStorage>;
|
||||
}
|
||||
|
||||
function JSONEditor(props: JSONEditorProps) {
|
||||
const { data, onChange } = props;
|
||||
const [isEditing, setIsEditing] = React.useState(false);
|
||||
const [json, setJson] = React.useState(JSON.stringify(data, null, 2));
|
||||
|
||||
useEffect(() => {
|
||||
setJson(JSON.stringify(data, null, 2));
|
||||
}, [data]);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setJson(e.target.value);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
try {
|
||||
const updates = JSON.parse(json);
|
||||
onChange(updates);
|
||||
setIsEditing(false);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
// eslint-disable-next-line no-alert
|
||||
alert('Invalid JSON');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isEditing ? (
|
||||
<div>
|
||||
<div style={{ flex: 1, marginBottom: 10, gap: 10, display: 'flex' }}>
|
||||
<button style={{ color: 'green' }} onClick={handleSave}>
|
||||
Save
|
||||
</button>
|
||||
<button style={{ color: 'red' }} onClick={() => setIsEditing(false)}>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
<textarea style={{ width: '100%', height: '300px' }} value={json} onChange={handleChange} />
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<pre onClick={() => setIsEditing(true)}>{json}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// const PrettyPrintJson = React.memo(({ data }: any) => (
|
||||
// <div>
|
||||
// <pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
// </div>
|
||||
// ));
|
||||
|
||||
function DevDashboard() {
|
||||
const [localStorage, setLocalStorage] = React.useState<Record<string, unknown>>({});
|
||||
const [syncStorage, setSyncStorage] = React.useState<Record<string, unknown>>({});
|
||||
const [sessionStorage, setSessionStorage] = React.useState<Record<string, unknown>>({});
|
||||
|
||||
useEffect(() => {
|
||||
const onVisibilityChange = () => {
|
||||
DevStore.set('wasDebugTabVisible', document.visibilityState === 'visible');
|
||||
};
|
||||
document.addEventListener('visibilitychange', onVisibilityChange);
|
||||
return () => {
|
||||
document.removeEventListener('visibilitychange', onVisibilityChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
chrome.storage.local.get(null, result => {
|
||||
setLocalStorage(result);
|
||||
});
|
||||
|
||||
chrome.storage.sync.get(null, result => {
|
||||
setSyncStorage(result);
|
||||
});
|
||||
|
||||
chrome.storage.session.get(null, result => {
|
||||
setSessionStorage(result);
|
||||
});
|
||||
}, []);
|
||||
|
||||
// listen for changes to the chrome storage to update the local storage state displayed in the dashboard
|
||||
useEffect(() => {
|
||||
const onChanged = (changes: chrome.storage.StorageChange, areaName: chrome.storage.AreaName) => {
|
||||
let copy: Record<string, unknown> = {};
|
||||
|
||||
if (areaName === 'local') {
|
||||
copy = { ...localStorage };
|
||||
} else if (areaName === 'sync') {
|
||||
copy = { ...syncStorage };
|
||||
} else if (areaName === 'session') {
|
||||
copy = { ...sessionStorage };
|
||||
}
|
||||
|
||||
Object.keys(changes).forEach((key: string) => {
|
||||
copy[key] = changes[key as keyof typeof changes].newValue;
|
||||
});
|
||||
|
||||
if (areaName === 'local') {
|
||||
setLocalStorage(copy);
|
||||
}
|
||||
if (areaName === 'sync') {
|
||||
setSyncStorage(copy);
|
||||
}
|
||||
if (areaName === 'session') {
|
||||
setSessionStorage(copy);
|
||||
}
|
||||
};
|
||||
|
||||
chrome.storage.onChanged.addListener(onChanged);
|
||||
|
||||
return () => {
|
||||
chrome.storage.onChanged.removeListener(onChanged);
|
||||
};
|
||||
}, [localStorage, syncStorage, sessionStorage]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>
|
||||
{manifest.name} {manifest.version} - {process.env.NODE_ENV}
|
||||
</h1>
|
||||
<p>This tab is used for hot reloading and debugging. We will update this tab further in the future.</p>
|
||||
<h2>Local Storage</h2>
|
||||
<JSONEditor data={localStorage} onChange={handleEditStorage('local')} />
|
||||
<h2>Sync Storage</h2>
|
||||
<JSONEditor data={syncStorage} onChange={handleEditStorage('sync')} />
|
||||
<h2>Session Storage</h2>
|
||||
<JSONEditor data={sessionStorage} onChange={handleEditStorage('session')} />
|
||||
<br />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById('root')!).render(<DevDashboard />);
|
||||
14
src/global.d.ts
vendored
@@ -1,14 +0,0 @@
|
||||
declare module '*.jpg' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.png' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.json' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import { defineManifest } from '@crxjs/vite-plugin';
|
||||
|
||||
import packageJson from '../package.json';
|
||||
|
||||
// Convert from Semver (example: 0.1.0-beta6)
|
||||
const [major, minor, patch, label = '0'] = packageJson.version
|
||||
// can only contain digits, dots, or dash
|
||||
.replace(/[^\d.-]+/g, '')
|
||||
// split into version parts
|
||||
.split(/[.-]/);
|
||||
|
||||
const isBeta = !!process.env.BETA;
|
||||
const mode = isBeta ? 'beta' : process.env.NODE_ENV;
|
||||
|
||||
if (isBeta && process.env.NODE_ENV !== 'production') throw new Error('Cannot have beta non-production build');
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
const nameSuffix = isBeta ? ' (beta)' : mode === 'development' ? ' (dev)' : '';
|
||||
|
||||
const HOST_PERMISSIONS: string[] = [
|
||||
'*://*.utdirect.utexas.edu/apps/registrar/course_schedule/*',
|
||||
'*://*.utexas.collegescheduler.com/*',
|
||||
'*://*.catalog.utexas.edu/ribbit/',
|
||||
'*://*.registrar.utexas.edu/schedules/*',
|
||||
'*://*.login.utexas.edu/login/*',
|
||||
'https://utexas.bluera.com/*',
|
||||
];
|
||||
|
||||
const manifest = defineManifest(async () => ({
|
||||
manifest_version: 3,
|
||||
name: `${packageJson.displayName ?? packageJson.name}${nameSuffix}`,
|
||||
version: `${major}.${minor}.${patch}.${label}`,
|
||||
description: packageJson.description,
|
||||
options_page: 'src/pages/options/index.html',
|
||||
background: { service_worker: 'src/pages/background/background.ts' },
|
||||
permissions: ['storage', 'unlimitedStorage', 'background', 'scripting'],
|
||||
host_permissions: process.env.MODE === 'development' ? [...HOST_PERMISSIONS, '<all_urls>'] : HOST_PERMISSIONS,
|
||||
action: {
|
||||
default_popup: 'src/pages/popup/index.html',
|
||||
default_icon: `icons/icon_${mode}_32.png`,
|
||||
},
|
||||
icons: {
|
||||
'16': `icons/icon_${mode}_16.png`,
|
||||
'32': `icons/icon_${mode}_32.png`,
|
||||
'48': `icons/icon_${mode}_48.png`,
|
||||
'128': `icons/icon_${mode}_128.png`,
|
||||
},
|
||||
content_scripts: [
|
||||
{
|
||||
matches: HOST_PERMISSIONS,
|
||||
js: ['src/pages/content/index.tsx'],
|
||||
},
|
||||
],
|
||||
web_accessible_resources: [
|
||||
{
|
||||
resources: ['assets/js/*.js', 'assets/css/*.css', 'assets/img/*'],
|
||||
matches: ['*://*/*'],
|
||||
},
|
||||
],
|
||||
content_security_policy: {
|
||||
extension_pages: "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'",
|
||||
},
|
||||
}));
|
||||
|
||||
export default manifest;
|
||||
@@ -1,59 +0,0 @@
|
||||
import type { BACKGROUND_MESSAGES } from '@shared/messages';
|
||||
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
||||
import updateBadgeText from '@shared/util/updateBadgeText';
|
||||
import { MessageListener } from 'chrome-extension-toolkit';
|
||||
|
||||
import onInstall from './events/onInstall';
|
||||
import onServiceWorkerAlive from './events/onServiceWorkerAlive';
|
||||
import onUpdate from './events/onUpdate';
|
||||
import browserActionHandler from './handler/browserActionHandler';
|
||||
import calendarBackgroundHandler from './handler/calendarBackgroundHandler';
|
||||
import CESHandler from './handler/CESHandler';
|
||||
import tabManagementHandler from './handler/tabManagementHandler';
|
||||
import userScheduleHandler from './handler/userScheduleHandler';
|
||||
|
||||
onServiceWorkerAlive();
|
||||
|
||||
/**
|
||||
* will be triggered on either install or update
|
||||
* (will also be triggered on a user's sync'd browsers (on other devices)))
|
||||
*/
|
||||
chrome.runtime.onInstalled.addListener(details => {
|
||||
switch (details.reason) {
|
||||
case 'install':
|
||||
onInstall();
|
||||
break;
|
||||
case 'update':
|
||||
onUpdate();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// initialize the message listener that will listen for messages from the content script
|
||||
const messageListener = new MessageListener<BACKGROUND_MESSAGES>({
|
||||
...browserActionHandler,
|
||||
...tabManagementHandler,
|
||||
...userScheduleHandler,
|
||||
...CESHandler,
|
||||
...calendarBackgroundHandler,
|
||||
});
|
||||
|
||||
messageListener.listen();
|
||||
|
||||
UserScheduleStore.listen('schedules', async schedules => {
|
||||
const index = await UserScheduleStore.get('activeIndex');
|
||||
const numCourses = schedules.newValue[index]?.courses?.length;
|
||||
if (!numCourses) return;
|
||||
|
||||
updateBadgeText(numCourses);
|
||||
});
|
||||
|
||||
UserScheduleStore.listen('activeIndex', async ({ newValue }) => {
|
||||
const schedules = await UserScheduleStore.get('schedules');
|
||||
const numCourses = schedules[newValue]?.courses?.length;
|
||||
if (!numCourses) return;
|
||||
|
||||
updateBadgeText(numCourses);
|
||||
});
|
||||