Compare commits

...

146 Commits

Author SHA1 Message Date
Hykilpikonna 46ef56efbd [M] Move deploy script 2020-01-27 16:49:48 -05:00
Hykilpikonna 7b04d1e006 [F] Enable third quarter 2020-01-27 16:49:38 -05:00
Hykilpikonna 511d4544b6 [U] Release v0.5.3.1373 2020-01-27 16:45:09 -05:00
Hykilpikonna 140893a9b2 [S] Correct margins
#3
2020-01-27 16:38:08 -05:00
Hykilpikonna 427773f4cb [+] Display assignment completion status if it is not complete
#3
2020-01-27 16:37:48 -05:00
Hykilpikonna dfeb5d0272 [+] Create method to get completion status color
#3
2020-01-27 16:37:10 -05:00
Hykilpikonna 304ba63771 [+] Create method to get completion status
#3
2020-01-27 16:36:43 -05:00
Hykilpikonna 1698f1737d [+] Include late and NREQ assignments
#3
2020-01-27 16:03:09 -05:00
Hykilpikonna 9d8a797c54 [-] Remove background for now 2020-01-23 20:35:16 -05:00
Hykilpikonna 4d0c48619d [+] Add title 2020-01-05 18:27:12 -05:00
Hykilpikonna e95d212628 [O] Compress background 2020-01-05 18:25:42 -05:00
Hykilpikonna 936e6ffa41 [+] Create info page background 2020-01-05 18:23:32 -05:00
Hykilpikonna f2a3260b8f [F] Dark mode should not apply to info page 2020-01-05 17:33:25 -05:00
Hykilpikonna 5c8b2b37d4 [O] Mark darkmode as unfinished 2020-01-05 17:31:33 -05:00
Hykilpikonna 44e4b615ba [S] Auto opacity based on darkmode 2020-01-05 17:31:18 -05:00
Hykilpikonna 60a852815b [S] Dark mode for unread number 2020-01-05 17:18:15 -05:00
Hykilpikonna 7c7d7e609a [S] Make course assignment type background look nicer 2020-01-05 17:17:43 -05:00
Hykilpikonna 0da5be9448 [S] Fix darkmode for navbar 2020-01-05 17:13:39 -05:00
Hykilpikonna 1ebb69c89c [S] Fix darkmode for desktop background 2020-01-05 17:13:29 -05:00
Hykilpikonna 99af1c4607 [S] Remove awkward white areas 2020-01-05 17:10:46 -05:00
Hykilpikonna e1caacce34 [+] Implement dark mode switcing 2020-01-05 17:09:13 -05:00
Hykilpikonna e2b6a92743 [O] Change text depending on isDark value 2020-01-05 17:09:03 -05:00
Hykilpikonna 982c8cf872 [+] Create isDark() function 2020-01-05 17:07:58 -05:00
Hykilpikonna 1e928ac1a8 [+] Add darkmode button 2020-01-05 17:07:39 -05:00
Hykilpikonna a17ab1511d [+] Add dark mode class depending on the switch 2020-01-05 17:07:28 -05:00
Hykilpikonna 07c55df766 [+] Initialize dark mode switch to cookie value 2020-01-05 17:07:15 -05:00
Hykilpikonna 06ee7b4de9 [+] Add dark mode switch 2020-01-05 17:07:00 -05:00
Hykilpikonna 0c520d5f50 [+] Use logo more compatible to dark mode 2020-01-05 17:06:48 -05:00
Hykilpikonna 522867a2dc [F] Ignore static page detection for nav-controller 2020-01-05 16:23:17 -05:00
Hykilpikonna d56d985734 [+] Display content according to staticpage 2020-01-05 16:22:56 -05:00
Hykilpikonna 1d404b4f51 [+] Detect static pages 2020-01-05 16:22:25 -05:00
Hykilpikonna c1d3cc88bc [+] Add static page field 2020-01-05 16:22:18 -05:00
Hykilpikonna 1242e2ed58 [+] Import info static page 2020-01-05 16:22:10 -05:00
Hykilpikonna f61dccb90a [+] Add hello world for testing 2020-01-04 21:15:42 -05:00
Hykilpikonna 106da7e7f1 [+] Create info component 2020-01-04 18:57:17 -05:00
Hykilpikonna 4774ad658b [O] Ignore jsons from tslint 2020-01-04 18:51:43 -05:00
Hykilpikonna 6035fe5318 [F] Fix older version url navigation issue 2020-01-04 18:51:26 -05:00
Hykilpikonna 452ac69fb6 [+] Add reminder in deploy script 2020-01-04 18:47:25 -05:00
Hykilpikonna 3ec604f049 [+] Redirect from vera.hydev.org/info 2020-01-04 18:47:13 -05:00
Hykilpikonna f5c093960a [U] Release v0.5.2.1335 2019-12-21 20:47:52 -05:00
Hykilpikonna 984fa394a5 [F] Fix history state null pointer 2019-12-21 20:47:11 -05:00
Hykilpikonna db12a5b9e7 [S] Fix updates style 2019-12-21 20:42:55 -05:00
Hykilpikonna dc57ee16c9 [U] Release v0.5.2.1332 2019-12-21 20:39:15 -05:00
Hykilpikonna 975542dab2 [S] Move the cancel button to the left 2019-12-21 20:38:41 -05:00
Hykilpikonna 5ad2be88c1 [+] Implement mark all as read 2019-12-21 20:27:23 -05:00
Hykilpikonna 76ec5d4476 [S] Make margins line up with the other cards 2019-12-21 20:24:12 -05:00
Hykilpikonna 62237d5f5f [S] Round to one digit 2019-12-21 20:23:52 -05:00
Hykilpikonna eeb2ae56d7 [S] Add margins to progress bar 2019-12-21 20:22:32 -05:00
Hykilpikonna bb1751f442 [S] Change progress bar color to green 2019-12-21 20:21:58 -05:00
Hykilpikonna a4241f9549 [S] Make progress bar visible 2019-12-21 20:21:43 -05:00
Hykilpikonna 693d479b0f [F] Progress percentage * 100 2019-12-21 20:20:35 -05:00
Hykilpikonna cf5c59fb78 [O] Separate started flag 2019-12-21 20:20:06 -05:00
Hykilpikonna 311055a4c9 [O] Create progress getter 2019-12-21 20:19:50 -05:00
Hykilpikonna 02d5e209d9 [U] Update usage in overall-course 2019-12-21 20:14:05 -05:00
Hykilpikonna e4ebaf2935 [+] Call callbacks when unread is updated 2019-12-21 20:13:53 -05:00
Hykilpikonna 97fe8395e6 [+] Encapsulate addCallback() 2019-12-21 20:13:42 -05:00
Hykilpikonna d7b319b6c9 [+] Create updateCallbacks in assignment 2019-12-21 20:13:32 -05:00
Hykilpikonna 1fdf6a8a72 [F] Fix import 2019-12-21 19:52:26 -05:00
Hykilpikonna 3425894c24 [F] Fix vue update problem 2019-12-21 19:45:08 -05:00
Hykilpikonna 3e6caab023 [S] Fix dialog checkbox alignment 2019-12-21 19:39:37 -05:00
Hykilpikonna 7e23919c0d [S] Fix word breaking globally 2019-12-21 19:39:26 -05:00
Hykilpikonna 6b7a5af7ae [O] Inline overall-course 2019-12-21 19:39:03 -05:00
Hykilpikonna 74bfec92f6 [U] Update usage in overall-course 2019-12-21 19:38:46 -05:00
Hykilpikonna 30030a0ca5 [+] Encapsulate markAsRead() 2019-12-21 19:38:28 -05:00
Hykilpikonna 78590f96f1 [O] Change that to -1 2019-12-21 19:24:23 -05:00
Hykilpikonna cc485708c5 [U] Only show progress when progress is not 0 2019-12-21 19:24:02 -05:00
Hykilpikonna daeca0fb0b [O] Use better cookie name 2019-12-21 19:23:13 -05:00
Hykilpikonna 5b5704bd75 [+] Set cookies when don't ask again is checked 2019-12-21 19:20:27 -05:00
Hykilpikonna 762be8bfba [+] Add progress bar 2019-12-21 19:10:02 -05:00
Hykilpikonna b224f07e00 [U] Update unread usage in vue 2019-12-21 18:56:12 -05:00
Hykilpikonna b1a902f92e [O] Optimize mark as unread 2019-12-21 18:55:02 -05:00
Hykilpikonna aeadb6fc8b [O] Optimize count unread for overall-course 2019-12-21 18:54:50 -05:00
Hykilpikonna e8aeecb27c [+] Add don't ask again checkbox 2019-12-21 18:40:56 -05:00
Hykilpikonna 29131227b5 [O] Use better variable names 2019-12-21 18:40:43 -05:00
Hykilpikonna 9b3a61e5cc [O] Cache unread assignments 2019-12-21 18:39:47 -05:00
Hykilpikonna 4bc9113b96 [+] Add image indicating too many unread 2019-12-21 18:32:24 -05:00
Hykilpikonna 45e0ac6066 [S] Fix word newline stuff 2019-12-21 18:26:09 -05:00
Hykilpikonna 4047a3e8f6 [+] Detect notification count 2019-12-21 18:25:43 -05:00
Hykilpikonna d4d8312016 [+] Create too many notifications prompt 2019-12-21 18:25:27 -05:00
Hykilpikonna e60df664c1 [O] Only show term grades if the selected term is "all year" 2019-12-21 17:32:03 -05:00
Hykilpikonna 916c586cbc [O] Encapsulate rawSelectedTerm() 2019-12-21 17:30:45 -05:00
Hykilpikonna a903b2cdfa [S] Largen the gap between term and current grades 2019-12-21 17:24:02 -05:00
Hykilpikonna 79f3a8e497 [S] Adjust term and numeric color to create contrast 2019-12-21 17:20:53 -05:00
Hykilpikonna 5905eb1923 [S] Adjust letter grade font size 2019-12-21 17:20:35 -05:00
Hykilpikonna 6dd3d898aa [S] Set color to gray 2019-12-21 17:20:24 -05:00
Hykilpikonna 7c86ecf3b5 [S] Apply font size to numeric too 2019-12-21 17:20:12 -05:00
Hykilpikonna 45e1376a8f [S] Auto adjust width based on contnet 2019-12-21 17:16:40 -05:00
Hykilpikonna 07f3544ba5 [S] Reduce term font size 2019-12-21 17:16:16 -05:00
Hykilpikonna 4f93539673 [F] Fix: displaying zero based counting 2019-12-21 17:14:09 -05:00
Hykilpikonna 095dfdb54f [+] Display term number 2019-12-21 17:13:48 -05:00
Hykilpikonna eaac2b9332 [S] Vertical align letter and numeric 2019-12-21 17:13:38 -05:00
Hykilpikonna e63547140b [S] Fix reversed array 2019-12-21 17:08:32 -05:00
Hykilpikonna 99aa7b4093 [S] Fix display order 2019-12-21 17:08:02 -05:00
Hykilpikonna b4e6dcdb8c [S] Adjust alignment 2019-12-21 17:07:51 -05:00
Hykilpikonna 2d182c3cf4 [+] Display term grades 2019-12-21 17:02:35 -05:00
Hykilpikonna 05feb8c9f3 [O] Enable caching for numeric grade 2019-12-21 17:02:08 -05:00
Hykilpikonna 3adfdb5aac [+] Encapsulate course.letterGradeTerm() 2019-12-21 17:01:57 -05:00
Hykilpikonna c0ec2e03ff [+] Encapsulate getAllGradingPeriods() 2019-12-21 17:01:37 -05:00
Hykilpikonna e128704552 [S] Optimize other selectors 2019-12-21 15:18:50 -05:00
Hykilpikonna af2aba9a50 [S] Optimize css selectors for block-info and block-grade 2019-12-21 15:16:10 -05:00
Hykilpikonna dbdd33d050 [S] Scope css 2019-12-21 15:13:13 -05:00
Hykilpikonna d230636a0e [O] Remove el-row from course-head 2019-12-21 15:13:04 -05:00
Hykilpikonna e7d639af33 [O] Optimize course-head clickable 2019-12-21 14:50:17 -05:00
Hykilpikonna b97ca4f699 [F] Fix history refresh problem 2019-12-21 14:46:06 -05:00
Hykilpikonna 1657330c57 [-] Remove installed element-ui js library 2019-12-21 14:45:14 -05:00
Hykilpikonna dd8bbf2cbe [U] Update dependencies 2019-12-21 14:44:37 -05:00
Hykilpikonna 53065c9dd3 [O] Serialize index 2019-12-21 14:09:14 -05:00
Hykilpikonna 27bcd6b9b6 [F] Fix navigator selection 2019-12-21 14:08:55 -05:00
Hykilpikonna bc190cbdfb [F] Fix problem where history activates twice 2019-12-21 13:53:02 -05:00
Hykilpikonna 755b384b76 [F] Fix initial index conversion 2019-12-21 13:41:53 -05:00
Hykilpikonna a3f03f5577 [O] Optimize null case with param default 2019-12-21 13:40:46 -05:00
Hykilpikonna 57f29262e3 [O] Separate checkIndex 2019-12-21 13:40:34 -05:00
Hykilpikonna ac5549ccc4 [F] Remove lastTab from history state 2019-12-21 13:40:05 -05:00
Hykilpikonna d3ba0af4f5 [F] Update current index 2019-12-21 13:32:32 -05:00
Hykilpikonna 6312515b7b [U] Update nav usage in navigation vue 2019-12-21 13:31:58 -05:00
Hykilpikonna 41cdbe93b1 [F] Fix error due to auto caching of get methods 2019-12-21 13:31:45 -05:00
Hykilpikonna 702eb4d8f3 [U] Update usage 2019-12-21 13:25:10 -05:00
Hykilpikonna 7581ea016e [O] Specify type for id and info 2019-12-21 13:24:22 -05:00
Hykilpikonna 4938b2a72a [O] Make identifier required 2019-12-21 13:23:21 -05:00
Hykilpikonna 815258e8be [+] Add identifier and info in course.urlIndex 2019-12-21 13:21:37 -05:00
Hykilpikonna adb211c58a [+] Add info to index 2019-12-21 13:20:32 -05:00
Hykilpikonna cdeae1e1d4 [+] Add identifier to index 2019-12-21 13:20:18 -05:00
Hykilpikonna dfbe255191 [M] Shorten var name "activeIndex" to index 2019-12-21 13:20:08 -05:00
Hykilpikonna f8f1c1f8a3 [F] Fix course-head navigator usage 2019-12-21 13:11:35 -05:00
Hykilpikonna 4e5809a1dc [U] Update app component usage 2019-12-21 13:08:41 -05:00
Hykilpikonna aef5fa47fc [U] Update usage in Navigation 2019-12-21 13:07:42 -05:00
Hykilpikonna ca751c27d1 [O] Remove unnecessary getTitle() 2019-12-21 12:59:42 -05:00
Hykilpikonna 95690fe046 [O] Specify type 2019-12-21 12:58:02 -05:00
Hykilpikonna 1fd17706e4 [-] Remove duplicate in course-utils 2019-12-21 12:56:23 -05:00
Hykilpikonna 082400abe8 [+] Encapsulate get urlIndex in course 2019-12-21 12:56:13 -05:00
Hykilpikonna e90741b6bc [+] Encapsulate get urlHash in Course 2019-12-21 12:55:59 -05:00
Hykilpikonna 71352ee39a [O] Make activeIndex accessable 2019-12-21 12:50:02 -05:00
Hykilpikonna caa6b38673 [M] Rename Navigator class to NavController to avoid conflict 2019-12-21 12:47:10 -05:00
Hykilpikonna 49c23c9562 [+] Create navigator field in app 2019-12-21 12:40:56 -05:00
Hykilpikonna 94424ea288 [M] Rename filteredCourses to gradedCourses 2019-12-21 12:40:43 -05:00
Hykilpikonna 956119be2a [O] Optimize assignmentsReady 2019-12-21 12:39:48 -05:00
Hykilpikonna e26decd77d [O] Remove unnecessary public 2019-12-21 12:32:36 -05:00
Hykilpikonna 4d7b41bf2a [+] Create constructor 2019-12-21 11:52:16 -05:00
Hykilpikonna e6cfa30ed4 [O] Optimize encapsulation 2019-12-21 11:51:12 -05:00
Hykilpikonna ea46d16836 [+] Encapsulate updateIndex with only hash 2019-12-21 11:49:06 -05:00
Hykilpikonna 128dbb2ca7 [+] Auto format title if null 2019-12-21 11:44:43 -05:00
Hykilpikonna 097985f087 [O] Optimize nullable format 2019-12-21 11:44:21 -05:00
Hykilpikonna 26fa50ead2 [O] Make title nullable 2019-12-21 11:43:26 -05:00
Hykilpikonna 14a9c5a9b2 [O] Combine index and title 2019-12-21 11:37:25 -05:00
Hykilpikonna 55432e5c33 [+] Create navigator class 2019-12-21 11:34:09 -05:00
Hykilpikonna a2f6a30ad1 [U] Update packages 2019-12-08 13:05:26 -05:00
29 changed files with 4016 additions and 2878 deletions
+3
View File
@@ -1,5 +1,8 @@
#!/usr/bin/env bash
#echo "Switch to production database settings"
#exit
# abort on errors
set -e
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 51 KiB

+3345 -2607
View File
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -11,9 +11,9 @@
"@types/chroma-js": "^1.4.3",
"@types/md5": "^2.1.33",
"chroma-js": "^2.1.0",
"core-js": "^2.6.5",
"echarts": "^4.2.1",
"element-ui": "^2.11.1",
"core-js": "^2.6.10",
"echarts": "^4.5.0",
"element-ui": "^2.13.0",
"md5": "^2.2.1",
"moment": "^2.24.0",
"p-wait-for": "^3.1.0",
@@ -21,15 +21,15 @@
"vue": "^2.6.10",
"vue-class-component": "^7.0.2",
"vue-cookies": "^1.5.13",
"vue-property-decorator": "^8.1.0"
"vue-property-decorator": "^8.3.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.10.0",
"@vue/cli-plugin-typescript": "^3.10.0",
"@vue/cli-service": "^3.10.0",
"node-sass": "^4.9.0",
"sass-loader": "^7.1.0",
"typescript": "^3.4.3",
"@vue/cli-plugin-babel": "^3.12.1",
"@vue/cli-plugin-typescript": "^3.12.1",
"@vue/cli-service": "^4.1.1",
"node-sass": "^4.13.0",
"sass-loader": "^7.3.1",
"typescript": "^3.7.3",
"vue-template-compiler": "^2.6.10"
},
"postcss": {
-1
View File
@@ -24,7 +24,6 @@
<!-- ElementUI -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css?family=Nunito+Sans&display=swap" rel="stylesheet">
+14
View File
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Redirecting...</title>
<meta http-equiv = "refresh" content = "0; url = https://vera.hydev.org/#info" />
</head>
<body>
Redirecting to (<a href="https://vera.hydev.org/#info">https://vera.hydev.org/#info</a>)...
<script>
window.location.href = 'https://vera.hydev.org/#info';
</script>
</body>
</html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 44 KiB

+11 -10
View File
@@ -57,16 +57,17 @@
}
// Overall
#overall, #overall-course, .overall-span, #app-content
#app-inner, #overall, #overall-course, .overall-span, #app-content
{
background: var(--dark-layer-1) !important;
}
// Course card
.entry-box, .none .unread-number {background: #a1a1a1 !important}
.entry-box, .none .unread-number {background: #797979 !important}
.entry-box.max {background-color: #949494 !important}
.entry-box.percent {background-color: #a7a490 !important}
.course-name {color: #cffff6 !important}
#block-grade #updates.none #unread-number {background: #757575 !important}
.course-card-content.expand, .assignment-entry, .unread-row,
.unread-row .el-col, #assignment-type-head, .course-page-graph.el-col
@@ -74,14 +75,8 @@
background-color: var(--dark-layer-3) !important;
}
.overall-span.el-col, .course-page-graph.el-col
{
div, span
{
background: #f9f9f9 !important;
color: var(--dark-layer-1) !important;
}
}
// Nav bar
.el-menu--horizontal>.el-menu-item.is-active {color: var(--dark-foreground) !important;}
}
// ##############
@@ -144,3 +139,9 @@ div.el-card.course-card > div.el-card__body
{
font-family: Nunito Sans, Helvetica Neue, Microsoft YaHei, "微软雅黑", Arial, sans-serif;
}
// Fix word breaking
.el-dialog__body
{
word-break: unset !important;
}
+42 -29
View File
@@ -9,53 +9,67 @@ import Loading from '@/components/overlays/loading.vue';
import CoursePage from '@/pages/course/course-page.vue';
import Course from '@/logic/course';
import LoginUser from '@/logic/login-user';
import NavController from '@/logic/nav-controller';
import Info from '@/statics/Info.vue';
@Component({
components: {Login, Navigation, Overall, Loading, CoursePage},
components: {Login, Navigation, Overall, Loading, CoursePage, Info},
})
export default class App extends Vue
{
// Is the login panel shown
public showLogin: boolean = true;
showLogin: boolean = true;
// List of course that the student takes
public courses: Course[] = [];
// List of course that should be displayed
public filteredCourses: Course[] = [];
// The currently selected tab
public selectedTab: string = 'overall';
courses: Course[] = [];
gradedCourses: Course[] = [];
// Are the course assignments loaded from the server.
public assignmentsReady: boolean = false;
assignmentsReady: boolean = false;
// Token
public user: LoginUser = null as any;
user: LoginUser = null as any;
// Loading text
public loading: string = '';
loading: string = '';
// Loading error
public loadingError: boolean = false;
loadingError: boolean = false;
// Navigation controller
nav: NavController = new NavController();
// Http Client
public static http: HttpUtils = new HttpUtils();
static http: HttpUtils = new HttpUtils();
// Instance
public static instance: App;
static instance: App;
// Static page
staticPage: string = '';
// Dark mode
darkMode: boolean = false;
/**
* This is called when the instance is created.
*/
public created()
created()
{
// Show splash
console.log(Constants.SPLASH);
// Update instance
App.instance = this;
// Check location
if (window.location.hash == '#info')
{
this.staticPage = 'info';
}
// Dark mode
this.darkMode = this.$cookies.isKey('dark');
}
/**
@@ -63,7 +77,7 @@ export default class App extends Vue
*
* @param user Authorization user
*/
public onLogin(user: LoginUser)
onLogin(user: LoginUser)
{
// Hide login bar
this.showLogin = false;
@@ -84,7 +98,7 @@ export default class App extends Vue
/**
* Load courses data after logging in.
*/
public loadCoursesAfterLogin()
loadCoursesAfterLogin()
{
// Show loading message
this.logLoading('2. Loading courses...');
@@ -109,7 +123,7 @@ export default class App extends Vue
/**
* Load the assignments of the courses
*/
public loadAssignments()
loadAssignments()
{
// Show loading message
this.logLoading('3. Loading assignments...');
@@ -134,7 +148,7 @@ export default class App extends Vue
pWaitFor(() => this.courses.every(c => c.rawAssignments != null)).then(() =>
{
// Filter courses
this.filteredCourses = this.courses.filter(c => c.isGraded);
this.gradedCourses = this.courses.filter(c => c.isGraded);
// Check grading algorithms
this.checkGradingAlgorithms();
@@ -144,13 +158,13 @@ export default class App extends Vue
/**
* Check the courses' grading algorithms. (Total-mean or percent-type)
*/
private checkGradingAlgorithms()
checkGradingAlgorithms()
{
// Show loading message
this.logLoading('4. Checking grading algorithms...');
// Loop through all the courses
for (const course of this.filteredCourses)
for (const course of this.gradedCourses)
{
for (const i of [0, 1, 2, 3])
{
@@ -186,9 +200,8 @@ export default class App extends Vue
}
// Wait for done
pWaitFor(() => this.filteredCourses.every(c => c.termGrading.every(g => g != null))).then(() =>
pWaitFor(() => this.gradedCourses.every(c => c.termGrading.every(g => g != null))).then(() =>
{
// When the assignments are ready
this.assignmentsReady = true;
// Remove loading
@@ -201,7 +214,7 @@ export default class App extends Vue
*
* @param message Message
*/
private logLoading(message: string)
logLoading(message: string)
{
if (message == '') this.loading = '';
else this.loading += '\n' + message;
@@ -212,7 +225,7 @@ export default class App extends Vue
*
* @param message Error message
*/
private showError(message: string)
showError(message: string)
{
this.loadingError = true;
this.loading = message;
@@ -221,7 +234,7 @@ export default class App extends Vue
/**
* Sign out
*/
public signOut()
signOut()
{
// Clear all cookies
this.$cookies.keys().forEach(key => this.$cookies.remove(key));
@@ -235,7 +248,7 @@ export default class App extends Vue
*
* @param code
*/
public selectTime(code: number)
selectTime(code: number)
{
// TODO: Optimize
window.location.reload();
+19 -15
View File
@@ -1,23 +1,27 @@
<template>
<div id="app" class="theme-default">
<login v-if="showLogin" v-on:login:user="onLogin"/>
<navigation v-if="user != null"
:courses="filteredCourses"
:activeIndex.sync="selectedTab"
:user="user"
@sign-out="signOut" @select-time="selectTime">
</navigation>
<div id="app-inner" v-if="staticPage === ''" :class="{dark: darkMode}">
<login v-if="showLogin" v-on:login:user="onLogin"/>
<navigation v-if="user != null"
:courses="gradedCourses"
:user="user"
:nav="nav"
@sign-out="signOut" @select-time="selectTime">
</navigation>
<div id="app-content" v-if="assignmentsReady && loading === ''">
<overall v-if="selectedTab === 'overall'"
:courses="filteredCourses">
</overall>
<course-page v-if="selectedTab.split('/')[0] === 'course'"
:course="filteredCourses.find(c => +c.id === +selectedTab.split('/')[1])">
</course-page>
<div id="app-content" v-if="assignmentsReady && loading === ''">
<overall v-if="nav.id === 'overall'"
:courses="gradedCourses">
</overall>
<course-page v-if="nav.id === 'course'"
:course="gradedCourses.find(c => +c.id === +nav.info.id)">
</course-page>
</div>
<loading v-if="loading !== ''" :text="loading" :error="loadingError"/>
</div>
<loading v-if="loading !== ''" :text="loading" :error="loadingError"/>
<Info v-if="staticPage === 'info'"/>
</div>
</template>
+25 -77
View File
@@ -1,11 +1,9 @@
import {Component, Prop, Vue} from 'vue-property-decorator';
import App from '@/components/app/app';
import {CourseUtils} from '@/logic/utils/course-utils';
import {FormatUtils} from '@/logic/utils/format-utils';
import pWaitFor from 'p-wait-for';
import Course from '@/logic/course';
import Constants from '@/constants';
import LoginUser from '@/logic/login-user';
import NavController from '@/logic/nav-controller';
import App from '@/components/app/app';
/**
* This component is the top navigation bar
@@ -13,7 +11,7 @@ import LoginUser from '@/logic/login-user';
@Component
export default class Navigation extends Vue
{
@Prop({required: true}) activeIndex: string;
@Prop({required: true}) nav: NavController;
@Prop({required: true}) courses: Course[];
@Prop({required: true}) user: LoginUser;
@@ -40,35 +38,7 @@ export default class Navigation extends Vue
*/
mounted()
{
// Set instance
Navigation.instance = this;
// Set history state
let url = '/' + window.location.hash;
if (url == '/' || url == '') url = '/#overall';
window.history.replaceState({lastTab: url.substring(1)}, '', url);
// Update initial index after loading is done
pWaitFor(() => this.courses.length > 1 && App.instance.loading != '').then(() =>
{
this.updateIndex(url.substring(2), false);
});
// Create history state listener
window.onpopstate = e =>
{
if (e.state)
{
// Restore previous tab
console.log(`onPopState: Current: ${this.activeIndex}, Previous: ${e.state.lastTab}`);
this.updateIndex(e.state.lastTab, false);
}
};
}
formatCourseIndex(course: Course)
{
return CourseUtils.formatTabIndex(course);
}
/**
@@ -80,52 +50,16 @@ export default class Navigation extends Vue
onSelect(index: string, indexPath: string)
{
// Update active index
this.updateIndex(index);
}
/**
* Update index
*
* @param newIndex New index
* @param history Record in history or not (Default true)
*/
updateIndex(newIndex: string, history?: boolean)
{
// Call custom event
this.$emit('update:activeIndex', newIndex);
// Record or not
if (history == null || history)
try
{
// Check url
let url = `/#${newIndex}`;
// Push history state
window.history.pushState({lastTab: newIndex}, '', url);
// Is json
this.nav.updateIndex(JSON.parse(index))
}
// Update title
document.title = 'Veracross Analyzer - ' + this.getTitle(newIndex);
// Scroll to top
window.scrollTo(0, 0);
}
/**
* Get title for index
*
* @param index Index
*/
getTitle(index: string)
{
// Course
if (index.startsWith('course'))
catch (e)
{
return this.findCourse(index.split('/')[1], 0).name;
// Not json
this.nav.updateIndex(index);
}
// Others
return FormatUtils.toTitleCase(index);
}
/**
@@ -136,7 +70,7 @@ export default class Navigation extends Vue
nextCourse(indexOffset: number)
{
// Set tab to the next index
this.updateIndex(CourseUtils.formatTabIndex(this.findNextCourse(indexOffset)))
this.nav.updateIndex(this.findNextCourse(indexOffset).urlIndex)
}
/**
@@ -146,7 +80,7 @@ export default class Navigation extends Vue
*/
findNextCourse(indexOffset: number)
{
return this.findCourse(this.activeIndex.split('/')[1], indexOffset);
return this.findCourse(this.nav.info.id, indexOffset);
}
/**
@@ -201,8 +135,22 @@ export default class Navigation extends Vue
this.$emit('sign-out');
break
}
case 'switch-dark':
{
App.instance.darkMode = !App.instance.darkMode;
if (this.isDark()) this.$cookies.set('dark', true);
else this.$cookies.remove('dark');
break
}
}
}
isDark()
{
return App.instance.darkMode;
}
get version() {return Constants.VERSION}
}
+10 -7
View File
@@ -1,7 +1,7 @@
<template>
<div id="navigation">
<el-menu style="margin-bottom: 10px;" class="centered" mode="horizontal"
:default-active="activeIndex" @select="onSelect">
:default-active="nav.id" @select="onSelect">
<div id="nav-title">
<img id="nav-logo" alt="logo" src="../../assets/logo.png">
@@ -11,11 +11,11 @@
<el-menu-item index="overall">Overall</el-menu-item>
<el-submenu index="courses">
<el-submenu index="">
<template slot="title">Courses</template>
<el-menu-item v-for="course in courses"
:index="formatCourseIndex(course)"
:key="course.name">{{course.name}}</el-menu-item>
:index="JSON.stringify(course.urlIndex)"
:key="course.id">{{course.name}}</el-menu-item>
</el-submenu>
<!-- Grading period selection -->
@@ -26,8 +26,9 @@
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="Term 1">Term 1</el-dropdown-item>
<el-dropdown-item command="Term 2">Term 2</el-dropdown-item>
<el-dropdown-item command="Term 3" disabled>Term 3</el-dropdown-item>
<el-dropdown-item command="Term 3">Term 3</el-dropdown-item>
<el-dropdown-item command="Term 4" disabled>Term 4</el-dropdown-item>
<!-- TODO: Auto enable / disable quarters -->
<el-dropdown-item command="All Year" divided>All Year</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
@@ -39,18 +40,20 @@
<el-dropdown-menu slot="dropdown">
<el-dropdown-item style="text-align: center">{{user.nickname}}</el-dropdown-item>
<el-dropdown-item icon="el-icon-sunrise" command="switch-dark" divided>{{!isDark() ? 'Dark Mode (Unfinished)' : 'Light Mode'}}</el-dropdown-item>
<el-dropdown-item icon="el-icon-switch-button" command="sign-out" divided>Sign Out</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-menu>
<!-- Previous course / Next course (Only when the page is courses) -->
<div v-if="activeIndex.includes('course') && findNextCourse(-1) != null"
<div v-if="nav.id === 'course' && findNextCourse(-1) != null"
@click="nextCourse(-1)" id="prev-course" class="nav-course-operations unselectable">
PREVIOUS COURSE
</div>
<footer>
<div v-if="activeIndex.includes('course') && findNextCourse(1) != null"
<div v-if="nav.id === 'course' && findNextCourse(1) != null"
@click="nextCourse(1)" id="next-course" class="nav-course-operations unselectable">
NEXT COURSE
</div>
+1 -1
View File
@@ -10,7 +10,7 @@ export default class Constants
// static API_URL: string = 'http://localhost:24021/api';
/** Current version */
static VERSION: string = '0.5.1.1229';
static VERSION: string = '0.5.3.1373';
/** The minimum version that still supports the same cookies */
static MIN_SUPPORTED_VERSION: string = '0.4.6.1087';
+127 -6
View File
@@ -4,6 +4,8 @@ import Navigation from '@/components/navigation/navigation';
import {GPAUtils} from '@/logic/utils/gpa-utils';
import CacheUtils from '@/logic/utils/cache-utils';
import Constants from '@/constants';
import {Index} from '@/logic/nav-controller';
import App from '@/components/app/app';
/**
* Objects of this interface represent assignment grades.
@@ -27,6 +29,9 @@ export class Assignment
gradingPeriod: number;
// Callbacks when this object updates
private updateCallbacks: (() => void)[] = [];
/**
* Construct assignment with json object
*
@@ -57,7 +62,67 @@ export class Assignment
*/
get graded()
{
return this.complete == 'Complete';
// TODO: Add more cases
return this.include && (this.complete == 'Complete' || this.complete == 'Late' || this.complete == 'NREQ');
}
/**
* What is the problem with this assignment
*
* @return string Empty string if complete, otherwise return problem.
*/
get problem()
{
switch (this.complete)
{
case 'Complete': return '';
case 'Late': return 'Late';
case 'NREQ': return 'Dropped';
}
}
/**
* Get the text color of the problem
*/
get problemColor()
{
switch (this.complete)
{
case 'Late': return '#ff0036';
case 'NREQ': return '#41b141';
}
}
/**
* Add callback
*
* @param callback
*/
addCallback(callback: () => void)
{
this.updateCallbacks.push(callback);
}
/**
* Mark as read
*/
markAsRead(): Promise<void>
{
return new Promise((resolve, reject) => {
App.http.post('/mark-as-read', {scoreId: this.scoreId})
.then(response =>
{
// Check success
if (response.success)
{
this.unread = false;
this.updateCallbacks.forEach(callback => callback());
resolve();
}
else reject(response.data);
})
.catch(reject)
})
}
}
@@ -183,12 +248,22 @@ export default class Course
{
return this.cache.get('GradingPeriods', () =>
{
let timeCode = Navigation.instance.getSelectedTerm();
return (timeCode == -1 ? [0, 1, 2, 3] : [timeCode]).filter(term =>
return (this.rawSelectedTerm == -1 ? [0, 1, 2, 3] : [this.rawSelectedTerm]).filter(term =>
this.termAssignments[term].filter(a => a.graded).length != 0);
})
}
/**
* Get currently selected grading periods
*/
get allGradingPeriods(): number[]
{
return this.cache.get('AllGradingPeriods', () =>
{
return [0, 1, 2, 3].filter(term => this.termAssignments[term].filter(a => a.graded).length != 0);
})
}
/**
* Get assignments of the selected grading periods
*/
@@ -228,11 +303,33 @@ export default class Course
})
}
/**
* Get letter grade by term
*
* @param term
*/
letterGradeTerm(term: number): string
{
return this.cache.get('LetterGrade' + term, () =>
{
// Get scale
let scale = GPAUtils.findScale(this.numericGradeTerm(term));
// Scale not found
return scale == undefined ? '--' : scale.letter;
})
}
/**
* Get numeric grade
*/
get numericGrade()
{
return this.gradingPeriods
.map(term => this.numericGradeTerm(term)).reduce((p, v) => p + v)
/ this.gradingPeriods.length;
return this.cache.get('NumericGrade', () =>
{
return this.gradingPeriods.map(term => this.numericGradeTerm(term))
.reduce((p, v) => p + v) / this.gradingPeriods.length
})
}
/**
@@ -294,4 +391,28 @@ export default class Course
})
})
}
/**
* Get url hash code
*/
get urlHash(): string
{
return `course/${this.id}`
}
/**
* Get navigation index
*/
get urlIndex(): Index
{
return {hash: this.urlHash, title: this.name, identifier: 'course', info: {id: this.id}}
}
/**
* Selected term
*/
get rawSelectedTerm(): number
{
return Navigation.instance.getSelectedTerm()
}
}
+160
View File
@@ -0,0 +1,160 @@
import {FormatUtils} from '@/logic/utils/format-utils';
import pWaitFor from 'p-wait-for';
import App from '@/components/app/app';
export interface Index
{
hash: string
title?: string
identifier: string
info?: any
}
export default class NavController
{
// Current index
index: Index;
// Callback
updateCallback?: () => void;
constructor()
{
// Create history state listener
window.onpopstate = (e: any) =>
{
if (e.state)
{
// Restore previous tab
//console.log(`onPopState: Current: ${this.index.hash}, Previous: ${e.state.hash}`);
this.updateIndex(e.state, false);
}
};
// Initialize
this.init()
}
/**
* Initialize from last location
*/
private init()
{
if (window.location.hash == '#info') return;
// Check history from last session
if (window.history.state != undefined && window.history.state.hash != undefined)
{
// Last history exists
this.index = window.history.state;
return;
}
// Last history doesn't exist but hash url might exist
let hash = window.location.hash.replace('#', '');
// Check hash
if (hash == '')
{
// No location info in url, set page to overall
window.history.replaceState(this.convertIndex('overall'), '', '/#overall');
this.updateIndex('overall', false);
return;
}
// There is hash info in url
let split = hash.split('/');
// Not course -> don't know what to do with this url, so just refresh
if (split[0] != 'course')
{
this.initClear();
return;
}
// Is course -> Update index with placeholder title
this.updateIndex({hash: hash, title: `Loading...`, identifier: 'course', info: {id: +split[1]}}, false);
// Wait for courses to finish loading
pWaitFor(() => App.instance != undefined && App.instance.assignmentsReady).then(() =>
{
// Find course
let course = App.instance.courses.find(c => c.id == +split[1]);
// This person has no such course, refresh to overall
if (course == null)
{
this.initClear();
return;
}
window.history.replaceState(course.urlIndex, '', '/#' + course.urlHash);
this.updateIndex(course.urlIndex, false);
})
}
private initClear()
{
window.location.hash = '';
window.location.reload();
}
/**
* Update index
*
* @param index Hash and title | Hash only
* @param history Record in history or not (Default true)
*/
updateIndex(index: Index | string, history: boolean = true)
{
index = this.convertIndex(index);
// Call custom event
if (this.updateCallback != null) this.updateCallback();
// Record history or not
if (history)
{
//console.log(`history: Current: ${this.index.hash}, New: ${index.hash}`);
// Check url
let url = `/#${index.hash}`;
// Push history state
window.history.pushState(index, '', url);
}
// Update title
document.title = 'Veracross Analyzer - ' + index.title;
// Scroll to top
window.scrollTo(0, 0);
// Update selected index
this.index = index;
}
/**
* Check index conversion
*
* @param index Hash and title | Hash only
* @return Index Hash and title
*/
private convertIndex(index: Index | string): Index
{
// Convert index format if it is hash only
if (typeof index == 'string') index = {hash: index, identifier: index};
if (index.title == null) index.title = FormatUtils.toTitleCase(index.hash);
return index;
}
get id(): string
{
return this.index.identifier
}
get info(): any
{
return this.index.info
}
}
-12
View File
@@ -1,4 +1,3 @@
import Course from '@/logic/course';
import Navigation from '@/components/navigation/navigation';
import Constants from '@/constants';
@@ -20,17 +19,6 @@ UNKNOWN_COURSE_LIST.set('Painting', LEVEL_CP);
export class CourseUtils
{
/**
* Format course to tab index string
*
* @param course Course object
* @return string Tab index
*/
public static formatTabIndex(course: Course): string
{
return `course/${course.id}/${course.name.toLowerCase().split(' ').join('-')}`;
}
/**
* Detect course level based on course name
*
+4
View File
@@ -1,4 +1,5 @@
import Constants from '@/constants';
import App from '@/components/app/app';
export default class GraphUtils
{
@@ -54,6 +55,9 @@ export default class GraphUtils
*/
static getGradeMarkAreas(opacity: number)
{
// TODO: Auto update after switching dark mode (possibly by refreshing)
opacity = App.instance.darkMode ? 0.1 : opacity;
return {
silent: true,
data:
@@ -57,6 +57,13 @@
text-align: right;
float: right;
// Status / Problems
span.status
{
margin-right: 8px;
}
// Percentage score
span.percent
{
font-style: italic;
@@ -18,6 +18,9 @@
</el-col>
<el-col :span="6" class="grade">
<span v-if="assignment.problem" class="status entry-box" :style="{color: assignment.problemColor}">
{{assignment.problem}}
</span>
<span class="percent entry-box">
{{(assignment.score / assignment.scoreMax * 100).toFixed(1)}}
<span class="symbol">%</span>
@@ -1,27 +1,30 @@
// Main card content
.course-card-content.main
{
padding: 0 20px 0 20px;
height: 90px;
// Main color
background: white;
// Alignment
display: block;
padding: 20px;
height: 50px;
}
.course-col-name
#block-info
{
// Align left
text-align: left;
float: left;
.course-name
#name
{
overflow: hidden;
font-size: 22px;
color: var(--main);
}
.course-teacher
#teacher
{
font-size: 12px;
color: #999999;
@@ -29,7 +32,7 @@
}
}
.course-col-grade
#block-grade
{
// Align right
text-align: right;
@@ -37,17 +40,18 @@
// Adjust position
margin-top: -2px;
margin-left: 10px;
.course-grade
#grade
{
font-size: 21px;
}
.course-updates
#updates
{
font-size: 14px;
.unread-number
#unread-number
{
display: inline-block;
width: 20px;
@@ -60,33 +64,53 @@
margin-right: 3px;
}
.unread-text
#unread-text
{
font-style: italic;
}
}
.course-updates.unread
#updates.unread
{
.unread-number
#unread-number
{
background: var(--unread);
color: white;
}
.unread-text
#unread-text
{
color: var(--unread);
}
}
.course-updates.none
#updates.none
{
color: #999999;
.unread-number
#unread-number
{
background: #eeeeee;
}
}
}
#block-term-grades
{
// Align right
width: auto;
float: right;
margin-right: 10px;
color: gray;
#term, #term-numeric
{
font-size: 11px;
color: #b3b3b3;
}
#term-letter
{
font-size: 14px;
}
}
@@ -1,33 +1,34 @@
<template>
<div id="course-head" class="course-card-content main vertical-center" @click="redirect">
<el-row>
<el-col :span="12" class="course-col-name">
<div v-if="clickable" class="course-name clickable" @click="redirect">{{course.name}}</div>
<div v-if="!clickable" class="course-name">{{course.name}}</div>
<div class="course-teacher">{{course.teacherName}}</div>
</el-col>
<el-col :span="12" class="course-col-grade">
<div class="course-grade">
<span class="letter">{{course.letterGrade}} </span>
<span class="numeric">{{course.numericGrade === undefined ? '--' : course.numericGrade.toFixed(2)}}</span>
<span class="percent" v-if="course.numericGrade !== undefined">%</span>
</div>
<div class="course-updates" @click="redirect" :class="unread === 0 ? 'none' : 'unread'">
<span class="unread-number">{{unread}}</span>
<span class="unread-text" :class="clickable ? 'clickable' : ''">
new update{{unread >= 2 ? 's' : ''}}
</span>
</div>
</el-col>
</el-row>
<div id="course-head" class="course-card-content main vertical-center"
:class="clickable ? 'clickable' : ''" @click="redirect">
<div id="block-info">
<div id="name">{{course.name}}</div>
<div id="teacher">{{course.teacherName}}</div>
</div>
<div id="block-grade">
<div id="grade">
<span id="letter">{{course.letterGrade}} </span>
<span id="numeric">{{course.numericGrade === undefined ? '--' : course.numericGrade.toFixed(2)}}</span>
<span id="percent" v-if="course.numericGrade !== undefined">%</span>
</div>
<div id="updates" @click="redirect" :class="unread === 0 ? 'none' : 'unread'">
<span id="unread-number">{{unread}}</span>
<span id="unread-text" :class="clickable ? 'clickable' : ''">new update{{unread >= 2 ? 's' : ''}}</span>
</div>
</div>
<div id="block-term-grades" v-if="course.rawSelectedTerm === -1"
v-for="term in course.allGradingPeriods.slice().reverse()">
<div id="term">Term {{term + 1}}</div>
<div id="term-letter">{{course.letterGradeTerm(term)}}</div>
<div id="term-numeric">{{course.numericGradeTerm(term).toFixed(1)}}%</div>
</div>
</div>
</template>
<script lang="ts">
import {Component, Prop, Vue} from 'vue-property-decorator';
import Course from '@/logic/course';
import {CourseUtils} from '@/logic/utils/course-utils';
import Navigation from '@/components/navigation/navigation';
import App from '@/components/app/app';
@Component
export default class CourseHead extends Vue
@@ -44,9 +45,9 @@
redirect()
{
if (!this.clickable) return;
Navigation.instance.updateIndex(CourseUtils.formatTabIndex(this.course));
App.instance.nav.updateIndex(this.course.urlIndex);
}
}
</script>
<style src="./course-head.scss" lang="scss"/>
<style src="./course-head.scss" lang="scss" scoped/>
@@ -1,51 +0,0 @@
import {Component, Prop, Vue} from 'vue-property-decorator';
import App from '@/components/app/app';
import AssignmentEntry from '@/pages/overall/overall-course/assignment-entry/assignment-entry.vue';
import CourseHead from '@/pages/overall/overall-course/course-head/course-head.vue';
import Course, {Assignment} from '@/logic/course';
@Component({
components: {UnreadEntry: AssignmentEntry, CourseHead}
})
export default class OverallCourse extends Vue
{
@Prop({required: true}) course: Course;
private unread: number = -1;
private unreadAssignments: Assignment[] = [];
/**
* Count the number of unread assignments with cache
*/
countUnread(): number
{
if (this.unread == -1)
{
this.unreadAssignments = this.course.assignments.filter(a => a.unread);
return this.unread = this.unreadAssignments.length;
}
else return this.unread;
}
/**
* Mark an assignment as read
*/
markAsRead(assignment: Assignment)
{
App.http.post('/mark-as-read', {scoreId: assignment.scoreId})
.then(response =>
{
// Check success
if (response.success)
{
this.unreadAssignments = this.unreadAssignments.filter(a => a != assignment);
this.unread = this.unreadAssignments.length;
}
else
{
// Show error message TODO: Show it properly
alert(response.data)
}
})
}
}
@@ -1,19 +1,47 @@
<template>
<div id="overall-course">
<el-card class="course-card">
<course-head :clickable="true" :course="course" :unread="countUnread()"/>
<course-head :clickable="true" :course="course" :unread="unread()"/>
<div class="course-card-content expand"
v-if="countUnread() !== 0">
<unread-entry v-for="assignment in unreadAssignments"
v-if="unread() !== 0">
<assignment-entry v-for="assignment in unreadAssignments()"
:assignment="assignment"
:key="assignment.id"
unread="true"
v-on:mark-as-read="markAsRead">
</unread-entry>
v-on:mark-as-read="assignment.markAsRead()">
</assignment-entry>
</div>
</el-card>
</div>
</template>
<script src="./overall-course.ts" lang="ts"></script>
<script lang="ts">
import {Component, Prop, Vue} from 'vue-property-decorator';
import AssignmentEntry from '@/pages/overall/overall-course/assignment-entry/assignment-entry.vue';
import CourseHead from '@/pages/overall/overall-course/course-head/course-head.vue';
import Course, {Assignment} from '@/logic/course';
@Component({
components: {AssignmentEntry, CourseHead}
})
export default class OverallCourse extends Vue
{
@Prop({required: true}) course: Course;
mounted()
{
this.unreadAssignments().forEach(a => a.addCallback(() => this.$forceUpdate()));
}
unreadAssignments(): Assignment[]
{
return this.course.assignments.filter(a => a.unread);
}
unread(): number
{
return this.unreadAssignments().length;
}
}
</script>
<style src="./overall-course.scss" lang="scss" scoped/>
+7
View File
@@ -51,3 +51,10 @@
margin-right: 20px;
min-width: 170px;
}
.dialog-checkbox
{
display: block;
margin-top: 20px;
margin-bottom: -20px;
}
+79 -3
View File
@@ -1,5 +1,19 @@
<template>
<div id="overall">
<el-progress v-if="started" :text-inside="true" :percentage="progress()"
:stroke-width="20" status="success" style="margin: 0 20px"/>
<el-dialog title="Notice" :visible.sync="clearUnreadPrompt"
width="30%" style="word-break: unset;">
<span>You have too many new grade notifications. Clear them now?</span>
<img src="./too-many-unread.png" alt=""/>
<el-checkbox class="dialog-checkbox" v-model="dontAskAgain">Don't Ask Again</el-checkbox>
<span slot="footer" class="dialog-footer">
<el-button @click="clearUnread(false)" style="float: left">Nope</el-button>
<el-button type="primary" @click="clearUnread(true)">Sure!</el-button>
</span>
</el-dialog>
<el-row v-if="getGPA().gpa !== -1">
<el-col :span="4" class="overall-span">
<el-card class="large gpa-card vertical-center" body-style="padding: 0">
@@ -42,8 +56,8 @@
import {Component, Prop, Vue} from 'vue-property-decorator';
import OverallLine from '@/pages/overall/overall-line/overall-line';
import OverallBar from '@/pages/overall/overall-bar/overall-bar';
import OverallCourse from '@/pages/overall/overall-course/overall-course';
import Course from '@/logic/course';
import OverallCourse from '@/pages/overall/overall-course/overall-course.vue';
import Course, {Assignment} from '@/logic/course';
import {GPAUtils} from '@/logic/utils/gpa-utils';
@Component({
@@ -57,10 +71,72 @@
* This function is called to get gpa since I can't import another
* class in the Vue file.
*/
public getGPA()
getGPA()
{
return GPAUtils.getGPA(this.courses);
}
// For clear unread prompt
unread: Assignment[];
clearUnreadPrompt = false;
dontAskAgain = false;
started = false;
/**
* Mark as read progress
*/
progress()
{
return +(this.unread.filter(a => !a.unread).length / this.unread.length * 100).toFixed(1);
}
/**
* On page load - check if the user has too many notifications
*/
mounted()
{
// Check unread
if (!this.$cookies.isKey('va.ignore-unread'))
{
// Count unread
this.unread = this.courses.flatMap(c => c.assignments.filter(a => a.unread));
// Prompt clear
if (this.unread.length > 15)
{
this.clearUnreadPrompt = true;
}
}
}
/**
* Clear unread
*
* @param confirmed
*/
clearUnread(confirmed: boolean)
{
// Hide prompt
this.clearUnreadPrompt = false;
// Not confirmed, do nothing
if (!confirmed)
{
if (!this.dontAskAgain) return;
// Don't ask again
this.$cookies.set('va.ignore-unread', true);
}
// Clear unread
this.started = true;
this.unread.forEach((a, i) =>
{
// Delay: 100ms per assignment
// I don't want my server to explode lol
setTimeout(() => a.markAsRead().then(() => this.$forceUpdate()), 100 * i);
});
}
}
</script>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

+45
View File
@@ -0,0 +1,45 @@
<template>
<div id="info">
<div id="top">
<div id="title">Veracross Analyzer for SJP</div>
<div id="subtitle">Know your grades better.</div>
</div>
</div>
</template>
<script lang="ts">
import {Component, Vue} from 'vue-property-decorator';
@Component
export default class Info extends Vue
{
}
</script>
<style lang="scss" scoped>
#top
{
padding: 40vh 0;
// Center and scale the image nicely
background-position: center;
background-repeat: no-repeat;
background-size: cover;
text-align: right;
#title
{
font-size: 30px;
margin-right: 10vw;
color: white;
}
#subtitle
{
margin-right: 10vw;
color: white;
}
}
</style>
+3 -1
View File
@@ -2,7 +2,9 @@
"defaultSeverity": "warning",
"linterOptions": {
"exclude": [
"node_modules/**"
"node_modules/**",
"*.json",
"**/*.json"
]
},
"rules": {