Merge branch 'feature' into styling

This commit is contained in:
Hykilpikonna
2019-09-18 08:26:07 -04:00
11 changed files with 303 additions and 40 deletions
+5
View File
@@ -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",
+1
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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">
+37 -22
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,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 =>
{
+7
View File
@@ -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 `---\' '
}
+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),
+15
View File
@@ -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;
}
}
+20 -2
View File
@@ -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>
+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)
{
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)
});
}
}