Compare commits

..

1 Commits

Author SHA1 Message Date
Hykilpikonna 8b41296507 deploy 2020-08-02 12:45:47 -04:00
60 changed files with 675 additions and 12703 deletions
-21
View File
@@ -1,21 +0,0 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+14
View File
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>404: Redirecting...</title>
<meta http-equiv = "refresh" content = "0; url = https://vera.hydev.org/" />
</head>
<body>
404 Not Found! Redirecting to (<a href="https://vera.hydev.org">https://vera.hydev.org</a>)...
<script>
window.location.href = 'https://vera.hydev.org';
</script>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
demo.vera.hydev.org
-29
View File
@@ -1,29 +0,0 @@
# veracross-analyzer
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Run your tests
```
npm run test
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
-5
View File
@@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/app'
]
};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
{"success":true,"data":{"assignments":[{"score_id":5593330,"id":322150,"assignment_id":322150,"assignment_type_id":3,"assignment_type":"Quiz","assignment_type_sort_key":3,"assignment_description":"2.3 Open Notes","grading_period":"Quarter 1","assignment_date_long":"Wed, Sep 11","due_date_long":"Wed, Sep 11","due_date":"Sep 11","due_day":"Wed","_date":"09/11/2019","include_in_calculated_grade":1,"num_attachments":0,"num_criteria":0,"num_feedback":0,"maximum_score":16,"points_possible":16,"raw_score":"14","percent_grade":"8750%","completion_status_id":3,"completion_status":"Complete","is_unread":1.0,"is_notification":0,"is_problem":0,"display_grade":1,"display_score":1,"display_maximum_score":1,"display_percent_grade":1,"display_points_possible":1,"allow_student_feedback":0},{"score_id":5584935,"id":321649,"assignment_id":321649,"assignment_type_id":3,"assignment_type":"Quiz","assignment_type_sort_key":3,"assignment_description":"2.2 Open Notes","grading_period":"Quarter 1","assignment_date_long":"Mon, Sep 09","due_date_long":"Mon, Sep 09","due_date":"Sep 09","due_day":"Mon","_date":"09/09/2019","include_in_calculated_grade":1,"num_attachments":0,"num_criteria":0,"num_feedback":0,"maximum_score":10,"points_possible":10,"raw_score":"6","percent_grade":"6000%","completion_status_id":3,"completion_status":"Complete","is_notification":0,"is_problem":0,"display_grade":1,"display_score":1,"display_maximum_score":1,"display_percent_grade":1,"display_points_possible":1,"allow_student_feedback":0},{"score_id":5602940,"id":322723,"assignment_id":322723,"assignment_type_id":2,"assignment_type":"Homework","assignment_type_sort_key":2,"assignment_description":"Autobiography","grading_period":"Quarter 1","assignment_date_long":"Wed, Sep 04","due_date_long":"Thu, Sep 05","due_date":"Sep 05","due_day":"Thu","_date":"09/05/2019","include_in_calculated_grade":1,"num_attachments":0,"num_criteria":0,"num_feedback":0,"maximum_score":10,"points_possible":10,"raw_score":"","percent_grade":"0%","completion_status_id":0,"completion_status":"Pending","is_notification":0,"is_problem":0,"display_grade":1,"display_score":1,"display_maximum_score":1,"display_percent_grade":1,"display_points_possible":1,"allow_student_feedback":0}],"attachments":[],"criteria":[],"criteria_grade_scale_levels":[]}}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
{"success":true,"data":{"assignments":[],"attachments":[],"criteria":[],"criteria_grade_scale_levels":[]}}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
{"success":true,"data":{"assignments":[],"attachments":[],"criteria":[],"criteria_grade_scale_levels":[]}}
+1
View File
@@ -0,0 +1 @@
{"success":true,"data":{"assignments":[],"attachments":[],"criteria":[],"criteria_grade_scale_levels":[]}}
+1
View File
@@ -0,0 +1 @@
{"success":true,"data":{"assignments":[],"attachments":[],"criteria":[],"criteria_grade_scale_levels":[]}}
+1
View File
@@ -0,0 +1 @@
{"success":true,"data":{"assignments":[],"attachments":[],"criteria":[],"criteria_grade_scale_levels":[]}}
+250
View File
@@ -0,0 +1,250 @@
{
"success": true,
"data": {
"user": {
"id": 1,
"schoolPersonPk": 109467,
"username": "ygui21",
"lastLogin": "Jun 5, 2020 1:28:51 PM",
"firstLogin": "Dec 2, 2019 7:31:15 PM",
"firstName": "Yijie",
"lastName": "Gui",
"graduationYear": 2021,
"emails": "ygui21@stjohnsprep.org",
"classes": "32451|32453|32458|32856|32872|32874|32878|32880|32882|32890|33070|33093|33121|33173|33337|33464|34174|34197|34199|34209"
},
"token": "Removed",
"courses": [
{
"level": "H",
"id_ci": 196,
"rating": {
"id_ci": 196,
"id_user": 1,
"userFullName": "Yijie]\u003d[Gui",
"ratings": [
5,
5,
3,
5,
4
],
"comment": "Mr. Crowell\u0027s selected books for this course are very interesting, and the essay topics are generally unique too, allowing us to express our creativity. His lectures are also very in-depth, strengthening our understanding of the ideas that the authors wanted to express. However, the style of the classroom might be boring for some people. For grading fairness, I think it is very fair, but his standards are a little bit too high because achieving a perfect score for an essay is very much impossible."
},
"name": "English 3 H",
"teacherName": "Mr. Crowell",
"id": 33337,
"assignmentsId": 10934147,
"letterGrade": "A+",
"numericGrade": 97.14,
"status": "active"
},
{
"level": "AP",
"id_ci": 19,
"rating": {
"id_ci": 19,
"id_user": -1,
"userFullName": "Anonymous]\u003d[Student",
"ratings": [
5,
5,
5,
5,
5
],
"comment": "Calculus made so much easier"
},
"name": "AP Calculus AB (Juniors)",
"teacherName": "Ms. Dobrowolski",
"id": 32453,
"assignmentsId": 10934142,
"letterGrade": "A+",
"numericGrade": 100.0,
"status": "active"
},
{
"level": "AP",
"id_ci": 251,
"rating": {
"id_ci": 251,
"id_user": 1,
"userFullName": "Yijie]\u003d[Gui",
"ratings": [
5,
5,
5,
5,
5
],
"comment": "Cations go meow"
},
"name": "AP Chemistry",
"teacherName": "Ms. Stone",
"id": 33464,
"assignmentsId": 10934148,
"letterGrade": "A+",
"numericGrade": 100.0,
"status": "active"
},
{
"level": "AP",
"id_ci": 22,
"rating": {
"id_ci": 22,
"id_user": 1,
"userFullName": "Yijie]\u003d[Gui",
"ratings": [
5,
5,
5,
3,
4
],
"comment": "Mr. Dankert can\u0027t really systematically teach, often he forgets to tell us something very important and then forgets that he forgot. The labs explained a lot of them very well."
},
"name": "AP Physics 1",
"teacherName": "Mr. Dankert",
"id": 32458,
"assignmentsId": 10934143,
"letterGrade": "A",
"numericGrade": 95.67,
"status": "active"
},
{
"level": "AP",
"id_ci": 18,
"rating": {
"id_ci": 18,
"id_user": 1,
"userFullName": "Yijie]\u003d[Gui",
"ratings": [
5,
5,
5,
5,
5
],
"comment": "Psychology is the best, and most valuable class I\u0027ve ever taken! Everything is just so relatable to my life!"
},
"name": "AP Psychology",
"teacherName": "Mr. Emerson",
"id": 32451,
"assignmentsId": 10934141,
"letterGrade": "A+",
"numericGrade": 100.0,
"status": "active"
},
{
"level": "A",
"id_ci": 98,
"rating": {
"id_ci": 98,
"id_user": 1,
"userFullName": "Yijie]\u003d[Gui",
"ratings": [
5,
5,
5,
5,
5
],
"comment": "Mr. Pynchon is very nice and encouraging."
},
"name": "US History A",
"teacherName": "Mr. Pynchon",
"id": 33093,
"assignmentsId": 10941280,
"letterGrade": "A+",
"numericGrade": 99.39,
"status": "active"
},
{
"level": "H",
"id_ci": 100,
"name": "US History H",
"teacherName": "Ms. Heath",
"id": 33096,
"assignmentsId": 10934144,
"status": "past"
},
{
"level": "A",
"id_ci": 134,
"name": "Relational Dynamics A",
"teacherName": "Mr. Reinbold",
"id": 33173,
"assignmentsId": 10934146,
"status": "past"
},
{
"level": "A",
"id_ci": 77,
"rating": {
"id_ci": 77,
"id_user": -1,
"userFullName": "Anonymous]\u003d[Student",
"ratings": [
5,
4,
5,
5,
3
],
"comment": "Honestly, everything is great except that the grading is way too harsh for an Accelerated course."
},
"name": "Social Justice A",
"teacherName": "Mr. Reinbold",
"id": 33121,
"assignmentsId": 10934145,
"letterGrade": "A+",
"numericGrade": 97.0,
"status": "active"
},
{
"level": "Sport",
"name": "Yoga AS",
"teacherName": "Ms. Fanikos",
"id": 33070,
"assignmentsId": 10935189,
"status": "past"
},
{
"level": "Club",
"id_ci": 316,
"name": "HS Magic Trick Club 2019",
"teacherName": "Mr. Reinbold",
"id": 34174,
"assignmentsId": 10949139,
"status": "active"
},
{
"level": "Club",
"id_ci": 331,
"name": "HS Science \u0026amp; Technology 2019",
"teacherName": "Ms. Erwin",
"id": 34197,
"assignmentsId": 10952100,
"status": "active"
},
{
"level": "Club",
"id_ci": 333,
"name": "HS Computer Club 2019",
"teacherName": "Mr. Gilmore",
"id": 34199,
"assignmentsId": 10951448,
"status": "active"
},
{
"level": "Club",
"id_ci": 336,
"name": "HS Chinese Ambassadors Club 2019",
"teacherName": "Mrs. Mills",
"id": 34209,
"assignmentsId": 10953979,
"status": "active"
}
]
}
}
View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

+5
View File
@@ -0,0 +1,5 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=1024"><link rel=icon href=/logo@32px.png><title>Veracross Analyzer</title><link href=/css/app.72ceade9.css rel=preload as=style><link href=/js/app.c7702e9a.js rel=preload as=script><link href=/js/chunk-vendors.4383782d.js rel=preload as=script><link href=/css/app.72ceade9.css rel=stylesheet></head><body style="margin: 0"><noscript><strong>We're sorry but veracross-analyzer doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js></script><script src=https://cdn.jsdelivr.net/npm/v-charts/lib/index.min.js></script><link rel=stylesheet href=https://cdn.jsdelivr.net/npm/v-charts/lib/style.min.css><link rel=stylesheet href=https://unpkg.com/element-ui/lib/theme-chalk/index.css><link href="https://fonts.googleapis.com/css?family=Nunito+Sans&display=swap" rel=stylesheet><script src=/js/chunk-vendors.4383782d.js></script><script src=/js/app.c7702e9a.js></script></body><script async src="https://www.googletagmanager.com/gtag/js?id=G-Q615K1KFLC"></script><script>window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-Q615K1KFLC');</script></html>
+14
View File
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Redirecting...</title>
<meta http-equiv = "refresh" content = "0; url = https://vera.hydev.org/#info" />
</head>
<body>
Redirecting to (<a href="https://vera.hydev.org/#info">https://vera.hydev.org/#info</a>)...
<script>
window.location.href = 'https://vera.hydev.org/#info';
</script>
</body>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

-11600
View File
File diff suppressed because it is too large Load Diff
-39
View File
@@ -1,39 +0,0 @@
{
"name": "veracross-analyzer",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"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": {
"@vue/cli-plugin-babel": "^3.10.0",
"@vue/cli-plugin-typescript": "^3.10.0",
"@vue/cli-service": "^3.10.0",
"node-sass": "^4.9.0",
"sass-loader": "^7.1.0",
"typescript": "^3.4.3",
"vue-template-compiler": "^2.6.10"
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}
-28
View File
@@ -1,28 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>Veracross Analyzer</title>
</head>
<body style="margin: 0">
<noscript>
<strong>We're sorry but veracross-analyzer doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- V-Charts -->
<script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/v-charts/lib/index.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/v-charts/lib/style.min.css">
<!-- ElementUI -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
</body>
</html>
-2
View File
@@ -1,2 +0,0 @@
cd ../
npm run serve
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

-8
View File
@@ -1,8 +0,0 @@
#app
{
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
-188
View File
@@ -1,188 +0,0 @@
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;
// List of course that the student takes
public courses: Course[] = [];
// 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 token Authorization token
*/
public onLogin(token: string)
{
// Hide login bar
this.showLogin = false;
// Store token
this.token = token;
// 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;
}
/**
* This is called when a navigation tab is clicked
*
* @param tab Tab name
*/
public onNavigate(tab: string)
{
// Debug output TODO: Remove this
console.log(tab);
// Update selected tab
this.selectedTab = tab;
}
}
-13
View File
@@ -1,13 +0,0 @@
<template>
<div id="app">
<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">
<overall :courses="courses" v-if="selectedTab === 'overall' && assignmentsReady"></overall>
</div>
</div>
</template>
<script src="./app.ts" lang="ts"></script>
<style src="./app.scss" lang="scss"></style>
-99
View File
@@ -1,99 +0,0 @@
// Parent div for login
#login
{
}
// Parent overlay
.login-overlay
{
// Credit to w3schools.com:
// https://www.w3schools.com/howto/howto_js_fullscreen_overlay.asp
// Fill entire screen
height: 100%;
width: 100%;
left: 0;
top: 0;
// Stay in place
position: fixed;
// Sit on top layer
z-index: 1;
// Overlay color
background-color: rgba(0,0,0, 0.65);
// Disable horizontal scroll
overflow-x: hidden;
// Make it a table for vertical centering
display: table;
}
.login-vertical-center
{
// Vertically center
display: table-cell;
vertical-align: middle;
}
// The user interacting panel
.login-panel
{
// Make it smaller
width: 256px;
// Center
margin-left: auto;
margin-right: auto;
// Borders
padding: 15px;
border-radius: 10px;
// box-shadow: 0 0 20px 0 white;
border: 1px solid #DCDFE6;
// Make it white
background-color: white;
// Input bars
.el-input
{
margin: 5px 0;
}
// Button
.el-button
{
margin: 5px 0;
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;
}
-73
View File
@@ -1,73 +0,0 @@
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.
*/
@Component({
components: {},
})
export default class Login extends Vue
{
public username: any = '';
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.
*/
public onLoginClick()
{
// Make login button loading
this.loading = true;
// Fetch request
(<HttpUtils> this.http).post('/login', {username: this.username, password: this.password})
.then(response =>
{
// Check success
if (response.success)
{
// 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 =>
{
alert(err);
// Allow the user to retry
this.loading = false;
});
}
}
-29
View File
@@ -1,29 +0,0 @@
<template>
<div id="login" class="login-overlay">
<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"
: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>
</div>
</template>
<script src="./login.ts" lang="ts"></script>
<style src="./login.scss" lang="scss"></style>
-17
View File
@@ -1,17 +0,0 @@
.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;
}
}
-30
View File
@@ -1,30 +0,0 @@
import {Component, Prop, Vue} from 'vue-property-decorator';
import Constants from '@/constants';
/**
* This component is the top navigation bar
*/
@Component({
components: {},
})
export default class Navigation extends Vue
{
public activeIndex: string = 'overall';
@Prop() courses: any;
/**
* This function is called when the selection changes.
*
* @param index The index selected
* @param indexPath The path of the index
*/
public onSelect(index: string, indexPath: string)
{
// Update active index
this.activeIndex = index;
// Call custom event
this.$emit('navigation:select', this.activeIndex);
}
}
-20
View File
@@ -1,20 +0,0 @@
<template>
<div id="navigation">
<el-menu class="centered" :default-active="activeIndex" mode="horizontal" @select="onSelect">
<el-menu-item index="overall">Overall</el-menu-item>
<el-submenu index="courses">
<template slot="title">Courses</template>
<el-menu-item v-for="course in courses"
:index="`course-${course.name}`"
:key="course.name">{{course.name}}</el-menu-item>
</el-submenu>
</el-menu>
<div class="line"></div>
</div>
</template>
<script src="./navigation.ts" lang="ts"></script>
<style src="./navigation.scss" lang="scss"></style>
-18
View File
@@ -1,18 +0,0 @@
/**
* This class stores the static constants.
*/
export default class Constants
{
/**
* Base url for api access
* 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 `---\' '
}
-22
View File
@@ -1,22 +0,0 @@
import Vue from 'vue';
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;
// Use Element UI
Vue.use(ElementUI, {locale: 'en-us'});
// Use VCharts
Vue.use(VCharts);
// Use Cookies
Vue.use(VueCookies);
// Init app
new Vue({
render: (h) => h(App),
}).$mount('#app');
@@ -1,22 +0,0 @@
import {Component, Prop, Vue} from 'vue-property-decorator';
import {Course} from '@/components/app/app';
@Component({
})
export default class GraphOverall extends Vue
{
@Prop({required: true}) chart: any;
public chartData =
{
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 }
]
};
}
@@ -1,8 +0,0 @@
<template>
<div id="graph-overall">
<ve-line :data="chart" :extend="{series: {smooth: false}}"></ve-line>
</div>
</template>
<script src="./graph-overall.ts" lang="ts"></script>
<style src="./graph-overall.scss" lang="scss"></style>
-29
View File
@@ -1,29 +0,0 @@
// 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;
}
-126
View File
@@ -1,126 +0,0 @@
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
{
@Prop({required: true}) courses: any;
get convertCharts()
{
// Null case
if (this.courses == null) return [];
// Compute the column names
let columns = ['date'];
this.courses.forEach((course: Course) =>
{
// Ignore non-important courses
if (course.status != 'active') return;
columns.push(course.name);
});
// Find the min date
let minDate: Date = new Date();
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);
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} = {};
this.courses.forEach((course: Course) =>
{
// Ignore non-important courses
if (course.status != 'active') return;
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
this.courses.forEach((course: Course) =>
{
// Ignore non-important courses
if (course.status != 'active') return;
// Reversed loop through the assignments
for (let r = courseIndexes[course.name]; r >= 0; r--)
{
let assignment = course.assignments[r];
let assignmentDate = new Date(assignment.date);
// Date is being looked at
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
}
}
public getGPA()
{
let gpa = GPAUtils.getGPA(this.courses);
let result = '' + gpa.gpa;
/* Not accurate
if (!gpa.accurate)
{
result = `(${result})`;
}*/
return result;
}
}
-30
View File
@@ -1,30 +0,0 @@
<template>
<div id="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">
<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>
<script src="./overall.ts" lang="ts"></script>
<style src="./overall.scss" lang="scss"></style>
-13
View File
@@ -1,13 +0,0 @@
import Vue, { VNode } from 'vue';
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any;
}
}
}
-4
View File
@@ -1,4 +0,0 @@
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}
-101
View File
@@ -1,101 +0,0 @@
/**
* 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
@@ -1,35 +0,0 @@
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
@@ -1,33 +0,0 @@
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.complete_status,
include: assignment.include_in_calculated_grade == 1,
display: assignment.display_grade == 1,
scoreMax: assignment.maximum_score,
score: +assignment.raw_score
});
});
return result;
}
}
-39
View File
@@ -1,39 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}
-17
View File
@@ -1,17 +0,0 @@
{
"defaultSeverity": "warning",
"linterOptions": {
"exclude": [
"node_modules/**"
]
},
"rules": {
"indent": [true, "spaces", 4],
"curly": false,
"interface-name": false,
"no-consecutive-blank-lines": false,
"object-literal-sort-keys": false,
"ordered-imports": false,
"quotemark": [true, "single"]
}
}
-5
View File
@@ -1,5 +0,0 @@
module.exports = {
devServer: {
disableHostCheck: true,
}
}