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",
|
||||
"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",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"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": {
|
||||
|
||||
+77
-15
@@ -5,6 +5,7 @@ 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.
|
||||
@@ -31,6 +32,13 @@ export interface Course
|
||||
id: number,
|
||||
name: string,
|
||||
teacherName: string,
|
||||
status: string,
|
||||
|
||||
letterGrade?: string,
|
||||
numericGrade?: number,
|
||||
|
||||
level: string,
|
||||
scaleUp: number,
|
||||
|
||||
assignments: Grade[]
|
||||
}
|
||||
@@ -52,39 +60,93 @@ export default class App extends Vue
|
||||
// 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: Course[])
|
||||
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
|
||||
fetch(`${Constants.API_URL}/veracross/assignments?id=${course.assignmentsId}`).then(res =>
|
||||
this.http.post('/assignments', {id: course.assignmentsId}).then(response =>
|
||||
{
|
||||
// Get response body text
|
||||
res.text().then(text =>
|
||||
// Check success
|
||||
if (response.success)
|
||||
{
|
||||
// Load assignments
|
||||
// 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 =>
|
||||
{
|
||||
alert(err);
|
||||
});
|
||||
.catch(alert);
|
||||
});
|
||||
|
||||
// Wait for assignments to be ready.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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>
|
||||
|
||||
<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 {HttpUtils} from '@/utils/http-utils';
|
||||
|
||||
/**
|
||||
* 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 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.
|
||||
@@ -23,29 +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}/api/login`,
|
||||
{body: JSON.stringify({username: this.username, password: this.password})})
|
||||
.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)
|
||||
{
|
||||
// Parse response
|
||||
let response = JSON.parse(text);
|
||||
// Save token to cookies
|
||||
this.$cookies.set('va.token', response.data, '7d');
|
||||
|
||||
// Check success
|
||||
if (response.success)
|
||||
{
|
||||
// Call custom event with courses info
|
||||
this.$emit('login:token', response.token);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Allow the user to retry
|
||||
this.loading = false;
|
||||
}
|
||||
})
|
||||
// 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 =>
|
||||
{
|
||||
|
||||
@@ -8,4 +8,11 @@ export default class Constants
|
||||
* TODO: Use https for actual usage
|
||||
*/
|
||||
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');
|
||||
|
||||
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,6 +1,7 @@
|
||||
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}
|
||||
@@ -95,4 +96,18 @@ export default class Overall extends Vue
|
||||
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>
|
||||
<div id="overall">
|
||||
<p>这是 Overall</p>
|
||||
<graph-overall :chart="convertCharts"></graph-overall>
|
||||
<el-row>
|
||||
<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>
|
||||
</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