Compare commits
401 Commits
legacy
...
DereC4-der
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f2a29f268 | ||
|
|
d5c3d32170 | ||
|
|
149fda3b72 | ||
| 0ab83efd47 | |||
|
|
9eaff24cbb | ||
|
|
75ad4167b6 | ||
| f34dd95d77 | |||
| 9356357545 | |||
| d424ccce49 | |||
| be87e41814 | |||
| 438c82bfb5 | |||
| 0d63c66356 | |||
| 703a0fa596 | |||
| 7acc91f4ee | |||
| 5d9980cac3 | |||
| 57832b0dd4 | |||
| b9358803dd | |||
| 4397dfb1c5 | |||
| f83e012d62 | |||
| 0e70bbd52f | |||
|
|
bc5d68ce18 | ||
|
|
863d980b2d | ||
|
|
f943e4801b | ||
| 79f2ffa4cc | |||
|
|
78d749a8a9 | ||
| bc354f3798 | |||
| 8959e0d9f7 | |||
|
|
40ef92b230 | ||
|
|
4f0ae43411 | ||
| c330e3123a | |||
|
|
229a8a29e8 | ||
|
|
2f9e9b1297 | ||
|
|
6812d685d0 | ||
|
|
a2303ee35f | ||
|
|
e3c2da36df | ||
|
|
0c7cd3e790 | ||
|
|
7a40008c1e | ||
|
|
07cadb8c62 | ||
|
|
d4611952d4 | ||
|
|
88c0061187 | ||
|
|
3684ee5e9b | ||
|
|
d1b921a5b0 | ||
|
|
bcb5a8c469 | ||
| 38d09e4aae | |||
| 0534f607a5 | |||
| 98a7cc618b | |||
| bae1da43d3 | |||
|
|
a3f5e0f27f | ||
| 74cc6e6da1 | |||
| c68b017462 | |||
|
|
227de53e84 | ||
|
|
d3f64ec79e | ||
| aeb19cff54 | |||
| 5a73c8ee6c | |||
| 35b0d01c51 | |||
| b84cf7c15d | |||
|
|
8e79d6a6a8 | ||
|
|
60d1f48bd9 | ||
|
|
ee2b7c40b9 | ||
| 9b4d61c2b0 | |||
|
|
5b1e4513e2 | ||
|
|
0c42979423 | ||
|
|
36ac8607a9 | ||
|
|
4f4f34e281 | ||
|
|
8a5e12ec63 | ||
|
|
7d4c5d7be8 | ||
|
|
2dfb10e57b | ||
|
|
036cd628d3 | ||
|
|
9ee567530f | ||
|
|
e080e93faa | ||
|
|
efed1c0edb | ||
|
|
0c76052478 | ||
|
|
2af351efa8 | ||
|
|
949bbb0835 | ||
|
|
5ed81e4be9 | ||
| c5fc6219e1 | |||
| ba2bc60add | |||
|
|
8027c3d1bf | ||
|
|
dc77cc27da | ||
|
|
afa634f085 | ||
|
|
791a42bcd4 | ||
|
|
df1849180d | ||
| 27094846f7 | |||
|
|
ed4fbe5651 | ||
|
|
d04818ccd8 | ||
|
|
61c43962fb | ||
|
|
b4ad6870bf | ||
|
|
ea297d09bb | ||
|
|
85769e9d2c | ||
|
|
5714ed16d7 | ||
|
|
e919e96c53 | ||
|
|
442be8cbee | ||
|
|
df7a7c65d6 | ||
|
|
a409090b90 | ||
|
|
5cce1c79fc | ||
|
|
61c1e88dcf | ||
|
|
038ebaa268 | ||
|
|
91f62e1943 | ||
|
|
7986549fdd | ||
|
|
1d8da6579e | ||
|
|
0dff12232c | ||
|
|
a5e9e3c214 | ||
|
|
8e181b3010 | ||
|
|
44af9e16e4 | ||
|
|
7760e3acf4 | ||
|
|
a8ea3bc683 | ||
|
|
4c61ebd3fc | ||
|
|
591687eee8 | ||
|
|
261d2f2e84 | ||
|
|
3839bff29e | ||
|
|
78a6939929 | ||
|
|
3406e9a0e2 | ||
|
|
c51e6881d1 | ||
|
|
d70011016a | ||
|
|
f67280127a | ||
|
|
f932168f66 | ||
|
|
dc100b5d3a | ||
|
|
10eb9e4456 | ||
|
|
2f537b4f3e | ||
|
|
11303daebc | ||
|
|
60ab140c55 | ||
|
|
51bbd6590b | ||
|
|
346b9ced97 | ||
|
|
2992e784b0 | ||
|
|
2593b371d5 | ||
|
|
8f360206fb | ||
|
|
0c44849e15 | ||
|
|
f93a98e46a | ||
|
|
e5443122b4 | ||
|
|
28f192472b | ||
|
|
745f9dd6fb | ||
|
|
471e55dcea | ||
|
|
adbe8ac163 | ||
|
|
e44223084a | ||
|
|
7718d76be9 | ||
|
|
f22a3cd7c0 | ||
|
|
5abb2a4d4f | ||
|
|
74e6379d93 | ||
|
|
7f2a5893d4 | ||
|
|
89d03f4244 | ||
|
|
0c5bec8002 | ||
|
|
265652c420 | ||
|
|
208103d708 | ||
|
|
6ba8b68654 | ||
|
|
eba5d9f508 | ||
|
|
d9ee23c5bb | ||
|
|
d62b8d1af1 | ||
|
|
62f0851406 | ||
|
|
7ab5b157b1 | ||
|
|
a99a55788a | ||
|
|
3a48859ddd | ||
|
|
ced29975b2 | ||
|
|
84e8320e8f | ||
|
|
5f1c0231e4 | ||
|
|
b17c3fae6d | ||
|
|
19fe070491 | ||
|
|
8c069b7ad3 | ||
|
|
87799d8f02 | ||
| 07ec5abc3e | |||
|
|
ee37897df4 | ||
|
|
8a6e9070e0 | ||
|
|
8ab60c9f01 | ||
|
|
5eb7be246c | ||
|
|
fe599dfe75 | ||
|
|
23632d3c09 | ||
|
|
2a0150600f | ||
| 4d387e8063 | |||
| 3b588c2039 | |||
|
|
e73c9fe417 | ||
|
|
35fab34445 | ||
|
|
35f3c72250 | ||
|
|
c4a738f281 | ||
|
|
e0212d5109 | ||
|
|
152bc45776 | ||
|
|
91d930ee92 | ||
|
|
c6a48dd3f6 | ||
|
|
39947b3694 | ||
|
|
8df9ea55a9 | ||
|
|
bec2649fc1 | ||
|
|
1599e48d75 | ||
|
|
bda02826b1 | ||
| 58c2b4634a | |||
|
|
a30fecf8ec | ||
|
|
b27b21bbfc | ||
| 12d09b54cb | |||
|
|
25b35846e9 | ||
|
|
0f43796cd8 | ||
|
|
0f730d6c50 | ||
|
|
92462cf0df | ||
|
|
c49b094068 | ||
|
|
b34aacb067 | ||
|
|
103d2e0323 | ||
|
|
ad83ba4cdc | ||
|
|
e95ac42d57 | ||
|
|
ad18fbd162 | ||
|
|
bc3054aa43 | ||
|
|
53e7c7feda | ||
|
|
f8691207e3 | ||
|
|
5ad72af566 | ||
|
|
d1a336e903 | ||
|
|
5be79730b3 | ||
|
|
9d0f210548 | ||
|
|
387b508e40 | ||
|
|
0ed8d9c82d | ||
|
|
23276e5c7c | ||
|
|
37b5101e44 | ||
|
|
f04ff2fe26 | ||
|
|
13c69ef862 | ||
|
|
27d945f57c | ||
|
|
aef8c3d987 | ||
|
|
115d794ef9 | ||
|
|
bc935cc80a | ||
|
|
e54f488b17 | ||
|
|
cc7138949c | ||
|
|
74be880f9d | ||
|
|
5dbee6f0c3 | ||
|
|
cbb190bf4b | ||
|
|
4375118cd9 | ||
|
|
9aa78a02a5 | ||
|
|
1269731b55 | ||
|
|
79d7832d09 | ||
|
|
64fa12b10c | ||
|
|
56306ab944 | ||
|
|
923c673988 | ||
|
|
7e2f5eaed7 | ||
| 4dc8957c45 | |||
| cbb89c0db5 | |||
| ecdaadb83e | |||
| b480fa28b2 | |||
| 8493b482c4 | |||
| 18406b0c94 | |||
| 8b9cb065c2 | |||
| 7ee732b31e | |||
| e49fc295ba | |||
| b691bf3231 | |||
| 0777b822b3 | |||
| fc5af56bb7 | |||
|
|
c9d46b60ec | ||
| fd91c3b12e | |||
| 1bb6191244 | |||
| 063349d96d | |||
| d377afbf8b | |||
| ded3d96aae | |||
|
|
56f6456ce8 | ||
| 95e0544b73 | |||
| 9d6821127e | |||
|
|
0ba61534cb | ||
|
|
6cdcf4930d | ||
| b535a6eb32 | |||
|
|
0036dc5c80 | ||
|
|
dcc8a6d249 | ||
| 203b3bb340 | |||
| 200f67a1eb | |||
|
|
9105bcba15 | ||
|
|
07f75914bf | ||
|
|
cb3cb5d5fc | ||
|
|
4165d484bf | ||
|
|
7f138dafd0 | ||
|
|
5a8f6a8f1f | ||
|
|
e7ce014c70 | ||
|
|
40ece9f425 | ||
|
|
bd73e27db4 | ||
|
|
0695c70dbf | ||
|
|
9e0f9df9de | ||
|
|
8e3aa7ef33 | ||
|
|
1fdbe6294e | ||
|
|
93b65ac2ed | ||
|
|
28d93b3c25 | ||
|
|
863521fb3b | ||
|
|
837fddf804 | ||
|
|
677aa624d7 | ||
|
|
d390af3c79 | ||
|
|
6af805ba3a | ||
|
|
cd34601379 | ||
|
|
b800c58502 | ||
|
|
34a6449529 | ||
|
|
0273a23913 | ||
|
|
e6b4049403 | ||
|
|
b602b0b895 | ||
|
|
4ca97abd06 | ||
|
|
da9e7aac41 | ||
|
|
3568b8eb5e | ||
|
|
fa05d9c492 | ||
|
|
7cf34f2956 | ||
|
|
23e881f14c | ||
|
|
7eb3113ada | ||
|
|
f3a8a7db56 | ||
|
|
12f680d7e9 | ||
|
|
21b643000d | ||
|
|
7dec3c0c2a | ||
|
|
93f3a307b4 | ||
|
|
babc925967 | ||
|
|
58d7df499c | ||
|
|
b0512e392b | ||
|
|
b7878d81c6 | ||
|
|
9accd17bd4 | ||
|
|
bfeb2398aa | ||
|
|
2321540e97 | ||
|
|
ccea0f4bd1 | ||
|
|
00e00197ef | ||
|
|
85c7f7817c | ||
|
|
bb3b313fd2 | ||
|
|
52e34cb830 | ||
|
|
8b8433deaf | ||
|
|
4faca8c43b | ||
|
|
4455b10cc7 | ||
|
|
2d67b1218f | ||
|
|
fa1d7374bc | ||
|
|
c122e744ef | ||
|
|
708a136a5c | ||
|
|
f045b400a5 | ||
| dd2f696f8d | |||
|
|
b2b6a06280 | ||
|
|
1b51d65c89 | ||
|
|
a41cb3ed87 | ||
|
|
6521a4b2a9 | ||
|
|
e015a79526 | ||
|
|
359e65496f | ||
|
|
bb727f70be | ||
| ab2cd688fa | |||
|
|
f5e8fb5782 | ||
|
|
945e09b49e | ||
| 72b7a9d7b1 | |||
|
|
9cc299ced6 | ||
|
|
0560a01a55 | ||
|
|
1629c85818 | ||
|
|
5e98f45210 | ||
|
|
56643f9753 | ||
|
|
52431747ee | ||
|
|
6061295e0a | ||
|
|
89423d24b4 | ||
|
|
4f170db07d | ||
|
|
aea9b16f98 | ||
|
|
9658697d96 | ||
|
|
864afd8dcb | ||
|
|
1fac71dbd1 | ||
|
|
e199a0b766 | ||
|
|
4ae2966e98 | ||
|
|
e2c9955b41 | ||
|
|
9f1dcc667d | ||
|
|
fcfda3a447 | ||
|
|
486b2e4dfc | ||
|
|
ad85c2b816 | ||
|
|
2ddfde2642 | ||
|
|
882b5b4e00 | ||
|
|
6afd372945 | ||
|
|
6d4a4307cf | ||
|
|
fe4f0e7ecd | ||
|
|
32b73da959 | ||
|
|
d06b0f9f7a | ||
|
|
f28ab5182c | ||
|
|
e60242198a | ||
|
|
5be0cbbbf1 | ||
|
|
2462d24214 | ||
|
|
7dd53f3a94 | ||
|
|
c1910bacb0 | ||
|
|
2562e65d66 | ||
|
|
fe8c2378d2 | ||
|
|
f3bf746c6e | ||
|
|
353c43c987 | ||
|
|
04a82fb6a6 | ||
|
|
f48f39e67b | ||
|
|
1fa67f451a | ||
|
|
ebeb7d692b | ||
|
|
950c4a573a | ||
|
|
9b76f8afa0 | ||
|
|
007ade81a0 | ||
|
|
8b5fabce0c | ||
|
|
7401138d87 | ||
|
|
ad8a06d831 | ||
|
|
6d69cd2548 | ||
|
|
295b466505 | ||
|
|
1f2374927d | ||
|
|
6147289b40 | ||
|
|
0956525e94 | ||
|
|
2b952d0591 | ||
|
|
15e9ff92a8 | ||
|
|
b7c3d22961 | ||
|
|
9dbe0d7ff7 | ||
|
|
d9739cdb56 | ||
|
|
bc464cd264 | ||
|
|
00b8cd74b6 | ||
|
|
070c8ea486 | ||
|
|
46282a0406 | ||
|
|
e99ba5864a | ||
|
|
c9684beb5b | ||
|
|
2d940493a3 | ||
|
|
94e74deb24 | ||
|
|
f47ad8272f | ||
|
|
39016c93aa | ||
|
|
e9c420a873 | ||
|
|
beb61176c1 | ||
|
|
4ed52a3c9f | ||
|
|
723caca417 | ||
|
|
57d704b759 | ||
|
|
e9acddfa16 | ||
|
|
f3ee5a0854 | ||
|
|
5203d3acf8 | ||
|
|
b0eba78697 | ||
|
|
bce2717088 | ||
|
|
21d7056aae |
9
.editorconfig
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
96
.eslintignore
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# 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
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
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
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
name: Best Practices
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v3
|
||||||
|
with:
|
||||||
|
version: 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
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
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
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
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
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: Tests
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v3
|
||||||
|
with:
|
||||||
|
version: 9
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: pnpm test
|
||||||
43
.github/workflows/validate-pr.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
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
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
# 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
.husky/commit-msg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
npx --no -- commitlint --edit $1
|
||||||
19
.prettierignore
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
*.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
|
||||||
12
.prettierrc.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"useTabs": false,
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"semi": true,
|
||||||
|
"jsxSingleQuote": true
|
||||||
|
}
|
||||||
37
.releaserc.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
24
.storybook/main.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
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;
|
||||||
178
.storybook/preview.tsx
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
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;
|
||||||
28
.storybook/vite-storybook.config.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"antfu.unocss",
|
||||||
|
"editorconfig.editorconfig",
|
||||||
|
"figma.figma-vscode-extension"
|
||||||
|
]
|
||||||
|
}
|
||||||
18
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"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
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"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
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_PACKAGE_VERSION: string;
|
||||||
|
readonly VITE_BETA_BUILD?: 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
||||||
21
LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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.
|
||||||
75
README.md
@@ -1,40 +1,59 @@
|
|||||||
|
|
||||||
# UT Registration Plus
|
# 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)
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
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.
|
- 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.
|
||||||
This extension tries to streamline most of the unnecessary steps and headaches of registering for classes at UT Austin.
|
|
||||||
|
|
||||||
|
- Shows the course description with highlighted information on prerequisites, restrictions, etc.
|
||||||
|
|
||||||
- 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.
|
- Shows an aggregate and semesterly graph of the grade distributions for each course.
|
||||||
|
|
||||||
- Gets the course description and highlight the important information like prerequisites, restrictions, etc.
|
- 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.
|
||||||
|
|
||||||
- Shows an aggregate graph of the grade distributions for when the professor taught the class in the past.
|
|
||||||
|
|
||||||
- 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.
|
|
||||||
|
|
||||||
- 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.
|
- 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.
|
||||||
|
|
||||||
- Display's a weekly schedule based on your saved courses
|
- Display's a weekly schedule based on your saved courses.
|
||||||
|
|
||||||
<p align="center">
|
- Give you the ability to create multiple schedules to plan for different scenarios.
|
||||||
<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>
|
|
||||||
|
|
||||||
# 2.0 coming soon....
|
- ... and much more!
|
||||||
|
|
||||||
|
## Toolchain
|
||||||
|
|
||||||
|
- React 18
|
||||||
|
- TypeScript
|
||||||
|
- Vite 5
|
||||||
|
- ESLint
|
||||||
|
- Prettier
|
||||||
|
- Storybook
|
||||||
|
- Semantic-Release
|
||||||
|
- Custom Messaging & Storage Wrappers
|
||||||
|
|
||||||
|
## Development: Getting Started
|
||||||
|
|
||||||
|
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.
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
<!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>
|
|
||||||
5
chromatic.config.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"onlyChanged": true,
|
||||||
|
"projectId": "Project:65c5172964f36dcf207985bf",
|
||||||
|
"zip": true
|
||||||
|
}
|
||||||
123
commitlint.config.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { RuleConfigCondition, RuleConfigSeverity, TargetCaseType } from '@commitlint/types';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
parserPreset: 'conventional-changelog-conventionalcommits',
|
||||||
|
rules: {
|
||||||
|
'body-leading-blank': [RuleConfigSeverity.Warning, 'always'] as const,
|
||||||
|
'body-max-line-length': [RuleConfigSeverity.Error, 'always', 100] as const,
|
||||||
|
'footer-leading-blank': [RuleConfigSeverity.Warning, 'always'] as const,
|
||||||
|
'footer-max-line-length': [RuleConfigSeverity.Error, 'always', 100] as const,
|
||||||
|
'header-max-length': [RuleConfigSeverity.Error, 'always', 100] as const,
|
||||||
|
'header-trim': [RuleConfigSeverity.Error, 'always'] as const,
|
||||||
|
'subject-case': [
|
||||||
|
RuleConfigSeverity.Error,
|
||||||
|
'never',
|
||||||
|
['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
|
||||||
|
] as [RuleConfigSeverity, RuleConfigCondition, TargetCaseType[]],
|
||||||
|
'subject-empty': [RuleConfigSeverity.Error, 'never'] as const,
|
||||||
|
'subject-full-stop': [RuleConfigSeverity.Error, 'never', '.'] as const,
|
||||||
|
'type-case': [RuleConfigSeverity.Error, 'always', 'lower-case'] as const,
|
||||||
|
'type-empty': [RuleConfigSeverity.Error, 'never'] as const,
|
||||||
|
'type-enum': [
|
||||||
|
RuleConfigSeverity.Error,
|
||||||
|
'always',
|
||||||
|
['build', 'chore', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test'],
|
||||||
|
] as [RuleConfigSeverity, RuleConfigCondition, string[]],
|
||||||
|
},
|
||||||
|
prompt: {
|
||||||
|
questions: {
|
||||||
|
type: {
|
||||||
|
description: "Select the type of change that you're committing",
|
||||||
|
enum: {
|
||||||
|
feat: {
|
||||||
|
description: 'A new feature',
|
||||||
|
title: 'Features',
|
||||||
|
emoji: '✨',
|
||||||
|
},
|
||||||
|
fix: {
|
||||||
|
description: 'A bug fix',
|
||||||
|
title: 'Bug Fixes',
|
||||||
|
emoji: '🐛',
|
||||||
|
},
|
||||||
|
docs: {
|
||||||
|
description: 'Documentation only changes',
|
||||||
|
title: 'Documentation',
|
||||||
|
emoji: '📚',
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
description:
|
||||||
|
'Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)',
|
||||||
|
title: 'Styles',
|
||||||
|
emoji: '💎',
|
||||||
|
},
|
||||||
|
refactor: {
|
||||||
|
description: 'A code change that neither fixes a bug nor adds a feature',
|
||||||
|
title: 'Code Refactoring',
|
||||||
|
emoji: '📦',
|
||||||
|
},
|
||||||
|
perf: {
|
||||||
|
description: 'A code change that improves performance',
|
||||||
|
title: 'Performance Improvements',
|
||||||
|
emoji: '🚀',
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
description: 'Adding missing tests or correcting existing tests',
|
||||||
|
title: 'Tests',
|
||||||
|
emoji: '🚨',
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
description:
|
||||||
|
'Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)',
|
||||||
|
title: 'Builds',
|
||||||
|
emoji: '🛠',
|
||||||
|
},
|
||||||
|
ci: {
|
||||||
|
description:
|
||||||
|
'Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)',
|
||||||
|
title: 'Continuous Integrations',
|
||||||
|
emoji: '⚙️',
|
||||||
|
},
|
||||||
|
chore: {
|
||||||
|
description: "Other changes that don't modify src or test files",
|
||||||
|
title: 'Chores',
|
||||||
|
emoji: '♻️',
|
||||||
|
},
|
||||||
|
revert: {
|
||||||
|
description: 'Reverts a previous commit',
|
||||||
|
title: 'Reverts',
|
||||||
|
emoji: '🗑',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scope: {
|
||||||
|
description: 'What is the scope of this change (e.g. component or file name)',
|
||||||
|
},
|
||||||
|
subject: {
|
||||||
|
description: 'Write a short, imperative tense description of the change',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
description: 'Provide a longer description of the change',
|
||||||
|
},
|
||||||
|
isBreaking: {
|
||||||
|
description: 'Are there any breaking changes?',
|
||||||
|
},
|
||||||
|
breakingBody: {
|
||||||
|
description:
|
||||||
|
'A BREAKING CHANGE commit requires a body. Please enter a longer description of the commit itself',
|
||||||
|
},
|
||||||
|
breaking: {
|
||||||
|
description: 'Describe the breaking changes',
|
||||||
|
},
|
||||||
|
isIssueAffected: {
|
||||||
|
description: 'Does this change affect any open issues?',
|
||||||
|
},
|
||||||
|
issuesBody: {
|
||||||
|
description:
|
||||||
|
'If issues are closed, the commit requires a body. Please enter a longer description of the commit itself',
|
||||||
|
},
|
||||||
|
issues: {
|
||||||
|
description: 'Add issue references (e.g. "fix #123", "re #123".)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,344 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
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
@@ -1,81 +0,0 @@
|
|||||||
.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
@@ -1,563 +0,0 @@
|
|||||||
.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
@@ -1,308 +0,0 @@
|
|||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
docs/UT-Austin-Design-System.pdf
Normal file
@@ -1 +0,0 @@
|
|||||||
theme: jekyll-theme-cayman
|
|
||||||
@@ -1 +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"]
|
|
||||||
|
Before Width: | Height: | Size: 19 KiB |
BIN
icons/icon16.png
|
Before Width: | Height: | Size: 18 KiB |
BIN
icons/icon32.png
|
Before Width: | Height: | Size: 18 KiB |
BIN
icons/icon48.png
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
196
js/Template.js
@@ -1,196 +0,0 @@
|
|||||||
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
@@ -1,487 +0,0 @@
|
|||||||
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
@@ -1,291 +0,0 @@
|
|||||||
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
@@ -1,93 +0,0 @@
|
|||||||
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!";
|
|
||||||
@@ -1,627 +0,0 @@
|
|||||||
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
@@ -1,164 +0,0 @@
|
|||||||
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
8737
js/lib/highcharts.js
6
js/lib/html2canvas.min.js
vendored
231
js/lib/ics.min.js
vendored
@@ -1,231 +0,0 @@
|
|||||||
/*! 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
1
js/lib/jquery.initialize.min.js
vendored
@@ -1 +0,0 @@
|
|||||||
(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
@@ -1,60 +0,0 @@
|
|||||||
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
@@ -1,437 +0,0 @@
|
|||||||
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
@@ -1,242 +0,0 @@
|
|||||||
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
@@ -1,386 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
{
|
|
||||||
"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
@@ -1,30 +0,0 @@
|
|||||||
<!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
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
105
patches/@crxjs__vite-plugin@2.0.0-beta.21.patch
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
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",
|
||||||
108
patches/@unocss__vite@0.58.6.patch
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
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
Normal file
90
popup.html
@@ -1,90 +0,0 @@
|
|||||||
<!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>
|
|
||||||
10
postcss.config.cjs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* 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;
|
||||||
BIN
public/database/grade_distributions.db
Normal file
BIN
public/fonts/inter-100.woff2
Normal file
BIN
public/fonts/inter-200.woff2
Normal file
BIN
public/fonts/inter-300.woff2
Normal file
BIN
public/fonts/inter-400.woff2
Normal file
BIN
public/fonts/inter-500.woff2
Normal file
BIN
public/fonts/inter-600.woff2
Normal file
BIN
public/fonts/inter-700.woff2
Normal file
BIN
public/fonts/inter-800.woff2
Normal file
BIN
public/fonts/inter-900.woff2
Normal file
BIN
public/fonts/roboto-flex.woff2
Normal file
30
public/icons/icon_beta.svg
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?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>
|
||||||
|
After Width: | Height: | Size: 684 B |
BIN
public/icons/icon_beta_128.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
public/icons/icon_beta_16.png
Normal file
|
After Width: | Height: | Size: 429 B |
BIN
public/icons/icon_beta_32.png
Normal file
|
After Width: | Height: | Size: 791 B |
BIN
public/icons/icon_beta_48.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
30
public/icons/icon_development.svg
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?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>
|
||||||
|
After Width: | Height: | Size: 697 B |
BIN
public/icons/icon_development_128.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/icons/icon_development_16.png
Normal file
|
After Width: | Height: | Size: 449 B |
BIN
public/icons/icon_development_32.png
Normal file
|
After Width: | Height: | Size: 729 B |
BIN
public/icons/icon_development_48.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
30
public/icons/icon_production.svg
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?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>
|
||||||
|
After Width: | Height: | Size: 690 B |
BIN
public/icons/icon_production_128.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
public/icons/icon_production_16.png
Normal file
|
After Width: | Height: | Size: 421 B |
BIN
public/icons/icon_production_32.png
Normal file
|
After Width: | Height: | Size: 706 B |
BIN
public/icons/icon_production_48.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
221
public/json/departments.json
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
[
|
||||||
|
"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"
|
||||||
|
]
|
||||||
54
src/assets/insideJokes.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
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;
|
||||||
3
src/assets/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
157
src/debug/index.tsx
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
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
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
64
src/manifest.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
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;
|
||||||
59
src/pages/background/background.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||