Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0345d37b2c | |||
| 97818c45f5 | |||
| 1a09c7311e | |||
| b658f6f707 | |||
| 1d63e4865b | |||
| 78fbd342d9 | |||
| 141c192017 | |||
| 87e44b83f7 | |||
| 1064831379 | |||
| 1e42ef7873 | |||
| c8327f3774 | |||
| 69cd4ff050 | |||
| 027f8bfd34 | |||
| 70ede22c18 | |||
| fd74298b0b | |||
| d47bae00ff | |||
| 0c91b1b598 | |||
| 6ba789c7cf | |||
| 54b70f6a1c | |||
| ac32a02ff4 | |||
| 993578277e | |||
| 48fb8c7d4c | |||
| 0f6f27aa15 | |||
| b2947e9ef6 | |||
| c5ef8a297b | |||
| 02ca6b8d47 | |||
| 98b92b4dfb | |||
| 1c2afdfa52 | |||
| 55b4bd9173 | |||
| e422c03ca5 | |||
| 831ef16a2f | |||
| 0deb0ed719 | |||
| f3d44ceda2 | |||
| cb11db6201 | |||
| b52f3d2617 | |||
| 39cd5bf1a2 | |||
| 19f03717bc | |||
| 6d728c25ae | |||
| c8daa2a06e | |||
| 4d41e679dd | |||
| acfebf9ff3 | |||
| 5af1b1e5a1 | |||
| bc9a4f00c1 | |||
| f13f3a155c | |||
| a19edb8aa7 | |||
| 6a0500acb2 | |||
| 96af69508f | |||
| 3fbc9764e9 | |||
| a79dcb76af | |||
| 86c2fd5df7 | |||
| 07eaeef133 | |||
| d1bd0a5667 | |||
| 49dad14c84 | |||
| 50e3c345d5 | |||
| a444dbb32a | |||
| 03e212bd37 | |||
| 0325eb13d5 | |||
| bfcb80aa64 | |||
| 91a6c0c6e4 | |||
| 845cc94513 | |||
| 21ccaceb53 | |||
| b9f63171f2 | |||
| 636d92d1a0 | |||
| 199ab9e00c | |||
| 77541764d1 | |||
| 53ae74f9c4 | |||
| 5bc1d6ba48 | |||
| 831ddcf84e | |||
| 35a85d8e83 | |||
| 71b0a6e4dd | |||
| d4fbd04466 | |||
| 31b3814b1e | |||
| 1aff2b0a68 | |||
| 2567fcadbd | |||
| d9e0e9f84e | |||
| 59b31ea43f | |||
| 2c8b3e0f84 | |||
| 6910a7b5ea | |||
| cbdcfc4ca1 | |||
| feabc336c1 |
@@ -1,8 +1,14 @@
|
||||
#app
|
||||
{
|
||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.theme-default
|
||||
{
|
||||
--unread: #ff6c00;
|
||||
--main: #0c6dad;
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@ import JsonUtils from '@/utils/json-utils';
|
||||
import pWaitFor from 'p-wait-for';
|
||||
import {HttpUtils} from '@/utils/http-utils';
|
||||
import {CourseUtils} from '@/utils/course-utils';
|
||||
import {GPAUtils} from '@/utils/gpa-utils';
|
||||
|
||||
/**
|
||||
* Objects of this interface represent assignment grades.
|
||||
*/
|
||||
export interface Grade
|
||||
export interface Assignment
|
||||
{
|
||||
id: number,
|
||||
type: string,
|
||||
description: string,
|
||||
date: string,
|
||||
@@ -20,6 +22,8 @@ export interface Grade
|
||||
include: boolean,
|
||||
display: boolean,
|
||||
|
||||
unread: boolean,
|
||||
|
||||
scoreMax: number,
|
||||
score: number
|
||||
}
|
||||
@@ -47,7 +51,7 @@ export interface Course
|
||||
weightingMap: {[index: string]: number}
|
||||
}
|
||||
|
||||
assignments: Grade[]
|
||||
assignments: Assignment[]
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -160,12 +164,11 @@ export default class App extends Vue
|
||||
// Wait for assignments to be ready.
|
||||
pWaitFor(() => this.isAssignmentsReady()).then(() =>
|
||||
{
|
||||
// When the assignments are ready
|
||||
// TODO: Display loading
|
||||
this.assignmentsReady = true;
|
||||
|
||||
// Filter courses
|
||||
this.filteredCourses = CourseUtils.getGradedCourses(this.courses);
|
||||
|
||||
// Check grading algorithms
|
||||
this.checkGradingAlgorithms();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -184,6 +187,67 @@ export default class App extends Vue
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the courses' grading algorithms. (Total-average or percent-type)
|
||||
*/
|
||||
private checkGradingAlgorithms()
|
||||
{
|
||||
// Loop through all the courses
|
||||
for (const course of this.filteredCourses)
|
||||
{
|
||||
// Check if total-average grade is the same with percent-type grade
|
||||
if (course.numericGrade == GPAUtils.getTotalMeanAverage(course))
|
||||
{
|
||||
course.grading = {method: 'TOTAL_AVERAGE', weightingMap: {}};
|
||||
}
|
||||
else
|
||||
{
|
||||
// Request grading scheme for this course
|
||||
this.http.post('/grading', {'assignment_id': course.assignmentsId}).then(response =>
|
||||
{
|
||||
// Check success
|
||||
if (response.success)
|
||||
{
|
||||
// Add it to course
|
||||
course.grading = response.data;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Show error message TODO: Show it properly
|
||||
alert(response.data)
|
||||
}
|
||||
})
|
||||
.catch(alert)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for done
|
||||
pWaitFor(() => this.isGradingReady()).then(() =>
|
||||
{
|
||||
// When the assignments are ready
|
||||
// TODO: Display loading
|
||||
this.assignmentsReady = true;
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Are grading algorithms ready or not.
|
||||
*
|
||||
* @returns boolean Ready or not
|
||||
*/
|
||||
private isGradingReady(): boolean
|
||||
{
|
||||
for (const course of this.filteredCourses)
|
||||
{
|
||||
if (course.grading == undefined)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when a navigation tab is clicked
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<div id="app" class="theme-default">
|
||||
<login v-if="showLogin" v-on:login:token="onLogin" :http="http"></login>
|
||||
<navigation :courses="filteredCourses"
|
||||
v-on:sign-out="signOut()"
|
||||
|
||||
@@ -15,3 +15,36 @@
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#sign-out-button
|
||||
{
|
||||
// Float right
|
||||
position: absolute;
|
||||
right: 0;
|
||||
|
||||
// Set width and height
|
||||
height: 60px;
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
#nav-title
|
||||
{
|
||||
// Float left
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
||||
// Set height
|
||||
height: 60px;
|
||||
|
||||
// Center text
|
||||
align-items: center;
|
||||
|
||||
// Margins
|
||||
margin-left: 14px;
|
||||
margin-right: 8px;
|
||||
|
||||
// Font
|
||||
font-size: 1.25rem;
|
||||
display: inline-flex;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<div id="navigation">
|
||||
<el-menu class="centered" :default-active="activeIndex" mode="horizontal" @select="onSelect">
|
||||
<el-menu style="margin-bottom: 10px;"
|
||||
class="centered" :default-active="activeIndex" mode="horizontal" @select="onSelect">
|
||||
|
||||
<!--div id="nav-title">
|
||||
Veracross Analyzer
|
||||
</div-->
|
||||
|
||||
<el-menu-item index="overall">Overall</el-menu-item>
|
||||
|
||||
|
||||
+7
-1
@@ -8,12 +8,18 @@ export default class Constants
|
||||
*/
|
||||
public static API_URL: string = 'https://va.hydev.org/api';
|
||||
|
||||
public static VERSION: string = '0.3.1.382';
|
||||
|
||||
public static GITHUB: string = 'https://github.com/HyDevelop/VeracrossAnalyzer.Client';
|
||||
|
||||
public static SPLASH: string =
|
||||
'. , ,---. | \n' +
|
||||
'| |. , |---|,---.,---.| , .,---,,---.,---.\n' +
|
||||
' \\ / >< | || |,---|| | | .-\' |---\'| \n' +
|
||||
' `\' \' ` ` \'` \'`---^`---\'`---|\'---\'`---\'` \n' +
|
||||
' v0.2.2.303 `---\' ';
|
||||
' `---\' \n' +
|
||||
` Version v${Constants.VERSION} by Hykilpikonna (YGui21)\n` +
|
||||
` Github: ${Constants.GITHUB}`;
|
||||
|
||||
// Graph Theme
|
||||
public static THEME =
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<template>
|
||||
<div id="graph-average">
|
||||
<ve-bar height="450px" class="graph"
|
||||
:extend="chartSettings"></ve-bar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./graph-average.ts" lang="ts"></script>
|
||||
<style src="./graph-average.scss" lang="scss"></style>
|
||||
@@ -1,8 +0,0 @@
|
||||
<template>
|
||||
<div id="graph-overall">
|
||||
<ve-line :data="convertChart" :extend="settings"></ve-line>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./graph-overall.ts" lang="ts"></script>
|
||||
<style src="./graph-overall.scss" lang="scss"></style>
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#graph-average
|
||||
#overall-bar
|
||||
{
|
||||
.graph
|
||||
{
|
||||
+1
-4
@@ -5,7 +5,7 @@ import Constants from '@/constants';
|
||||
|
||||
@Component({
|
||||
})
|
||||
export default class GraphAverage extends Vue
|
||||
export default class OverallBar extends Vue
|
||||
{
|
||||
// @ts-ignore
|
||||
@Prop({required: true}) courses: Course[];
|
||||
@@ -82,9 +82,6 @@ export default class GraphAverage extends Vue
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Remove this
|
||||
console.log(settings);
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div id="overall-bar">
|
||||
<ve-bar height="450px" class="graph"
|
||||
:extend="chartSettings"></ve-bar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./overall-bar.ts" lang="ts"></script>
|
||||
<style src="./overall-bar.scss" lang="scss"></style>
|
||||
@@ -0,0 +1,106 @@
|
||||
// Card
|
||||
.el-card.course-card
|
||||
{
|
||||
// Margins
|
||||
margin-right: 20px;
|
||||
margin-left: 20px;
|
||||
|
||||
// Height limit
|
||||
max-height: 90px;
|
||||
|
||||
// Limit name length
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.course-col-name
|
||||
{
|
||||
// Align left
|
||||
text-align: left;
|
||||
float: left;
|
||||
|
||||
.course-name
|
||||
{
|
||||
overflow: hidden;
|
||||
font-size: 22px;
|
||||
color: var(--main);
|
||||
}
|
||||
|
||||
.course-name:hover
|
||||
{
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.course-teacher
|
||||
{
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.course-col-grade
|
||||
{
|
||||
// Align right
|
||||
text-align: right;
|
||||
float: right;
|
||||
|
||||
// Adjust position
|
||||
margin-top: -2px;
|
||||
|
||||
.course-grade
|
||||
{
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
.course-updates
|
||||
{
|
||||
font-size: 14px;
|
||||
|
||||
.unread-number
|
||||
{
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.unread-text
|
||||
{
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.course-updates.unread
|
||||
{
|
||||
.unread-number
|
||||
{
|
||||
background: var(--unread);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.unread-text
|
||||
{
|
||||
color: var(--unread);
|
||||
}
|
||||
}
|
||||
|
||||
.course-updates.none
|
||||
{
|
||||
color: #999999;
|
||||
|
||||
.unread-number
|
||||
{
|
||||
background: #eeeeee;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-card__body
|
||||
{
|
||||
margin: auto 0;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||
import {Course} from '@/components/app/app';
|
||||
import {GPAUtils} from '@/utils/gpa-utils';
|
||||
import Constants from '@/constants';
|
||||
|
||||
@Component({
|
||||
})
|
||||
export default class OverallCourse extends Vue
|
||||
{
|
||||
// @ts-ignore
|
||||
@Prop({required: true}) course: Course;
|
||||
|
||||
private unread: number = -1;
|
||||
|
||||
/**
|
||||
* Count the number of unread assignments with cache
|
||||
*/
|
||||
countUnread(): number
|
||||
{
|
||||
if (this.unread == -1)
|
||||
{
|
||||
return this.unread = this.course.assignments.filter(a => a.unread).length;
|
||||
}
|
||||
else return this.unread;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div id="overall-course">
|
||||
<el-card class="course-card">
|
||||
<el-row>
|
||||
<el-col :span="6" class="course-col-name">
|
||||
<div class="course-name">
|
||||
{{course.name}}
|
||||
</div>
|
||||
<div class="course-teacher">
|
||||
{{course.teacherName}}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
|
||||
</el-col>
|
||||
<el-col :span="6" class="course-col-grade">
|
||||
<div class="course-grade">
|
||||
<span class="letter">{{course.letterGrade}} </span>
|
||||
<span class="numeric">{{course.numericGrade.toFixed(2)}}</span>
|
||||
<span class="percent">%</span>
|
||||
</div>
|
||||
<div class="course-updates" :class="countUnread() === 0 ? 'none' : 'unread'">
|
||||
<span class="unread-number">
|
||||
{{countUnread()}}
|
||||
</span>
|
||||
<span class="unread-text">
|
||||
new update{{countUnread() >= 2 ? 's' : ''}}
|
||||
</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./overall-course.ts" lang="ts"></script>
|
||||
<style src="./overall-course.scss" lang="scss"></style>
|
||||
+16
-7
@@ -3,7 +3,7 @@ import {Course} from '@/components/app/app';
|
||||
|
||||
@Component({
|
||||
})
|
||||
export default class GraphOverall extends Vue
|
||||
export default class OverallLine extends Vue
|
||||
{
|
||||
// @ts-ignore
|
||||
@Prop({required: true}) courses: Course[];
|
||||
@@ -142,17 +142,28 @@ export default class GraphOverall extends Vue
|
||||
}
|
||||
});
|
||||
|
||||
let score = 0;
|
||||
// Count total percentage (This is to avoid less than expected cases)
|
||||
// Eg. If HW = 25% and Quiz = 75%, I have 1 hw and 0 quiz
|
||||
// Without total percentage, the avg grade I get is 25%.
|
||||
let totalPercentage = 0;
|
||||
for (let type in course.grading.weightingMap)
|
||||
{
|
||||
if (typeScores[type] != undefined)
|
||||
{
|
||||
totalPercentage += course.grading.weightingMap[type];
|
||||
}
|
||||
}
|
||||
|
||||
// Count
|
||||
let score = 0;
|
||||
for (let type in typeScores)
|
||||
{
|
||||
score += typeScores[type] * course.grading.weightingMap[type] / typeCounts[type];
|
||||
console.log(type);
|
||||
let typeFactor = course.grading.weightingMap[type] / totalPercentage;
|
||||
score += typeScores[type] * typeFactor / typeCounts[type];
|
||||
}
|
||||
|
||||
// Add average to the row
|
||||
row[course.name] = score * 100;
|
||||
if (score != 0) row[course.name] = score * 100;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -160,8 +171,6 @@ export default class GraphOverall extends Vue
|
||||
rows.push(row);
|
||||
});
|
||||
|
||||
console.log(rows);
|
||||
|
||||
return {
|
||||
columns: columns,
|
||||
rows: rows
|
||||
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<div id="overall-line">
|
||||
<ve-line :data="convertChart" :extend="settings"></ve-line>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./overall-line.ts" lang="ts"></script>
|
||||
<style src="./overall-line.scss" lang="scss"></style>
|
||||
@@ -47,8 +47,8 @@
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
// Graph average
|
||||
.graph-average-card
|
||||
// Cards
|
||||
.el-card.overall-bar-card
|
||||
{
|
||||
margin-right: 20px;
|
||||
min-width: 170px;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||
import GraphOverall from '@/pages/overall/graph-overall/graph-overall';
|
||||
import GraphAverage from '@/pages/overall/graph-average/graph-average';
|
||||
import OverallLine from '@/pages/overall/overall-line/overall-line';
|
||||
import OverallBar from '@/pages/overall/overall-bar/overall-bar';
|
||||
import OverallCourse from '@/pages/overall/overall-course/overall-course';
|
||||
import {Course} from '@/components/app/app';
|
||||
import {GPAUtils} from '@/utils/gpa-utils';
|
||||
|
||||
@Component({
|
||||
components: {GraphOverall, GraphAverage}
|
||||
components: {OverallLine, OverallBar, OverallCourse}
|
||||
})
|
||||
export default class Overall extends Vue
|
||||
{
|
||||
|
||||
@@ -14,17 +14,18 @@
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="14">
|
||||
<el-card>
|
||||
<graph-overall :courses="courses"></graph-overall>
|
||||
<el-card class="overall-line-card">
|
||||
<overall-line :courses="courses"></overall-line>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="graph-average-card">
|
||||
<graph-average :courses="courses"></graph-average>
|
||||
<el-card class="overall-bar-card">
|
||||
<overall-bar :courses="courses"></overall-bar>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class=""></div>
|
||||
|
||||
<overall-course v-for="course in courses" :course="course"></overall-course>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export class CourseUtils
|
||||
if (course.assignments.length == 0) return;
|
||||
|
||||
// Skip if there are no grading scale
|
||||
if (course.grading.method == 'NOT_GRADED') return;
|
||||
// if (course.grading.method == 'NOT_GRADED') return;
|
||||
|
||||
// Add it to the list
|
||||
result.push(course);
|
||||
|
||||
@@ -102,4 +102,29 @@ export class GPAUtils
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the total-mean (total/max) average
|
||||
*
|
||||
* @param course Course
|
||||
*/
|
||||
public static getTotalMeanAverage(course: Course)
|
||||
{
|
||||
let score = 0;
|
||||
let max = 0;
|
||||
|
||||
// Loop through assignments
|
||||
course.assignments.forEach(assignment =>
|
||||
{
|
||||
// If assignment should be displayed
|
||||
if (assignment.complete != 'Complete') return;
|
||||
|
||||
// Record scores
|
||||
score += assignment.score;
|
||||
max += assignment.scoreMax;
|
||||
});
|
||||
|
||||
// Return
|
||||
return score / max * 100;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Grade} from '@/components/app/app';
|
||||
import {Assignment} from '@/components/app/app';
|
||||
|
||||
export default class JsonUtils
|
||||
{
|
||||
@@ -6,16 +6,17 @@ 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
|
||||
* @returns Assignment[] Filtered assignment grade object list
|
||||
*/
|
||||
public static filterAssignments(assignments: any): Grade[]
|
||||
public static filterAssignments(assignments: any): Assignment[]
|
||||
{
|
||||
let result: Grade[] = [];
|
||||
let result: Assignment[] = [];
|
||||
|
||||
assignments.assignments.forEach((assignment: any) =>
|
||||
{
|
||||
result.push(
|
||||
{
|
||||
id: assignment.assignment_id,
|
||||
type: assignment.assignment_type,
|
||||
description: assignment.assignment_description,
|
||||
date: assignment._date,
|
||||
@@ -23,6 +24,8 @@ export default class JsonUtils
|
||||
include: assignment.include_in_calculated_grade == 1,
|
||||
display: assignment.display_grade == 1,
|
||||
|
||||
unread: assignment.is_unread == 1,
|
||||
|
||||
scoreMax: assignment.maximum_score,
|
||||
score: +assignment.raw_score
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user