Merge branch 'feature' into styling
This commit is contained in:
Generated
+5
@@ -10848,6 +10848,11 @@
|
|||||||
"resolved": "https://registry.npm.taobao.org/vue-class-component/download/vue-class-component-7.1.0.tgz",
|
"resolved": "https://registry.npm.taobao.org/vue-class-component/download/vue-class-component-7.1.0.tgz",
|
||||||
"integrity": "sha1-sz78sQ4XI21oT3Cx6W8ZRux5Poc="
|
"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": {
|
"vue-hot-reload-api": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npm.taobao.org/vue-hot-reload-api/download/vue-hot-reload-api-2.3.3.tgz",
|
"resolved": "https://registry.npm.taobao.org/vue-hot-reload-api/download/vue-hot-reload-api-2.3.3.tgz",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"v-charts": "^1.19.0",
|
"v-charts": "^1.19.0",
|
||||||
"vue": "^2.6.10",
|
"vue": "^2.6.10",
|
||||||
"vue-class-component": "^7.0.2",
|
"vue-class-component": "^7.0.2",
|
||||||
|
"vue-cookies": "^1.5.13",
|
||||||
"vue-property-decorator": "^8.1.0"
|
"vue-property-decorator": "^8.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
+77
-15
@@ -5,6 +5,7 @@ import Overall from '@/pages/overall/overall';
|
|||||||
import Constants from '@/constants';
|
import Constants from '@/constants';
|
||||||
import JsonUtils from '@/utils/json-utils';
|
import JsonUtils from '@/utils/json-utils';
|
||||||
import pWaitFor from 'p-wait-for';
|
import pWaitFor from 'p-wait-for';
|
||||||
|
import {HttpUtils} from '@/utils/http-utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Objects of this interface represent assignment grades.
|
* Objects of this interface represent assignment grades.
|
||||||
@@ -31,6 +32,13 @@ export interface Course
|
|||||||
id: number,
|
id: number,
|
||||||
name: string,
|
name: string,
|
||||||
teacherName: string,
|
teacherName: string,
|
||||||
|
status: string,
|
||||||
|
|
||||||
|
letterGrade?: string,
|
||||||
|
numericGrade?: number,
|
||||||
|
|
||||||
|
level: string,
|
||||||
|
scaleUp: number,
|
||||||
|
|
||||||
assignments: Grade[]
|
assignments: Grade[]
|
||||||
}
|
}
|
||||||
@@ -52,39 +60,93 @@ export default class App extends Vue
|
|||||||
// Are the course assignments loaded from the server.
|
// Are the course assignments loaded from the server.
|
||||||
public assignmentsReady: boolean = false;
|
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.
|
* This is called when the user logs in.
|
||||||
*
|
*
|
||||||
* @param courses Courses Json
|
* @param token Authorization token
|
||||||
*/
|
*/
|
||||||
public onLogin(courses: Course[])
|
public onLogin(token: string)
|
||||||
{
|
{
|
||||||
// Hide login bar
|
// Hide login bar
|
||||||
this.showLogin = false;
|
this.showLogin = false;
|
||||||
|
|
||||||
// Assign courses
|
// Store token
|
||||||
this.courses = courses;
|
this.token = token;
|
||||||
|
|
||||||
// Debug output TODO: Remove this
|
// Assign token to http client
|
||||||
console.log(courses);
|
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
|
// Get assignments for all the courses
|
||||||
this.courses.forEach(course =>
|
this.courses.forEach(course =>
|
||||||
{
|
{
|
||||||
// Send request to get assignments
|
// Send request to get assignments
|
||||||
fetch(`${Constants.API_URL}/veracross/assignments?id=${course.assignmentsId}`).then(res =>
|
this.http.post('/assignments', {id: course.assignmentsId}).then(response =>
|
||||||
{
|
{
|
||||||
// Get response body text
|
// Check success
|
||||||
res.text().then(text =>
|
if (response.success)
|
||||||
{
|
{
|
||||||
|
// Load assignments
|
||||||
// Parse json and filter it
|
// Parse json and filter it
|
||||||
course.assignments = JsonUtils.filterAssignments(JSON.parse(text));
|
course.assignments = JsonUtils.filterAssignments(response.data);
|
||||||
})
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Show error message TODO: Show it properly
|
||||||
|
alert(response.data);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(err =>
|
.catch(alert);
|
||||||
{
|
|
||||||
alert(err);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait for assignments to be ready.
|
// Wait for assignments to be ready.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<login v-if="showLogin" v-on:login:courses="onLogin"></login>
|
<login v-if="showLogin" v-on:login:token="onLogin" :http="http"></login>
|
||||||
<navigation :courses="courses" v-on:navigation:select="onNavigate"></navigation>
|
<navigation :courses="courses" v-on:navigation:select="onNavigate"></navigation>
|
||||||
|
|
||||||
<div id="app-content">
|
<div id="app-content">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {Component, Vue} from 'vue-property-decorator';
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
import Constants from '@/constants';
|
import Constants from '@/constants';
|
||||||
|
import {HttpUtils} from '@/utils/http-utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component handles user login, and obtains data from the server.
|
* This component handles user login, and obtains data from the server.
|
||||||
@@ -13,7 +14,23 @@ export default class Login extends Vue
|
|||||||
public password: any = '';
|
public password: any = '';
|
||||||
|
|
||||||
public loading: boolean = false;
|
public loading: boolean = false;
|
||||||
public error: String = 'asdf';
|
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.
|
* On click, sends username and password to the server.
|
||||||
@@ -23,29 +40,27 @@ export default class Login extends Vue
|
|||||||
// Make login button loading
|
// Make login button loading
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
// Fetch request TODO: Add username and password when the https server is ready.
|
// Fetch request
|
||||||
fetch(`${Constants.API_URL}/api/login`,
|
(<HttpUtils> this.http).post('/login', {username: this.username, password: this.password})
|
||||||
{body: JSON.stringify({username: this.username, password: this.password})})
|
.then(response =>
|
||||||
.then(res =>
|
|
||||||
{
|
{
|
||||||
// Get response body text
|
// Check success
|
||||||
res.text().then(text =>
|
if (response.success)
|
||||||
{
|
{
|
||||||
// Parse response
|
// Save token to cookies
|
||||||
let response = JSON.parse(text);
|
this.$cookies.set('va.token', response.data, '7d');
|
||||||
|
|
||||||
// Check success
|
// Call custom event with token
|
||||||
if (response.success)
|
this.$emit('login:token', response.data);
|
||||||
{
|
}
|
||||||
// Call custom event with courses info
|
else
|
||||||
this.$emit('login:token', response.token);
|
{
|
||||||
}
|
// Show error message
|
||||||
else
|
this.error = response.data;
|
||||||
{
|
|
||||||
// Allow the user to retry
|
// Allow the user to retry
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.catch(err =>
|
.catch(err =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,4 +8,11 @@ export default class Constants
|
|||||||
* TODO: Use https for actual usage
|
* TODO: Use https for actual usage
|
||||||
*/
|
*/
|
||||||
public static API_URL: string = 'https://va.hydev.org/api';
|
public static API_URL: string = 'https://va.hydev.org/api';
|
||||||
|
|
||||||
|
public static SPLASH: string =
|
||||||
|
'. , ,---. | \n' +
|
||||||
|
'| |. , |---|,---.,---.| , .,---,,---.,---.\n' +
|
||||||
|
' \\ / >< | || |,---|| | | .-\' |---\'| \n' +
|
||||||
|
' `\' \' ` ` \'` \'`---^`---\'`---|\'---\'`---\'` \n' +
|
||||||
|
' v1.1.0 `---\' '
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import ElementUI from 'element-ui';
|
|||||||
const VCharts = require('v-charts');
|
const VCharts = require('v-charts');
|
||||||
|
|
||||||
import App from './components/app/app.vue';
|
import App from './components/app/app.vue';
|
||||||
|
import VueCookies from 'vue-cookies';
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
@@ -12,6 +13,9 @@ Vue.use(ElementUI, {locale: 'en-us'});
|
|||||||
// Use VCharts
|
// Use VCharts
|
||||||
Vue.use(VCharts);
|
Vue.use(VCharts);
|
||||||
|
|
||||||
|
// Use Cookies
|
||||||
|
Vue.use(VueCookies);
|
||||||
|
|
||||||
// Init app
|
// Init app
|
||||||
new Vue({
|
new Vue({
|
||||||
render: (h) => h(App),
|
render: (h) => h(App),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
import GraphOverall from '@/pages/overall/graph-overall/graph-overall';
|
import GraphOverall from '@/pages/overall/graph-overall/graph-overall';
|
||||||
import {Course} from '@/components/app/app';
|
import {Course} from '@/components/app/app';
|
||||||
|
import {GPAUtils} from '@/utils/gpa-utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {GraphOverall}
|
components: {GraphOverall}
|
||||||
@@ -95,4 +96,18 @@ export default class Overall extends Vue
|
|||||||
rows: rows
|
rows: rows
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getGPA()
|
||||||
|
{
|
||||||
|
let gpa = GPAUtils.getGPA(this.courses);
|
||||||
|
let result = '' + gpa.gpa;
|
||||||
|
|
||||||
|
// Not accurate
|
||||||
|
if (!gpa.accurate)
|
||||||
|
{
|
||||||
|
result = `(${result})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="overall">
|
<div id="overall">
|
||||||
<p>这是 Overall</p>
|
<el-row>
|
||||||
<graph-overall :chart="convertCharts"></graph-overall>
|
<el-col :span="4">
|
||||||
|
<el-card :body-style="{ padding: '0px' }">
|
||||||
|
<div style="padding: 14px;">
|
||||||
|
<span>GPA: {{getGPA()}}</span>
|
||||||
|
<div class="bottom clearfix">
|
||||||
|
<time class="time">{{ new Date().toDateString() }}</time>
|
||||||
|
<el-button type="text" class="button">操作按钮</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="20">
|
||||||
|
<el-card :body-style="{ padding: '0px' }">
|
||||||
|
<p>Your average score graph all time:</p>
|
||||||
|
<graph-overall :chart="convertCharts"></graph-overall>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<div class=""></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user