Compare commits

...

113 Commits

Author SHA1 Message Date
Hykilpikonna fa3fc44526 [U] Update version number 2019-09-23 19:41:49 -04:00
Hykilpikonna 352fd58fb5 [O] Show the graph with minimum y axis at 70 2019-09-23 19:33:27 -04:00
Hykilpikonna f043d77e5e [M] Move graph title to the graph component 2019-09-23 19:32:37 -04:00
Hykilpikonna 9e88f3efe1 [U] Ignore 0 grades in calculations 2019-09-23 19:32:21 -04:00
Hykilpikonna cadd328958 [O] Separate settings 2019-09-20 19:27:10 -04:00
Hykilpikonna 8c4759d497 [-] Remove unnecessary sample chart data 2019-09-20 19:26:23 -04:00
Hykilpikonna ac8f488ab4 [O] Refresh page after signing out 2019-09-20 19:21:07 -04:00
Hykilpikonna df639a360c [+] Implement actual sign out function 2019-09-20 19:10:37 -04:00
Hykilpikonna 24328508c0 [+] Bind sign out event 2019-09-20 19:10:23 -04:00
Hykilpikonna ebd4849703 [O] Optimize sign out button 2019-09-20 19:10:12 -04:00
Hykilpikonna 80d72bb5c0 Merge branch 'styling' into feature 2019-09-20 18:52:56 -04:00
Hykilpikonna 204e7dc04a [F] Move signOut() to the right place 2019-09-20 18:50:13 -04:00
Hykilpikonna cef0b31dba [O] Only display score when it is complete 2019-09-19 19:11:54 -04:00
Hykilpikonna 22ba3acab0 [F] Fix key word typo 2019-09-19 19:11:00 -04:00
Hykilpikonna 88bdcd8cf8 [O] Optimize code length 2019-09-19 18:58:59 -04:00
Hykilpikonna 8559a93b9c [+] Filter courses to a variable 2019-09-19 18:58:00 -04:00
Hykilpikonna f8db85dca3 [F] Fix getter signature problem 2019-09-19 18:57:47 -04:00
Hykilpikonna b4bd24d884 [+] Filter courses without assignments 2019-09-19 18:55:55 -04:00
Hykilpikonna f6d30a750a [+] Filter courses without levels 2019-09-19 18:55:44 -04:00
Hykilpikonna 988d6f5463 [+] Create method to filter out courses 2019-09-19 18:55:34 -04:00
Hykilpikonna 608132ad6c [O] Reduce code length 2019-09-19 18:49:00 -04:00
Hykilpikonna 6cffb745a7 [O] Specify type with ts-ignore 2019-09-19 18:48:17 -04:00
Hykilpikonna 82212cf10c [O] Fix " warning 2019-09-19 18:46:40 -04:00
Hykilpikonna 9656b3184b [O] Optimize nav bar shadow 2019-09-18 23:25:50 -04:00
Hykilpikonna 08b665a1fa [+] Call custom event to sign out 2019-09-18 23:25:25 -04:00
Hykilpikonna 500e13ef9d [+] Add onclick to sign-out 2019-09-18 23:25:12 -04:00
Hykilpikonna b95220ba1a [+] Add sign out button 2019-09-18 23:23:24 -04:00
Hykilpikonna b8dfb8f732 [F] Fix negative index problem 2019-09-18 23:07:43 -04:00
Hykilpikonna 002aa84444 [O] Ignore non-active scores 2019-09-18 21:52:10 -04:00
Hykilpikonna e7c513695d [O] Disable host check 2019-09-18 21:51:27 -04:00
Hykilpikonna e51dbd2c5b [O] Make GPA time font size smaller 2019-09-18 21:50:44 -04:00
Hykilpikonna 9157232d45 [O] Make GPA font size larger 2019-09-18 21:50:32 -04:00
Hykilpikonna 17ef8f4380 [O] Vertically center 2019-09-18 21:50:19 -04:00
Hykilpikonna c31dbf0e50 [O] Make GPA header font smaller 2019-09-18 21:50:05 -04:00
Hykilpikonna 9c2c1c3195 [O] Optimize card margins 2019-09-18 08:27:59 -04:00
Hykilpikonna 820c3c1148 [O] Optimize layout 2019-09-18 08:27:27 -04:00
Hykilpikonna 6efb832212 Merge branch 'feature' into styling 2019-09-18 08:26:07 -04:00
Hykilpikonna c885137ed7 [O] Fix floating point precision errors 2019-09-16 22:47:13 -04:00
Hykilpikonna bb4b34722f [+] Display GPA string 2019-09-16 22:24:56 -04:00
Hykilpikonna 2beca45e38 [+] Encapsulate method to get GPA string 2019-09-16 22:24:47 -04:00
Hykilpikonna cbec0add3b Revert "[T] Test GPA calculations"
This reverts commit 0ebce968b9.
2019-09-16 22:24:10 -04:00
Hykilpikonna 0ebce968b9 [T] Test GPA calculations 2019-09-16 22:24:06 -04:00
Hykilpikonna 4095041925 [+] Add status field in course model 2019-09-16 22:23:52 -04:00
Hykilpikonna 0e618ceb13 [+] Create cards in overall 2019-09-07 17:04:04 -04:00
Hykilpikonna 042e72abb6 [O] Reduce redundant code 2019-09-07 16:48:57 -04:00
Hykilpikonna 517235982b [F] Fix return type void 2019-09-07 16:48:46 -04:00
Hykilpikonna 6e7041edcd [+] Create method to calculate GP for a course 2019-09-07 16:46:46 -04:00
Hykilpikonna 67cf33b48c [F] Remove scaleUp if score is 0 2019-09-07 16:44:34 -04:00
Hykilpikonna 4c822fd207 [+] Add borders to navigation 2019-09-07 16:32:05 -04:00
Hykilpikonna 68afbb8c76 [O] Optimize accuracy detection 2019-09-07 16:14:42 -04:00
Hykilpikonna 5da0e89e08 [+] Return boolean indicating accurate or not when calculating gpa 2019-09-07 16:12:32 -04:00
Hykilpikonna b2db05d5e2 [O] Remove course that does not have level 2019-09-07 16:04:50 -04:00
Hykilpikonna 3cb74083a7 [+] Create method to calculate GPA 2019-09-07 16:03:57 -04:00
Hykilpikonna d3072ccaf6 [+] Create keyword constants in GPAUtils 2019-09-07 16:03:34 -04:00
Hykilpikonna d566b53c22 [+] Create letterGrade and numericGrade fields 2019-09-07 15:49:11 -04:00
Hykilpikonna 7bad961f70 [+] Add field level and scaleUp 2019-09-07 14:57:37 -04:00
Hykilpikonna 13e307f8d2 [+] Create GPA scale 2019-09-07 14:52:54 -04:00
Hykilpikonna 448e699cd3 [O] Optimize login http fetch 2019-09-07 14:42:22 -04:00
Hykilpikonna 1ca32b5ebd [O] Optimize loadAssignments() 2019-09-07 14:07:49 -04:00
Hykilpikonna 5ac3183ec1 [F] Fix body isn't in json format problem 2019-09-07 14:04:59 -04:00
Hykilpikonna a7384753c8 [O] Optimize loadCourses request 2019-09-07 14:04:38 -04:00
Hykilpikonna 393fc1cc71 [+] Create http field in App.ts 2019-09-07 14:04:23 -04:00
Hykilpikonna 06c265159b [F] Fix constructor 2019-09-07 13:59:05 -04:00
Hykilpikonna 53a0884d0b [+] Create http-utils class 2019-09-07 13:56:43 -04:00
Hykilpikonna 7b0f11a1f4 [F] Fix assignments null 2019-09-07 13:01:05 -04:00
Hykilpikonna 4dc5966e51 [+] Load assignments 2019-09-07 12:30:27 -04:00
Hykilpikonna be0a657ba3 [+] Load courses 2019-09-07 12:30:19 -04:00
Hykilpikonna 3287c14fa3 [+] Store token in a field 2019-09-07 12:15:13 -04:00
Hykilpikonna 7af20f806b [O] Move cookies detection to login class 2019-09-07 12:14:57 -04:00
Hykilpikonna bd8d7fd113 [U] Update keywords (from "token" to "data") 2019-09-07 11:48:28 -04:00
Hykilpikonna 76cf8c4c53 [+] Check cookies on initt 2019-09-07 10:11:31 -04:00
Hykilpikonna eaa1609b77 [+] Load data after login 2019-09-07 10:11:18 -04:00
Hykilpikonna 1324afe978 [+] Create method to load data after login 2019-09-07 10:11:08 -04:00
Hykilpikonna e86d2fd4f5 [O] Create a better ascii art 2019-09-07 10:08:49 -04:00
Hykilpikonna cf34db2c61 [+] Show splash on create 2019-09-07 10:07:04 -04:00
Hykilpikonna 9038a73678 [+] Save cookies on login 2019-09-07 10:06:19 -04:00
Hykilpikonna 446ed686bd [+] Create splash ascii art 2019-09-07 10:05:53 -04:00
Hykilpikonna 4782870d94 [+] Add vue-cookies to Vue.use() 2019-09-07 09:21:57 -04:00
Hykilpikonna 3ce21623d8 [+] Import vue-cookies 2019-09-07 09:21:29 -04:00
Hykilpikonna 2338e4f6af [+] Show error message on error 2019-09-07 09:14:13 -04:00
Hykilpikonna 38089c74b5 [F] Fix duplicate "/api/" in path 2019-09-07 09:13:11 -04:00
Hykilpikonna 8860c88b1a [F] Fix HTTP GET can't have body 2019-09-07 09:09:50 -04:00
Hykilpikonna 75f9dc9849 [U] Remove testing error text 2019-09-07 08:31:32 -04:00
Hykilpikonna 805ffaa50e [+] Check the success flag in response 2019-09-07 08:28:54 -04:00
Hykilpikonna c7d16a00e6 [+] Send username and password to api in body 2019-09-07 08:28:24 -04:00
Hykilpikonna 67ec2b85b2 [+] Create error message div 2019-09-07 08:24:16 -04:00
Hykilpikonna 9a752305e3 [O] Fix error message styling 2019-09-07 08:23:34 -04:00
Hykilpikonna c95a5b343e Merge branch 'feature' into styling 2019-09-07 08:10:10 -04:00
Hykilpikonna 5029555c21 [U] Update api url 2019-09-07 08:02:50 -04:00
Hykilpikonna 8eb2080f14 [+] Add error class on error 2019-09-07 08:02:22 -04:00
Hykilpikonna 0296b2151a [+] Create error message field 2019-09-07 08:01:49 -04:00
Hykilpikonna 3575db8182 [+] Create css class for input error 2019-09-07 08:01:36 -04:00
Hykilpikonna 0025ec9213 [O] Optimize graph creation 2019-09-06 19:43:36 -04:00
Hykilpikonna 489328c624 [+] Use the chart data to show chart 2019-08-27 20:55:33 +08:00
Hykilpikonna 36a211e186 [+] Create method to convert chart data 2019-08-27 20:55:06 +08:00
Hykilpikonna d7c4c87959 [+] Create courses prop. and pass it from overall.vue 2019-08-25 01:57:11 +08:00
Hykilpikonna 3cd9db65ec [+] Create courses prop. and pass it from app.vue 2019-08-25 01:56:52 +08:00
Hykilpikonna e03798af13 Merge branch 'optimization' into feature 2019-08-24 21:21:38 +08:00
Hykilpikonna 1fd4e89f84 [+] Add proper documentation to the fields 2019-08-24 21:21:04 +08:00
Hykilpikonna c05b799334 [O] Show overall only when assignments are ready. 2019-08-24 21:18:51 +08:00
Hykilpikonna b9f0316f76 [+] Wait for isAssignmentReady() 2019-08-24 21:16:42 +08:00
Hykilpikonna 2b3cbb4061 [+] Create method to check if the courses are ready 2019-08-24 21:16:13 +08:00
Hykilpikonna 0653deba64 [+] Import pWaitFor dependency 2019-08-24 21:08:06 +08:00
Hykilpikonna 67a9417f2b [+] Create assignmentsReady field 2019-08-24 21:05:41 +08:00
Hykilpikonna ca49223432 [+] Get assignments for all the courses 2019-08-24 21:05:23 +08:00
Hykilpikonna 6d0d23d0dd [O] Specify type for the onLogin method 2019-08-24 20:39:43 +08:00
Hykilpikonna 6c9061d4d9 [O] Specify type for the courses field 2019-08-24 20:39:29 +08:00
Hykilpikonna 56b9be01ba [+] Create course interface 2019-08-24 20:39:14 +08:00
Hykilpikonna c8935c41cb [M] Move Grade interface to App.ts 2019-08-24 20:39:00 +08:00
Hykilpikonna 1d96dcc0a8 [+] Create method to filter assignments 2019-08-22 21:58:16 +08:00
Hykilpikonna 811de8f5e2 [+] Create a grade interfacce 2019-08-22 21:58:01 +08:00
Hykilpikonna ec4196088a [+] Create json utils class 2019-08-22 21:57:48 +08:00
Hykilpikonna 863fdcb50e Merge branch 'bug-fixes' into feature 2019-08-22 21:05:51 +08:00
21 changed files with 686 additions and 40 deletions
+22 -2
View File
@@ -7546,8 +7546,7 @@
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npm.taobao.org/p-finally/download/p-finally-1.0.0.tgz",
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
"dev": true
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
},
"p-is-promise": {
"version": "2.1.0",
@@ -7588,12 +7587,28 @@
"retry": "^0.12.0"
}
},
"p-timeout": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.1.0.tgz",
"integrity": "sha512-C27DYI+tCroT8J8cTEyySGydl2B7FlxrGNF5/wmMbl1V+jeehUCzEE/BVgzRebdm2K3ZitKOKx8YbdFumDyYmw==",
"requires": {
"p-finally": "^1.0.0"
}
},
"p-try": {
"version": "1.0.0",
"resolved": "https://registry.npm.taobao.org/p-try/download/p-try-1.0.0.tgz",
"integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
"dev": true
},
"p-wait-for": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-wait-for/-/p-wait-for-3.1.0.tgz",
"integrity": "sha512-0Uy19uhxbssHelu9ynDMcON6BmMk6pH8551CvxROhiz3Vx+yC4RqxjyIDk2V4ll0g9177RKT++PK4zcV58uJ7A==",
"requires": {
"p-timeout": "^3.0.0"
}
},
"pako": {
"version": "1.0.10",
"resolved": "https://registry.npm.taobao.org/pako/download/pako-1.0.10.tgz",
@@ -10833,6 +10848,11 @@
"resolved": "https://registry.npm.taobao.org/vue-class-component/download/vue-class-component-7.1.0.tgz",
"integrity": "sha1-sz78sQ4XI21oT3Cx6W8ZRux5Poc="
},
"vue-cookies": {
"version": "1.5.13",
"resolved": "https://registry.npmjs.org/vue-cookies/-/vue-cookies-1.5.13.tgz",
"integrity": "sha512-8pjpXnvbNWx1Lft0t3MJnW+ylv0Wa2Tb6Ch617u/pQah3+SfXUZZdkh3EL3bSpe/Sw2Wdw3+DhycgQsKANSxXg=="
},
"vue-hot-reload-api": {
"version": "2.3.3",
"resolved": "https://registry.npm.taobao.org/vue-hot-reload-api/download/vue-hot-reload-api-2.3.3.tgz",
+2
View File
@@ -11,9 +11,11 @@
"core-js": "^2.6.5",
"echarts": "^4.2.1",
"element-ui": "^2.11.1",
"p-wait-for": "^3.1.0",
"v-charts": "^1.19.0",
"vue": "^2.6.10",
"vue-class-component": "^7.0.2",
"vue-cookies": "^1.5.13",
"vue-property-decorator": "^8.1.0"
},
"devDependencies": {
+161 -8
View File
@@ -2,33 +2,174 @@ import {Component, Vue} from 'vue-property-decorator';
import Login from '@/components/login/login';
import Navigation from '@/components/navigation/navigation';
import Overall from '@/pages/overall/overall';
import Constants from '@/constants';
import JsonUtils from '@/utils/json-utils';
import pWaitFor from 'p-wait-for';
import {HttpUtils} from '@/utils/http-utils';
/**
* Objects of this interface represent assignment grades.
*/
export interface Grade
{
type: string,
description: string,
date: string,
complete: string,
include: boolean,
display: boolean,
scoreMax: number,
score: number
}
/**
* A course
*/
export interface Course
{
assignmentsId: number,
id: number,
name: string,
teacherName: string,
status: string,
letterGrade?: string,
numericGrade?: number,
level: string,
scaleUp: number,
assignments: Grade[]
}
@Component({
components: {Login, Navigation, Overall},
})
export default class App extends Vue
{
// Is the login panel shown
public showLogin: boolean = true;
public courses = null;
// List of course that the student takes
public courses: Course[] = [];
public selectedTab: string = "overall";
// Currently selected tab
public selectedTab: string = 'overall';
// Are the course assignments loaded from the server.
public assignmentsReady: boolean = false;
// Token
public token: string = '';
// Http Client
public http: HttpUtils = new HttpUtils('');
/**
* This is called when the instance is created.
*/
public created()
{
// Show splash
console.log(Constants.SPLASH);
}
/**
* This is called when the user logs in.
*
* @param courses Courses Json
* @param token Authorization token
*/
public onLogin(courses: any)
public onLogin(token: string)
{
// Hide login bar
this.showLogin = false;
// Assign courses
this.courses = courses;
// Store token
this.token = token;
// Debug output TODO: Remove this
console.log(courses);
// Assign token to http client
this.http.token = token;
// Load data
this.loadCoursesAfterLogin();
}
/**
* Load courses data after login.
*/
public loadCoursesAfterLogin()
{
this.http.post('/courses', {}).then(response =>
{
// Check success
if (response.success)
{
// Save courses
this.courses = response.data;
// Load assignments
this.loadAssignments();
}
else
{
// Show error message TODO: Show it properly
alert(response.data);
}
})
.catch(alert);
}
/**
* Load the assignments of the courses
*
* @param courses Courses Json
*/
public loadAssignments()
{
// Get assignments for all the courses
this.courses.forEach(course =>
{
// Send request to get assignments
this.http.post('/assignments', {id: course.assignmentsId}).then(response =>
{
// Check success
if (response.success)
{
// Load assignments
// Parse json and filter it
course.assignments = JsonUtils.filterAssignments(response.data);
}
else
{
// Show error message TODO: Show it properly
alert(response.data);
}
})
.catch(alert);
});
// Wait for assignments to be ready.
pWaitFor(() => this.isAssignmentsReady()).then(() =>
{
// When the assignments are ready
this.assignmentsReady = true;
});
}
/**
* Are assignments ready or not
*
* @returns boolean Ready or not
*/
private isAssignmentsReady(): boolean
{
for (const course of this.courses)
{
if (course.assignments == null) return false;
}
return true;
}
/**
@@ -44,4 +185,16 @@ export default class App extends Vue
// Update selected tab
this.selectedTab = tab;
}
/**
* Sign out
*/
public signOut()
{
// Clear all cookies
this.$cookies.keys().forEach(key => this.$cookies.remove(key));
// Refresh
window.location.reload();
}
}
+6 -3
View File
@@ -1,10 +1,13 @@
<template>
<div id="app">
<login v-if="showLogin" v-on:login:courses="onLogin"></login>
<navigation :courses="courses" v-on:navigation:select="onNavigate"></navigation>
<login v-if="showLogin" v-on:login:token="onLogin" :http="http"></login>
<navigation :courses="courses"
v-on:sign-out="signOut()"
v-on:navigation:select="onNavigate">
</navigation>
<div id="app-content">
<overall v-if="selectedTab === 'overall'"></overall>
<overall :courses="courses" v-if="selectedTab === 'overall' && assignmentsReady"></overall>
</div>
</div>
</template>
+25
View File
@@ -72,3 +72,28 @@
width: 100%;
}
}
// Error
.input-error
{
.el-input__inner
{
color: #ff3a3a6b !important;
border-color: #ff3a3a6b !important;
background-color: #ffdddd3b !important;
}
.el-input__inner:focus
{
background-color: white !important;
}
}
// Fix error message
.el-form-item__error.custom
{
padding-top: 0;
position: relative;
top: auto;
float: left;
}
+38 -8
View File
@@ -1,5 +1,6 @@
import {Component, Vue} from 'vue-property-decorator';
import {Component, Prop, Vue} from 'vue-property-decorator';
import Constants from '@/constants';
import {HttpUtils} from '@/utils/http-utils';
/**
* This component handles user login, and obtains data from the server.
@@ -13,6 +14,23 @@ export default class Login extends Vue
public password: any = '';
public loading: boolean = false;
public error: String = '';
@Prop()
public http?: HttpUtils;
/**
* This is called when the instance is created.
*/
public created()
{
// Check login cookies
if (this.$cookies.isKey('va.token'))
{
// Already contains valid token / TODO: Validate
this.$emit('login:token', this.$cookies.get('va.token'));
}
}
/**
* On click, sends username and password to the server.
@@ -22,15 +40,27 @@ export default class Login extends Vue
// Make login button loading
this.loading = true;
// Fetch request TODO: Add username and password when the https server is ready.
fetch(`${Constants.API_URL}/veracross/courses`).then(res =>
// Fetch request
(<HttpUtils> this.http).post('/login', {username: this.username, password: this.password})
.then(response =>
{
// Get response body text
res.text().then(text =>
// Check success
if (response.success)
{
// Call custom event with courses info
this.$emit('login:courses', JSON.parse(text));
})
// Save token to cookies
this.$cookies.set('va.token', response.data, '7d');
// Call custom event with token
this.$emit('login:token', response.data);
}
else
{
// Show error message
this.error = response.data;
// Allow the user to retry
this.loading = false;
}
})
.catch(err =>
{
+15 -2
View File
@@ -3,9 +3,22 @@
<div class="login-vertical-center">
<div class="login-panel">
<img alt="Vue logo" src="../../assets/logo.png">
<h1>Veracross Analyzer</h1>
<el-input v-model="username" placeholder="School Username"></el-input>
<el-input v-model="password" placeholder="Veracross Password" show-password=""></el-input>
<el-input v-model="username"
placeholder="School Username"
:class="{'input-error': error !== ''}">
</el-input>
<el-input v-model="password"
placeholder="Veracross Password"
show-password=""
:class="{'input-error': error !== ''}">
</el-input>
<div class="el-form-item__error custom">{{error}}</div>
<el-button plain type="primary" @click="onLoginClick" :loading="loading">Login</el-button>
</div>
</div>
+12
View File
@@ -1,5 +1,17 @@
.el-menu.centered li
{
display: inline-block !important;
float: none !important;
}
// Borders
#navigation
{
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
ul
{
border-bottom-width: 0;
}
}
+9
View File
@@ -27,4 +27,13 @@ export default class Navigation extends Vue
// Call custom event
this.$emit('navigation:select', this.activeIndex);
}
/**
* This function is called when the sign out button is clicked.
*/
public signOut()
{
// Call custom event
this.$emit('sign-out');
}
}
+1
View File
@@ -11,6 +11,7 @@
:key="course.name">{{course.name}}</el-menu-item>
</el-submenu>
<el-button @click="signOut" id="sign-out-button" type="text">Sign Out</el-button>
</el-menu>
<div class="line"></div>
</div>
+8 -1
View File
@@ -7,5 +7,12 @@ export default class Constants
* Base url for api access
* TODO: Use https for actual usage
*/
public static API_URL: string = 'http://cn2.hydev.org:24021/api';
public static API_URL: string = 'https://va.hydev.org/api';
public static SPLASH: string =
'. , ,---. | \n' +
'| |. , |---|,---.,---.| , .,---,,---.,---.\n' +
' \\ / >< | || |,---|| | | .-\' |---\'| \n' +
' `\' \' ` ` \'` \'`---^`---\'`---|\'---\'`---\'` \n' +
' v0.1.1.0 `---\' '
}
+4
View File
@@ -3,6 +3,7 @@ import ElementUI from 'element-ui';
const VCharts = require('v-charts');
import App from './components/app/app.vue';
import VueCookies from 'vue-cookies';
Vue.config.productionTip = false;
@@ -12,6 +13,9 @@ Vue.use(ElementUI, {locale: 'en-us'});
// Use VCharts
Vue.use(VCharts);
// Use Cookies
Vue.use(VueCookies);
// Init app
new Vue({
render: (h) => h(App),
@@ -1,19 +1,21 @@
import {Component, Vue} from 'vue-property-decorator';
import {Component, Prop, Vue} from 'vue-property-decorator';
import {Course} from '@/components/app/app';
@Component({
})
export default class GraphOverall extends Vue
{
public chartData =
@Prop({required: true}) chart: any;
private settings =
{
columns: ['日期', '访问用户', '下单用户', '下单率'],
rows: [
{ '日期': '1/1', '访问用户': 1393, '下单用户': 1093, '下单率': 0.32 },
{ '日期': '1/2', '访问用户': 3530, '下单用户': 3230, '下单率': 0.26 },
{ '日期': '1/3', '访问用户': 2923, '下单用户': 2623, '下单率': 0.76 },
{ '日期': '1/4', '访问用户': 1723, '下单用户': 1423, '下单率': 0.49 },
{ '日期': '1/5', '访问用户': 3792, '下单用户': 3492, '下单率': 0.323 },
{ '日期': '1/6', '访问用户': 4593, '下单用户': 4293, '下单率': 0.78 }
]
};
series:
{
smooth: false
},
yAxis:
{
min: 70
}
}
}
@@ -1,6 +1,7 @@
<template>
<div id="graph-overall">
<ve-line :data="chartData" :extend="{series: {smooth: false}}"></ve-line>
<p>Your average score graph all time:</p>
<ve-line :data="chart" :extend="settings"></ve-line>
</div>
</template>
+29
View File
@@ -0,0 +1,29 @@
// Add some margins
.el-card
{
margin: 10px;
height: 494px;
padding: 0;
// Vertical center
display: flex;
justify-content: center;
flex-direction: column;
}
.span-gpa-header
{
font-size: 14px;
}
.span-gpa
{
font-size: 35px;
font-family: 'Avenir', Helvetica, Arial, sans-serif;
}
.gpa-time
{
font-size: 14px;
}
+142 -1
View File
@@ -1,10 +1,151 @@
import {Component, Vue} from 'vue-property-decorator';
import {Component, Prop, Vue} from 'vue-property-decorator';
import GraphOverall from '@/pages/overall/graph-overall/graph-overall';
import {Course} from '@/components/app/app';
import {GPAUtils} from '@/utils/gpa-utils';
@Component({
components: {GraphOverall}
})
export default class Overall extends Vue
{
// @ts-ignore
@Prop({required: true}) courses: Course[];
get convertCharts()
{
// Null case
if (this.courses == null) return [];
// Filter it
let courses: Course[] = this.filterCourses();
// Compute the column names
let columns = ['date'];
courses.forEach(course =>
{
columns.push(course.name);
});
// Find the min date
let minDate: Date = new Date();
courses.forEach(course =>
{
if (course.assignments.length == 0) return;
let date = new Date(course.assignments[course.assignments.length - 1].date);
if (date < minDate) minDate = date;
});
// Find the dates in between
let now = new Date();
let dates = [];
for (let date = minDate; date <= now; date.setDate(date.getDate() + 1))
{
dates.push(new Date(date));
}
// Initialize course specific variables
let courseScores: {[index: string]: any} = {};
let courseMaxScores: {[index: string]: any} = {};
let courseIndexes: {[index: string]: any} = {};
courses.forEach(course =>
{
courseScores[course.name] = 0;
courseMaxScores[course.name] = 0;
courseIndexes[course.name] = course.assignments.length - 1;
});
// Compute the rows data
let rows: {[index: string]: any}[] = [];
dates.forEach(date =>
{
// Define row object
let row: {[index: string]:any} = {'date': date.toLocaleDateString('en-US')};
// Loop through courses
courses.forEach(course =>
{
// Reversed loop through the assignments
for (let r = courseIndexes[course.name]; r >= 0; r--)
{
let assignment = course.assignments[r];
// If assignment should be displayed
if (assignment.complete != 'Complete') continue;
// Date is being looked at
let assignmentDate = new Date(assignment.date);
if (assignmentDate.getTime() == date.getTime())
{
// Record scores
courseScores[course.name] += assignment.score;
courseMaxScores[course.name] += assignment.scoreMax;
}
// Not now
else if (assignmentDate > date)
{
courseIndexes[course.name] = r;
break;
}
}
// Add average to the row
row[course.name] = courseScores[course.name] / courseMaxScores[course.name] * 100;
});
// Add it to the array
rows.push(row);
});
console.log(rows);
return {
columns: columns,
rows: rows
}
}
/**
* Return a list of courses that are graphed
*/
private filterCourses(): Course[]
{
// Define result
let result: Course[] = [];
// Filter through courses
this.courses.forEach(course =>
{
// Skip future or past courses
if (course.status != 'active') return;
// Skip courses without levels
if (course.level == 'None') return;
// Skip courses without assignments
if (course.assignments.length == 0) return;
// Add it to the list
result.push(course);
});
return result;
}
/**
* This function is called to get gpa as a string.
*/
public getGPA()
{
let gpa = GPAUtils.getGPA(this.courses);
let result = '' + gpa.gpa;
/* Not accurate
if (!gpa.accurate)
{
result = `(${result})`;
}*/
return result;
}
}
+22 -2
View File
@@ -1,7 +1,27 @@
<template>
<div id="overall">
<p>这是 Overall</p>
<graph-overall></graph-overall>
<el-row>
<el-col :span="4">
<el-card style="margin-left: 20px">
<div style="padding: 14px;">
<span class="span-gpa-header">GPA:</span>
<br>
<span class="span-gpa">{{getGPA()}}</span>
<div class="bottom clearfix gpa-time">
<time class="time">{{ new Date().toDateString() }}</time>
<br>
<el-button type="text" class="button">Button</el-button>
</div>
</div>
</el-card>
</el-col>
<el-col :span="20">
<el-card style="margin-right: 20px">
<graph-overall :chart="convertCharts"></graph-overall>
</el-card>
</el-col>
</el-row>
<div class=""></div>
</div>
</template>
+101
View File
@@ -0,0 +1,101 @@
/**
* This is an utility class to calculate GPA.
*/
import {Course} from '@/components/app/app';
export class GPAUtils
{
// [[Min score, Letter grade, Base GPA], ...]
public static SCALE =
[
[96.5, 'A+', 4.00],
[92.5, 'A' , 3.75],
[89.5, 'A-', 3.50],
[86.5, 'B+', 3.25],
[82.5, 'B' , 3.00],
[79.5, 'B-', 2.75],
[76.5, 'C+', 2.50],
[72.5, 'C' , 2.25],
[70.5, 'C-', 2.00],
[69.5, 'D' , 1.00],
[0 , 'F' , 0.00]
];
// Keywords
public static MIN = 0;
public static LETTER = 1;
public static GPA = 2;
/**
* Calculate GPA for a list of couses
*
* @param coursesOriginal List of courses
*/
public static getGPA(coursesOriginal: Course[]): {gpa: number, accurate: boolean}
{
// Clone array
let courses: Course[] = [];
// Accurate or not
let accurate: boolean = true;
// Remove all courses that does not have a grade
coursesOriginal.forEach(course =>
{
if (course.numericGrade == null || course.numericGrade == 0)
{
accurate = false;
}
else if (course.level != 'none')
{
courses.push(course);
}
});
// If no course have grade, return -1
if (courses.length == 0)
{
return {gpa: -1, accurate: false};
}
// Count total GPA
let totalGPA = 0;
courses.forEach(course =>
{
totalGPA += this.getGP(course);
});
// Get average GPA, round to two decimal places
let gpa = Math.round(totalGPA / courses.length * 100) / 100;
// Return results
return {gpa: gpa, accurate: accurate};
}
/**
* Calculate GPA for a course
*
* @param course Course
*/
public static getGP(course: Course): number
{
// Find the GPA for this course.
for (let scale of this.SCALE)
{
// Letter grades are the same
if (scale[this.LETTER] == course.letterGrade)
{
// Get grade and add it
let grade = <number> scale[this.GPA];
// Add scaleUp if not failed.
if (grade != 0) grade += course.scaleUp;
// That's it
return grade;
}
}
return -1;
}
}
+35
View File
@@ -0,0 +1,35 @@
import Constants from '@/constants';
export class HttpUtils
{
public token: string = '';
constructor (token: string)
{
this.token = token;
}
public post(node: string, body: any): Promise<any>
{
// Add token
if (this.token != '') body['token'] = this.token;
// Create promise
return new Promise<any>((resolve, reject) =>
{
// Fetch request
fetch(`${Constants.API_URL}${node}`, {method: 'POST', body: JSON.stringify(body)}).then(res =>
{
// Get response body text
res.text().then(text =>
{
// Parse response
let response = JSON.parse(text);
resolve(response);
})
.catch(reject)
})
.catch(reject)
});
}
}
+33
View File
@@ -0,0 +1,33 @@
import {Grade} from '@/components/app/app';
export default class JsonUtils
{
/**
* This method filters the information provided in an assignments json.
*
* @param assignments Assignments object
* @returns Grade[] Filtered assignment grade object list
*/
public static filterAssignments(assignments: any): Grade[]
{
let result: Grade[] = [];
assignments.assignments.forEach((assignment: any) =>
{
result.push(
{
type: assignment.assignment_type,
description: assignment.assignment_description,
date: assignment._date,
complete: assignment.completion_status,
include: assignment.include_in_calculated_grade == 1,
display: assignment.display_grade == 1,
scoreMax: assignment.maximum_score,
score: +assignment.raw_score
});
});
return result;
}
}
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
devServer: {
disableHostCheck: true,
}
}