Compare commits
66 Commits
optimization
...
UI
| Author | SHA1 | Date | |
|---|---|---|---|
| 9656b3184b | |||
| b8dfb8f732 | |||
| 002aa84444 | |||
| e7c513695d | |||
| e51dbd2c5b | |||
| 9157232d45 | |||
| 17ef8f4380 | |||
| c31dbf0e50 | |||
| 9c2c1c3195 | |||
| 820c3c1148 | |||
| 6efb832212 | |||
| c885137ed7 | |||
| bb4b34722f | |||
| 2beca45e38 | |||
| cbec0add3b | |||
| 0ebce968b9 | |||
| 4095041925 | |||
| 0e618ceb13 | |||
| 042e72abb6 | |||
| 517235982b | |||
| 6e7041edcd | |||
| 67cf33b48c | |||
| 4c822fd207 | |||
| 68afbb8c76 | |||
| 5da0e89e08 | |||
| b2db05d5e2 | |||
| 3cb74083a7 | |||
| d3072ccaf6 | |||
| d566b53c22 | |||
| 7bad961f70 | |||
| 13e307f8d2 | |||
| 448e699cd3 | |||
| 1ca32b5ebd | |||
| 5ac3183ec1 | |||
| a7384753c8 | |||
| 393fc1cc71 | |||
| 06c265159b | |||
| 53a0884d0b | |||
| 7b0f11a1f4 | |||
| 4dc5966e51 | |||
| be0a657ba3 | |||
| 3287c14fa3 | |||
| 7af20f806b | |||
| bd8d7fd113 | |||
| 76cf8c4c53 | |||
| eaa1609b77 | |||
| 1324afe978 | |||
| e86d2fd4f5 | |||
| cf34db2c61 | |||
| 9038a73678 | |||
| 446ed686bd | |||
| 4782870d94 | |||
| 3ce21623d8 | |||
| 2338e4f6af | |||
| 38089c74b5 | |||
| 8860c88b1a | |||
| 75f9dc9849 | |||
| 805ffaa50e | |||
| c7d16a00e6 | |||
| 67ec2b85b2 | |||
| 9a752305e3 | |||
| c95a5b343e | |||
| 5029555c21 | |||
| 8eb2080f14 | |||
| 0296b2151a | |||
| 3575db8182 |
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">
|
||||||
|
|||||||
@@ -72,3 +72,28 @@
|
|||||||
width: 100%;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,6 +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 = '';
|
||||||
|
|
||||||
|
@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.
|
||||||
@@ -22,15 +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}/veracross/courses`).then(res =>
|
(<HttpUtils> this.http).post('/login', {username: this.username, password: this.password})
|
||||||
|
.then(response =>
|
||||||
{
|
{
|
||||||
// Get response body text
|
// Check success
|
||||||
res.text().then(text =>
|
if (response.success)
|
||||||
{
|
{
|
||||||
// Call custom event with courses info
|
// Save token to cookies
|
||||||
this.$emit('login:courses', JSON.parse(text));
|
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 =>
|
.catch(err =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,9 +3,22 @@
|
|||||||
<div class="login-vertical-center">
|
<div class="login-vertical-center">
|
||||||
<div class="login-panel">
|
<div class="login-panel">
|
||||||
<img alt="Vue logo" src="../../assets/logo.png">
|
<img alt="Vue logo" src="../../assets/logo.png">
|
||||||
|
|
||||||
<h1>Veracross Analyzer</h1>
|
<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>
|
<el-button plain type="primary" @click="onLoginClick" :loading="loading">Login</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
|
|
||||||
.el-menu.centered li
|
.el-menu.centered li
|
||||||
{
|
{
|
||||||
display: inline-block !important;
|
display: inline-block !important;
|
||||||
float: none !important;
|
float: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Borders
|
||||||
|
#navigation
|
||||||
|
{
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
|
||||||
|
|
||||||
|
ul
|
||||||
|
{
|
||||||
|
border-bottom-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+8
-1
@@ -7,5 +7,12 @@ export default class Constants
|
|||||||
* Base url for api access
|
* Base url for api access
|
||||||
* TODO: Use https for actual usage
|
* 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' +
|
||||||
|
' 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),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
@@ -18,6 +19,9 @@ export default class Overall extends Vue
|
|||||||
let columns = ['date'];
|
let columns = ['date'];
|
||||||
this.courses.forEach((course: Course) =>
|
this.courses.forEach((course: Course) =>
|
||||||
{
|
{
|
||||||
|
// Ignore non-important courses
|
||||||
|
if (course.status != 'active') return;
|
||||||
|
|
||||||
columns.push(course.name);
|
columns.push(course.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -25,6 +29,10 @@ export default class Overall extends Vue
|
|||||||
let minDate: Date = new Date();
|
let minDate: Date = new Date();
|
||||||
this.courses.forEach((course: Course) =>
|
this.courses.forEach((course: Course) =>
|
||||||
{
|
{
|
||||||
|
// Ignore non-important courses
|
||||||
|
if (course.status != 'active') return;
|
||||||
|
|
||||||
|
if (course.assignments.length == 0) return;
|
||||||
let date = new Date(course.assignments[course.assignments.length - 1].date);
|
let date = new Date(course.assignments[course.assignments.length - 1].date);
|
||||||
if (date < minDate) minDate = date;
|
if (date < minDate) minDate = date;
|
||||||
});
|
});
|
||||||
@@ -43,6 +51,9 @@ export default class Overall extends Vue
|
|||||||
let courseIndexes: {[index: string]: any} = {};
|
let courseIndexes: {[index: string]: any} = {};
|
||||||
this.courses.forEach((course: Course) =>
|
this.courses.forEach((course: Course) =>
|
||||||
{
|
{
|
||||||
|
// Ignore non-important courses
|
||||||
|
if (course.status != 'active') return;
|
||||||
|
|
||||||
courseScores[course.name] = 0;
|
courseScores[course.name] = 0;
|
||||||
courseMaxScores[course.name] = 0;
|
courseMaxScores[course.name] = 0;
|
||||||
courseIndexes[course.name] = course.assignments.length - 1;
|
courseIndexes[course.name] = course.assignments.length - 1;
|
||||||
@@ -58,6 +69,9 @@ export default class Overall extends Vue
|
|||||||
// Loop through courses
|
// Loop through courses
|
||||||
this.courses.forEach((course: Course) =>
|
this.courses.forEach((course: Course) =>
|
||||||
{
|
{
|
||||||
|
// Ignore non-important courses
|
||||||
|
if (course.status != 'active') return;
|
||||||
|
|
||||||
// Reversed loop through the assignments
|
// Reversed loop through the assignments
|
||||||
for (let r = courseIndexes[course.name]; r >= 0; r--)
|
for (let r = courseIndexes[course.name]; r >= 0; r--)
|
||||||
{
|
{
|
||||||
@@ -95,4 +109,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,28 @@
|
|||||||
<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 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">
|
||||||
|
<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)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
devServer: {
|
||||||
|
disableHostCheck: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user