Compare commits
142 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e28191e67 | |||
| ef4d9d38e7 | |||
| 52626406bf | |||
| fec6c77dc2 | |||
| 4db8ee7837 | |||
| 9bfde0e39d | |||
| cfac8493cd | |||
| 0d003a4f7e | |||
| 106a4759ae | |||
| 06d09220c5 | |||
| a19960b18f | |||
| 656dd7d884 | |||
| b965859537 | |||
| 707e96d2e9 | |||
| ab795674b2 | |||
| eb74feb659 | |||
| 4b4c49b5c7 | |||
| 1c134e02f9 | |||
| 85305f987f | |||
| ec6e2b8c7e | |||
| ab34435395 | |||
| da96d846eb | |||
| 9f6cb2658a | |||
| 33402012b6 | |||
| 97cf1651f9 | |||
| 97bfe69986 | |||
| b08fd7f433 | |||
| 83c6a690ec | |||
| cb54ae4dee | |||
| be2dcbf085 | |||
| 4df2726b95 | |||
| aa78e850bb | |||
| c05f18c12c | |||
| b4041a11a0 | |||
| bcf222a38b | |||
| 7cfe928fcc | |||
| b5ad7c12e3 | |||
| 6286c5736e | |||
| 0773d3ef0e | |||
| 7b912a9325 | |||
| 008155fa61 | |||
| 117cd9f568 | |||
| 3b14727f81 | |||
| 444a3733bd | |||
| de63ff9bb5 | |||
| 2b2a4c59a8 | |||
| 30f737bd7c | |||
| 1e2c6ca66f | |||
| 7b13716ce3 | |||
| 7a1b21a2ac | |||
| 2345bb6442 | |||
| 47fd9e1db3 | |||
| f8eee081b3 | |||
| 84e0647c35 | |||
| 60804dd98c | |||
| 5789fac985 | |||
| 2a89c6316f | |||
| 4c0a26a900 | |||
| 6915617980 | |||
| 0a09e14021 | |||
| 34446494a9 | |||
| 8477c2f63b | |||
| 105d1f7619 | |||
| a4be712bd7 | |||
| 3a9a65920e | |||
| ff62847758 | |||
| 0207f617c2 | |||
| 5c530952d3 | |||
| bdcefefd92 | |||
| 68cebfa68c | |||
| 94b337e772 | |||
| fd1f6223c0 | |||
| 0d86e461cd | |||
| 9f06748b63 | |||
| 9769fca6af | |||
| 416ef0991d | |||
| 2150a563eb | |||
| 1ef08c17ec | |||
| 15e375900c | |||
| d70d54d3f7 | |||
| 0159f639d2 | |||
| 19d03536b5 | |||
| 214a716f16 | |||
| 1f5ecedf9f | |||
| 3b52dab371 | |||
| 40340a0abd | |||
| c2311464f0 | |||
| f4f6fa2523 | |||
| 44d01eaec0 | |||
| 300ff04f2e | |||
| 6a34e9f706 | |||
| 97c8953dbd | |||
| 179d0bc567 | |||
| 2d1242f3c0 | |||
| b7f7e168b9 | |||
| 83244ec496 | |||
| eb3221fa4b | |||
| 4e37ca4c44 | |||
| ae6172068e | |||
| 65134c02af | |||
| fa9e1aae7c | |||
| 3d1401e1c2 | |||
| 8c102f5d5f | |||
| c4e1790444 | |||
| 68c1fa1216 | |||
| eb094be9af | |||
| 8527e9860d | |||
| ceb1ed1404 | |||
| 968fa63f51 | |||
| 2d530d204e | |||
| 65efde05ee | |||
| a5d261ac93 | |||
| b61cd3839a | |||
| 665567bf88 | |||
| a074a9fbed | |||
| 0e30954f6e | |||
| 0e98cffc64 | |||
| e7d5e766e3 | |||
| 88b0ef752c | |||
| 1a8fc9eab4 | |||
| 76dd8f73e4 | |||
| 4807c4babb | |||
| 9b4f5291f0 | |||
| 5edc4c55f5 | |||
| 563bf06b8e | |||
| cdaa2679d1 | |||
| 4efac6023f | |||
| c6e3806024 | |||
| e3df5b8e3b | |||
| 5afc0aebd5 | |||
| d23446b409 | |||
| bc63b457d3 | |||
| 30a8d62402 | |||
| 349683de24 | |||
| 06db7f48bd | |||
| 986a38d81d | |||
| 8b7ee75ca7 | |||
| f59bed777b | |||
| 87634f0df5 | |||
| bb618e74c9 | |||
| 92d2a7673b | |||
| 232fe43eb5 |
@@ -26,3 +26,48 @@
|
|||||||
|
|
||||||
--font: 'Avenir', Helvetica, Arial, sans-serif;
|
--font: 'Avenir', Helvetica, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ##############
|
||||||
|
// # Global CSS #
|
||||||
|
// ##############
|
||||||
|
|
||||||
|
.el-card
|
||||||
|
{
|
||||||
|
margin: 10px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card.large
|
||||||
|
{
|
||||||
|
height: 494px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix padding
|
||||||
|
.el-card__body
|
||||||
|
{
|
||||||
|
padding-top: 0 !important;
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertical centering
|
||||||
|
.vertical-center
|
||||||
|
{
|
||||||
|
// Vertical center
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove card padding for styling issues
|
||||||
|
div.el-card.course-card > div.el-card__body
|
||||||
|
{
|
||||||
|
padding-right: 0 !important;
|
||||||
|
padding-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clickable text
|
||||||
|
.clickable:hover
|
||||||
|
{
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import {HttpUtils} from '@/utils/http-utils';
|
|||||||
import {CourseUtils} from '@/utils/course-utils';
|
import {CourseUtils} from '@/utils/course-utils';
|
||||||
import {GPAUtils} from '@/utils/gpa-utils';
|
import {GPAUtils} from '@/utils/gpa-utils';
|
||||||
import Loading from '@/components/loading/loading.vue';
|
import Loading from '@/components/loading/loading.vue';
|
||||||
|
import CoursePage from '@/pages/course/course-page';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Objects of this interface represent assignment grades.
|
* Objects of this interface represent assignment grades.
|
||||||
@@ -58,7 +60,7 @@ export interface Course
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {Login, Navigation, Overall, Loading},
|
components: {Login, Navigation, Overall, Loading, CoursePage},
|
||||||
})
|
})
|
||||||
export default class App extends Vue
|
export default class App extends Vue
|
||||||
{
|
{
|
||||||
@@ -89,6 +91,9 @@ export default class App extends Vue
|
|||||||
// Http Client
|
// Http Client
|
||||||
public static http: HttpUtils = new HttpUtils();
|
public static http: HttpUtils = new HttpUtils();
|
||||||
|
|
||||||
|
// Instance
|
||||||
|
public static instance: App;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is called when the instance is created.
|
* This is called when the instance is created.
|
||||||
*/
|
*/
|
||||||
@@ -96,6 +101,9 @@ export default class App extends Vue
|
|||||||
{
|
{
|
||||||
// Show splash
|
// Show splash
|
||||||
console.log(Constants.SPLASH);
|
console.log(Constants.SPLASH);
|
||||||
|
|
||||||
|
// Update instance
|
||||||
|
App.instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -249,20 +257,6 @@ export default class App extends Vue
|
|||||||
this.loading = message;
|
this.loading = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign out
|
* Sign out
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,14 +2,17 @@
|
|||||||
<div id="app" class="theme-default">
|
<div id="app" class="theme-default">
|
||||||
<login v-if="showLogin" v-on:login:token="onLogin"></login>
|
<login v-if="showLogin" v-on:login:token="onLogin"></login>
|
||||||
<navigation :courses="filteredCourses"
|
<navigation :courses="filteredCourses"
|
||||||
v-on:sign-out="signOut()"
|
:activeIndex.sync="selectedTab"
|
||||||
v-on:navigation:select="onNavigate">
|
v-on:sign-out="signOut">
|
||||||
</navigation>
|
</navigation>
|
||||||
|
|
||||||
<div id="app-content">
|
<div id="app-content" v-if="assignmentsReady && loading === ''">
|
||||||
<overall :courses="filteredCourses"
|
<overall v-if="selectedTab === 'overall'"
|
||||||
v-if="selectedTab === 'overall' && assignmentsReady">
|
:courses="filteredCourses">
|
||||||
</overall>
|
</overall>
|
||||||
|
<course-page v-if="selectedTab.split('/')[0] === 'course'"
|
||||||
|
:course="filteredCourses.find(c => +c.id === +selectedTab.split('/')[1])">
|
||||||
|
</course-page>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<loading v-if="loading !== ''" :text="loading" :error="loadingError"></loading>
|
<loading v-if="loading !== ''" :text="loading" :error="loadingError"></loading>
|
||||||
|
|||||||
@@ -60,7 +60,8 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
background: #00000065;
|
box-shadow: inset 0 0 1px 1px rgba(0,0,0,.1);
|
||||||
|
background: -webkit-linear-gradient(left, rgba(95, 18, 72, 0.4), rgba(42, 81, 117, 0.4) 100%);
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@@ -92,7 +93,7 @@
|
|||||||
|
|
||||||
margin-top: -5px;
|
margin-top: -5px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #eeeeee;
|
color: #f9f9f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
#error-details
|
#error-details
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {Component, Prop, Vue} from 'vue-property-decorator';
|
|||||||
import Constants from '@/constants';
|
import Constants from '@/constants';
|
||||||
import {HttpUtils} from '@/utils/http-utils';
|
import {HttpUtils} from '@/utils/http-utils';
|
||||||
import App from '@/components/app/app';
|
import App from '@/components/app/app';
|
||||||
|
import VersionUtils from '@/utils/version-utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component handles user login, and obtains data from the server.
|
* This component handles user login, and obtains data from the server.
|
||||||
@@ -24,8 +25,10 @@ export default class Login extends Vue
|
|||||||
public created()
|
public created()
|
||||||
{
|
{
|
||||||
// Check cookies version
|
// Check cookies version
|
||||||
if (!this.$cookies.isKey('va.version') || this.$cookies.get('va.version') != Constants.VERSION)
|
if (this.needToUpdateCookies())
|
||||||
{
|
{
|
||||||
|
console.log('Version Updated! Clearing cookies...');
|
||||||
|
|
||||||
// Clear all cookies
|
// Clear all cookies
|
||||||
this.$cookies.keys().forEach(key => this.$cookies.remove(key));
|
this.$cookies.keys().forEach(key => this.$cookies.remove(key));
|
||||||
}
|
}
|
||||||
@@ -38,6 +41,20 @@ export default class Login extends Vue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check version number
|
||||||
|
*
|
||||||
|
* @returns boolean Need to clear cookies or not
|
||||||
|
*/
|
||||||
|
public needToUpdateCookies(): boolean
|
||||||
|
{
|
||||||
|
// Version doesn't exist
|
||||||
|
if (!this.$cookies.isKey('va.version')) return true;
|
||||||
|
|
||||||
|
// If the current version is less than the min supported version
|
||||||
|
return VersionUtils.compare(this.$cookies.get('va.version'), Constants.MIN_SUPPORTED_VERSION) == -1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On click, sends username and password to the server.
|
* On click, sends username and password to the server.
|
||||||
*/
|
*/
|
||||||
@@ -54,7 +71,7 @@ export default class Login extends Vue
|
|||||||
if (response.success)
|
if (response.success)
|
||||||
{
|
{
|
||||||
// Save token to cookies
|
// Save token to cookies
|
||||||
this.$cookies.set('va.token', response.data, '7d');
|
this.$cookies.set('va.token', response.data, '27d');
|
||||||
this.$cookies.set('va.version', Constants.VERSION);
|
this.$cookies.set('va.version', Constants.VERSION);
|
||||||
|
|
||||||
// Call custom event with token
|
// Call custom event with token
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
import Constants from '@/constants';
|
import Constants from '@/constants';
|
||||||
|
import {Course} from '@/components/app/app';
|
||||||
|
import {CourseUtils} from '@/utils/course-utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component is the top navigation bar
|
* This component is the top navigation bar
|
||||||
@@ -9,10 +11,41 @@ import Constants from '@/constants';
|
|||||||
})
|
})
|
||||||
export default class Navigation extends Vue
|
export default class Navigation extends Vue
|
||||||
{
|
{
|
||||||
public activeIndex: string = 'overall';
|
// @ts-ignore
|
||||||
|
@Prop() activeIndex: string;
|
||||||
|
|
||||||
@Prop() courses: any;
|
@Prop() courses: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is called when the instance is created.
|
||||||
|
*/
|
||||||
|
public created()
|
||||||
|
{
|
||||||
|
// Set history state
|
||||||
|
let url = window.location.pathname;
|
||||||
|
if (url == '/' || url == '') url = '/overall';
|
||||||
|
window.history.replaceState({lastTab: url.substring(1)}, '', url);
|
||||||
|
|
||||||
|
// Update initial index
|
||||||
|
this.updateIndex(url.substring(1));
|
||||||
|
|
||||||
|
// Create history state listener
|
||||||
|
window.onpopstate = e =>
|
||||||
|
{
|
||||||
|
if (e.state)
|
||||||
|
{
|
||||||
|
// Restore previous tab
|
||||||
|
console.log(`onPopState: Current: ${this.activeIndex}, Previous: ${e.state.lastTab}`);
|
||||||
|
this.updateIndex(e.state.lastTab);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public formatCourseIndex(course: Course)
|
||||||
|
{
|
||||||
|
return CourseUtils.formatTabIndex(course);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is called when the selection changes.
|
* This function is called when the selection changes.
|
||||||
*
|
*
|
||||||
@@ -22,10 +55,26 @@ export default class Navigation extends Vue
|
|||||||
public onSelect(index: string, indexPath: string)
|
public onSelect(index: string, indexPath: string)
|
||||||
{
|
{
|
||||||
// Update active index
|
// Update active index
|
||||||
this.activeIndex = index;
|
this.updateIndex(index);
|
||||||
|
|
||||||
// Call custom event
|
// Debug output TODO: Remove this
|
||||||
this.$emit('navigation:select', this.activeIndex);
|
console.log(`onNavigate: Previous: ${this.activeIndex}, New: ${index}`);
|
||||||
|
|
||||||
|
// Check url
|
||||||
|
let url = `/${index}`;
|
||||||
|
|
||||||
|
// Push history state
|
||||||
|
window.history.pushState({lastTab: index}, '', url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update index
|
||||||
|
*
|
||||||
|
* @param newIndex New index
|
||||||
|
*/
|
||||||
|
private updateIndex(newIndex: string)
|
||||||
|
{
|
||||||
|
this.$emit('update:activeIndex', newIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="navigation">
|
<div id="navigation">
|
||||||
<el-menu style="margin-bottom: 10px;"
|
<el-menu style="margin-bottom: 10px;" class="centered" mode="horizontal"
|
||||||
class="centered" :default-active="activeIndex" mode="horizontal" @select="onSelect">
|
:default-active="activeIndex" @select="onSelect">
|
||||||
|
|
||||||
<!--div id="nav-title">
|
<!--div id="nav-title">
|
||||||
Veracross Analyzer
|
Veracross Analyzer
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<el-submenu index="courses">
|
<el-submenu index="courses">
|
||||||
<template slot="title">Courses</template>
|
<template slot="title">Courses</template>
|
||||||
<el-menu-item v-for="course in courses"
|
<el-menu-item v-for="course in courses"
|
||||||
:index="`course-${course.name}`"
|
:index="formatCourseIndex(course)"
|
||||||
:key="course.name">{{course.name}}</el-menu-item>
|
:key="course.name">{{course.name}}</el-menu-item>
|
||||||
</el-submenu>
|
</el-submenu>
|
||||||
|
|
||||||
|
|||||||
+19
-11
@@ -3,12 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
export default class Constants
|
export default class Constants
|
||||||
{
|
{
|
||||||
/**
|
/** Base url for api access */
|
||||||
* Base url for api access
|
|
||||||
*/
|
|
||||||
public static API_URL: string = 'https://va.hydev.org/api';
|
public static API_URL: string = 'https://va.hydev.org/api';
|
||||||
|
|
||||||
public static VERSION: string = '0.3.4.561';
|
/** Current version */
|
||||||
|
public static VERSION: string = '0.3.5.703';
|
||||||
|
|
||||||
|
/** Minimum version that still supports the same cookies */
|
||||||
|
public static MIN_SUPPORTED_VERSION: string = '0.3.4.561';
|
||||||
|
|
||||||
public static GITHUB: string = 'https://github.com/HyDevelop/VeracrossAnalyzer.Client';
|
public static GITHUB: string = 'https://github.com/HyDevelop/VeracrossAnalyzer.Client';
|
||||||
|
|
||||||
@@ -27,13 +29,19 @@ export default class Constants
|
|||||||
// Colors
|
// Colors
|
||||||
colors:
|
colors:
|
||||||
[
|
[
|
||||||
'#18cea5',
|
'#19d4ae',
|
||||||
'#4fa8ed',
|
'#5ab1ef',
|
||||||
'#f9627b',
|
'#fa6e86',
|
||||||
'#ffb075',
|
'#ffb980',
|
||||||
'#005c9c',
|
'#0067a6',
|
||||||
'#bcabe0',
|
'#c4b4e4',
|
||||||
'#d36e75',
|
'#d87a80',
|
||||||
|
'#9cbbff',
|
||||||
|
'#d9d0c7',
|
||||||
|
'#87a997',
|
||||||
|
'#d49ea2',
|
||||||
|
'#5b4947',
|
||||||
|
'#7ba3a8',
|
||||||
'#fc97af',
|
'#fc97af',
|
||||||
'#919e8b',
|
'#919e8b',
|
||||||
'#d7ab82',
|
'#d7ab82',
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Card
|
||||||
|
.el-card.course-card
|
||||||
|
{
|
||||||
|
// Margins
|
||||||
|
margin-right: 20px;
|
||||||
|
margin-left: 20px;
|
||||||
|
|
||||||
|
// Limit name length
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
// Expansion color
|
||||||
|
background: #f4f6f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-card-content.expand
|
||||||
|
{
|
||||||
|
// Top shadow
|
||||||
|
// https://stackoverflow.com/questions/17572619/inset-box-shadow-only-on-one-side
|
||||||
|
box-shadow: inset 0 7px 9px -7px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,29 @@
|
|||||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
import OverallLine from '@/pages/overall/overall-line/overall-line';
|
import {Assignment, Course} from '@/components/app/app';
|
||||||
import OverallBar from '@/pages/overall/overall-bar/overall-bar';
|
import CourseHead from '@/pages/overall/overall-course/course-head/course-head.vue';
|
||||||
import OverallCourse from '@/pages/overall/overall-course/overall-course';
|
import CourseScatter from '@/pages/course/course-scatter/course-scatter';
|
||||||
import {Course} from '@/components/app/app';
|
|
||||||
import {GPAUtils} from '@/utils/gpa-utils';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {OverallLine, OverallBar, OverallCourse}
|
components: {CourseHead, CourseScatter}
|
||||||
})
|
})
|
||||||
export default class CoursePage extends Vue
|
export default class CoursePage extends Vue
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@Prop({required: true}) course: Course;
|
@Prop({required: true}) course: Course;
|
||||||
|
|
||||||
|
private unread: number = -1;
|
||||||
|
private unreadAssignments: Assignment[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count the number of unread assignments with cache
|
||||||
|
*/
|
||||||
|
countUnread(): number
|
||||||
|
{
|
||||||
|
if (this.unread == -1)
|
||||||
|
{
|
||||||
|
this.unreadAssignments = this.course.assignments.filter(a => a.unread);
|
||||||
|
return this.unread = this.unreadAssignments.length;
|
||||||
|
}
|
||||||
|
else return this.unread;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="course-page">
|
<el-card id="course-card" class="course-card">
|
||||||
</div>
|
<course-head :course="course" :unread="countUnread()"></course-head>
|
||||||
|
|
||||||
|
<div class="course-card-content expand">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-card class="large overall-line-card vertical-center">
|
||||||
|
<course-scatter :course="course"></course-scatter>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="0">
|
||||||
|
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./course-page.ts" lang="ts"></script>
|
<script src="./course-page.ts" lang="ts"></script>
|
||||||
<style src="./course-page.scss" lang="scss"></style>
|
<style src="./course-page.scss" lang="scss" scoped></style>
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
|
import {Assignment, Course} from '@/components/app/app';
|
||||||
|
import {GPAUtils} from '@/utils/gpa-utils';
|
||||||
|
import Constants from '@/constants';
|
||||||
|
import {FormatUtils} from '@/utils/format-utils';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
})
|
||||||
|
export default class CourseScatter extends Vue
|
||||||
|
{
|
||||||
|
private static DOT = '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:{color}"></span>';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
@Prop({required: true}) course: Course;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override options
|
||||||
|
*
|
||||||
|
* @param options Original options (Unused)
|
||||||
|
*/
|
||||||
|
afterConfig(options: any)
|
||||||
|
{
|
||||||
|
return this.chartSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate settings
|
||||||
|
*/
|
||||||
|
get chartSettings()
|
||||||
|
{
|
||||||
|
// Map assignments
|
||||||
|
let map = this.mapAssignments();
|
||||||
|
|
||||||
|
// Scatter data point style
|
||||||
|
let itemStyle =
|
||||||
|
{
|
||||||
|
normal:
|
||||||
|
{
|
||||||
|
opacity: 0.8,
|
||||||
|
shadowBlur: 10,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 0,
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.2)'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create settings
|
||||||
|
let settings =
|
||||||
|
{
|
||||||
|
// Color
|
||||||
|
color: Constants.THEME.colors,
|
||||||
|
|
||||||
|
// Title
|
||||||
|
title:
|
||||||
|
{
|
||||||
|
show: true,
|
||||||
|
textStyle:
|
||||||
|
{
|
||||||
|
fontSize: 13
|
||||||
|
},
|
||||||
|
text: 'Assignments',
|
||||||
|
subtext: 'Assignment scores for ' + this.course.name,
|
||||||
|
x: 'center'
|
||||||
|
},
|
||||||
|
|
||||||
|
// X axis represents course names
|
||||||
|
xAxis:
|
||||||
|
{
|
||||||
|
type: 'time',
|
||||||
|
axisLabel:
|
||||||
|
{
|
||||||
|
formatter: (name: any) => moment(name).format('MMM DD')
|
||||||
|
},
|
||||||
|
max: FormatUtils.toChartDate(new Date())
|
||||||
|
},
|
||||||
|
|
||||||
|
// Y axis represents GPAs and MaxGPAs
|
||||||
|
yAxis:
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: 'Percentage Score',
|
||||||
|
nameLocation: 'center',
|
||||||
|
nameGap: 38,
|
||||||
|
axisLabel:
|
||||||
|
{
|
||||||
|
formatter: (name: any) => name + '%'
|
||||||
|
},
|
||||||
|
max: 100,
|
||||||
|
min: (value: any) => Math.floor(value.min) - 5
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tooltip
|
||||||
|
tooltip:
|
||||||
|
{
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer:
|
||||||
|
{
|
||||||
|
type: 'cross'
|
||||||
|
},
|
||||||
|
formatter: (ps: any[]) => ps[0].data[0] + '<br>' + ps.map(p =>
|
||||||
|
`${CourseScatter.DOT.replace('{color}', p.color)}
|
||||||
|
${FormatUtils.limit(p.data[2], 22)}: ${p.data[1]}%<br>`).join('')
|
||||||
|
},
|
||||||
|
|
||||||
|
// Legend
|
||||||
|
legend:
|
||||||
|
{
|
||||||
|
bottom: 24,
|
||||||
|
itemWidth: 14,
|
||||||
|
textStyle:
|
||||||
|
{
|
||||||
|
color: '#777',
|
||||||
|
fontSize: 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Data
|
||||||
|
series: Array.from(map, ([type, assignments]) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type: 'scatter',
|
||||||
|
name: type,
|
||||||
|
data: CourseScatter.assignmentsData(assignments),
|
||||||
|
itemStyle: itemStyle
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map assignments to {assignmentType, [assignment]} format.
|
||||||
|
*/
|
||||||
|
private mapAssignments(): Map<string, Assignment[]>
|
||||||
|
{
|
||||||
|
// Define map
|
||||||
|
let map = new Map();
|
||||||
|
|
||||||
|
// Move data to map
|
||||||
|
this.course.assignments.forEach(a =>
|
||||||
|
{
|
||||||
|
// Null case, create empty array
|
||||||
|
if (!map.has(a.type)) map.set(a.type, []);
|
||||||
|
|
||||||
|
// Put data
|
||||||
|
map.get(a.type).push(a);
|
||||||
|
});
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert assignments to series data
|
||||||
|
*
|
||||||
|
* @param assignments Assignments
|
||||||
|
*/
|
||||||
|
private static assignmentsData(assignments: Assignment[])
|
||||||
|
{
|
||||||
|
return assignments.filter(a => a.complete == 'Complete')
|
||||||
|
.map(a => [FormatUtils.toChartDate(a.date), (a.score / a.scoreMax * 100).toFixed(2), a.description]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<div id="course-scatter">
|
||||||
|
<ve-scatter height="450px" class="graph" :extend="{heyIUsedCourseObject: this.course.name}" :after-config="afterConfig"></ve-scatter>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./course-scatter.ts" lang="ts"></script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
#overall-bar
|
||||||
|
{
|
||||||
|
.graph
|
||||||
|
{
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
#overall-bar
|
|
||||||
{
|
|
||||||
.graph
|
|
||||||
{
|
|
||||||
margin-top: 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@ import {Component, Prop, Vue} from 'vue-property-decorator';
|
|||||||
import {Course} from '@/components/app/app';
|
import {Course} from '@/components/app/app';
|
||||||
import {GPAUtils} from '@/utils/gpa-utils';
|
import {GPAUtils} from '@/utils/gpa-utils';
|
||||||
import Constants from '@/constants';
|
import Constants from '@/constants';
|
||||||
|
import {FormatUtils} from '@/utils/format-utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
})
|
})
|
||||||
@@ -40,7 +41,7 @@ export default class OverallBar extends Vue
|
|||||||
rotate: 90,
|
rotate: 90,
|
||||||
|
|
||||||
// Truncate text length
|
// Truncate text length
|
||||||
formatter: (value: string) => value.length <= 16 ? value : value.substr(0, 14) + '...'
|
formatter: (value: string) => FormatUtils.limit(value, 16)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -6,4 +6,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./overall-bar.ts" lang="ts"></script>
|
<script src="./overall-bar.ts" lang="ts"></script>
|
||||||
<style src="./overall-bar.scss" lang="scss"></style>
|
<style lang="scss" scoped>
|
||||||
|
#overall-bar
|
||||||
|
{
|
||||||
|
.graph
|
||||||
|
{
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
<template>
|
||||||
|
<div id="course-head" class="course-card-content main vertical-center">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12" class="course-col-name">
|
||||||
|
<div v-if="clickable" class="course-name clickable" @click="redirect">{{course.name}}</div>
|
||||||
|
<div v-if="!clickable" class="course-name">{{course.name}}</div>
|
||||||
|
<div class="course-teacher">{{course.teacherName}}</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12" 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" @click="redirect" :class="unread === 0 ? 'none' : 'unread'">
|
||||||
|
<span class="unread-number">{{unread}}</span>
|
||||||
|
<span class="unread-text" :class="clickable ? 'clickable' : ''">
|
||||||
|
new update{{unread >= 2 ? 's' : ''}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
|
import App, {Course} from '@/components/app/app';
|
||||||
|
import {CourseUtils} from '@/utils/course-utils';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {}
|
||||||
|
})
|
||||||
|
export default class CourseHead extends Vue
|
||||||
|
{
|
||||||
|
// @ts-ignore
|
||||||
|
@Prop() unread: number;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
@Prop() course: Course;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
@Prop() clickable: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to the course page
|
||||||
|
*/
|
||||||
|
redirect()
|
||||||
|
{
|
||||||
|
if (!this.clickable) return;
|
||||||
|
App.instance.selectedTab = CourseUtils.formatTabIndex(this.course);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
// Main card content
|
||||||
|
.course-card-content.main
|
||||||
|
{
|
||||||
|
padding: 0 20px 0 20px;
|
||||||
|
height: 90px;
|
||||||
|
|
||||||
|
// Main color
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-col-name
|
||||||
|
{
|
||||||
|
// Align left
|
||||||
|
text-align: left;
|
||||||
|
float: left;
|
||||||
|
|
||||||
|
.course-name
|
||||||
|
{
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 22px;
|
||||||
|
color: var(--main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -13,120 +13,4 @@
|
|||||||
|
|
||||||
// Expansion color
|
// Expansion color
|
||||||
background: #f4f6f9;
|
background: #f4f6f9;
|
||||||
|
|
||||||
// Main card content
|
|
||||||
.course-card-content.main
|
|
||||||
{
|
|
||||||
padding: 0 20px 0 20px;
|
|
||||||
height: 90px;
|
|
||||||
|
|
||||||
// Main color
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expansion content
|
|
||||||
.course-card-content.expand.notification
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove card padding for styling issues
|
|
||||||
div.el-card.course-card > div.el-card__body
|
|
||||||
{
|
|
||||||
padding-right: 0 !important;
|
|
||||||
padding-left: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import App, {Assignment, Course} from '@/components/app/app';
|
|||||||
import {GPAUtils} from '@/utils/gpa-utils';
|
import {GPAUtils} from '@/utils/gpa-utils';
|
||||||
import Constants from '@/constants';
|
import Constants from '@/constants';
|
||||||
import UnreadEntry from '@/pages/overall/overall-course/unread-entry/unread-entry';
|
import UnreadEntry from '@/pages/overall/overall-course/unread-entry/unread-entry';
|
||||||
|
import CourseHead from '@/pages/overall/overall-course/course-head/course-head.vue';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {UnreadEntry}
|
components: {UnreadEntry, CourseHead}
|
||||||
})
|
})
|
||||||
export default class OverallCourse extends Vue
|
export default class OverallCourse extends Vue
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,37 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="overall-course">
|
<div id="overall-course">
|
||||||
<el-card class="course-card">
|
<el-card class="course-card">
|
||||||
<div class="course-card-content main vertical-center">
|
<course-head :clickable="true" :course="course" :unread="countUnread()"></course-head>
|
||||||
<el-row>
|
<div class="course-card-content expand"
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
<div class="course-card-content expand notification"
|
|
||||||
v-if="countUnread() !== 0">
|
v-if="countUnread() !== 0">
|
||||||
<unread-entry v-for="assignment in unreadAssignments"
|
<unread-entry v-for="assignment in unreadAssignments"
|
||||||
:assignment="assignment"
|
:assignment="assignment"
|
||||||
@@ -44,4 +15,4 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./overall-course.ts" lang="ts"></script>
|
<script src="./overall-course.ts" lang="ts"></script>
|
||||||
<style src="./overall-course.scss" lang="scss"></style>
|
<style src="./overall-course.scss" lang="scss" scoped></style>
|
||||||
|
|||||||
@@ -28,4 +28,4 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./unread-entry.ts" lang="ts"></script>
|
<script src="./unread-entry.ts" lang="ts"></script>
|
||||||
<style src="./unread-entry.scss" lang="scss"></style>
|
<style src="./unread-entry.scss" lang="scss" scoped></style>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export default class OverallLine extends Vue
|
|||||||
},
|
},
|
||||||
yAxis:
|
yAxis:
|
||||||
{
|
{
|
||||||
min: (value: any) => value.min,
|
min: (value: any) => Math.floor(value.min),
|
||||||
max: (value: any) => value.max
|
max: (value: any) => value.max
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,4 +5,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./overall-line.ts" lang="ts"></script>
|
<script src="./overall-line.ts" lang="ts"></script>
|
||||||
<style src="./overall-line.scss" lang="scss"></style>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,16 +1,4 @@
|
|||||||
|
|
||||||
// Add some margins
|
|
||||||
.el-card
|
|
||||||
{
|
|
||||||
margin: 10px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-card.large
|
|
||||||
{
|
|
||||||
height: 494px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gpa-card
|
.gpa-card
|
||||||
{
|
{
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
@@ -52,19 +40,3 @@
|
|||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
min-width: 170px;
|
min-width: 170px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix padding
|
|
||||||
.el-card__body
|
|
||||||
{
|
|
||||||
padding-top: 0 !important;
|
|
||||||
padding-bottom: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vertical centering
|
|
||||||
.vertical-center
|
|
||||||
{
|
|
||||||
// Vertical center
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -33,4 +33,4 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./overall.ts" lang="ts"></script>
|
<script src="./overall.ts" lang="ts"></script>
|
||||||
<style src="./overall.scss" lang="scss"></style>
|
<style src="./overall.scss" lang="scss" scoped></style>
|
||||||
|
|||||||
@@ -34,4 +34,15 @@ export class CourseUtils
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format course to tab index string
|
||||||
|
*
|
||||||
|
* @param course Course object
|
||||||
|
* @return string Tab index
|
||||||
|
*/
|
||||||
|
public static formatTabIndex(course: Course): string
|
||||||
|
{
|
||||||
|
return `course/${course.id}/${course.name.toLowerCase().split(' ').join('-')}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
// Initialize course specific variables
|
|
||||||
let courseScores: {[index: string]: any} = {};
|
|
||||||
let courseMaxScores: {[index: string]: any} = {};
|
|
||||||
let courseIndexes: {[index: string]: any} = {};
|
|
||||||
courses.forEach(course =>
|
|
||||||
{
|
|
||||||
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
|
|
||||||
courses.forEach(course =>
|
|
||||||
{
|
|
||||||
// Reversed loop through the assignments
|
|
||||||
for (let r = courseIndexes[course.name]; r >= 0; r--)
|
|
||||||
{
|
|
||||||
let assignment = course.assignments[r];
|
|
||||||
|
|
||||||
// If assignment should be displayed
|
|
||||||
if (assignment.complete != 'Complete') continue;
|
|
||||||
|
|
||||||
// Date is being looked at
|
|
||||||
let assignmentDate = new Date(assignment.date);
|
|
||||||
if (assignmentDate.getTime() == date.getTime())
|
|
||||||
{
|
|
||||||
// Detect grading method and record scores
|
|
||||||
if (course.grading.method == 'TOTAL_MEAN')
|
|
||||||
{
|
|
||||||
courseScores[course.name] += assignment.score;
|
|
||||||
courseMaxScores[course.name] += assignment.scoreMax;
|
|
||||||
}
|
|
||||||
else if (course.grading.method == 'PERCENT_TYPE')
|
|
||||||
{
|
|
||||||
let scale = course.grading.weightingMap[assignment.type];
|
|
||||||
courseScores[course.name] += assignment.score * scale;
|
|
||||||
courseMaxScores[course.name] += assignment.scoreMax * scale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
else if (course.grading.method == 'PERCENT_TYPE')
|
|
||||||
{
|
|
||||||
let typeScores: {[index: string]: any} = {};
|
|
||||||
let typeCounts: {[index: string]: any} = {};
|
|
||||||
|
|
||||||
// Loop through assignments
|
|
||||||
course.assignments.forEach(assignment =>
|
|
||||||
{
|
|
||||||
// If assignment should be displayed
|
|
||||||
if (assignment.complete != 'Complete') return;
|
|
||||||
|
|
||||||
// Date is being looked at
|
|
||||||
let assignmentDate = new Date(assignment.date);
|
|
||||||
if (assignmentDate.getTime() < date.getTime())
|
|
||||||
{
|
|
||||||
// Record scores
|
|
||||||
if (typeScores[assignment.type] == undefined) typeScores[assignment.type] = 0;
|
|
||||||
typeScores[assignment.type] += assignment.score / assignment.scoreMax;
|
|
||||||
|
|
||||||
if (typeCounts[assignment.type] == undefined) typeCounts[assignment.type] = 0;
|
|
||||||
typeCounts[assignment.type] ++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let score = 0;
|
|
||||||
|
|
||||||
// Count
|
|
||||||
for (let type in typeScores)
|
|
||||||
{
|
|
||||||
score += typeScores[type] * course.grading.weightingMap[type] / typeCounts[type];
|
|
||||||
console.log(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add average to the row
|
|
||||||
row[course.name] = score * 100;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
export class FormatUtils
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Convert date format to yyyy-mm-dd
|
||||||
|
*
|
||||||
|
* @param _date Date
|
||||||
|
*/
|
||||||
|
public static toChartDate(_date: string | Date)
|
||||||
|
{
|
||||||
|
// Convert to Date
|
||||||
|
let date: Date = _date instanceof Date ? _date : new Date(_date);
|
||||||
|
|
||||||
|
// Convert to yyyy-mm-dd
|
||||||
|
return moment(date).format('YYYY-MM-DD');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limit string length
|
||||||
|
*
|
||||||
|
* @param str String
|
||||||
|
* @param length Max length
|
||||||
|
*/
|
||||||
|
static limit(str: string, length: number): string
|
||||||
|
{
|
||||||
|
return str.length <= length ? str : str.substr(0, length - 2) + '...'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
export default class VersionUtils
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Compare two version numbers
|
||||||
|
*
|
||||||
|
* Eg.
|
||||||
|
* compare('0.1.2', '0.1.3') = -1
|
||||||
|
* compare('1.0.0', '0.1.3') = 1
|
||||||
|
* compare('0.0.1', '0.0.1') = 0
|
||||||
|
*
|
||||||
|
* @param ver1 Version 1
|
||||||
|
* @param ver2 Version 2
|
||||||
|
* @return number (-1 if ver1 < ver2), (1 if ver1 > ver2), (0 if equal)
|
||||||
|
*/
|
||||||
|
public static compare(ver1: string, ver2: string): number
|
||||||
|
{
|
||||||
|
// Equal case
|
||||||
|
if (ver1 == ver2) return 0;
|
||||||
|
|
||||||
|
// Split
|
||||||
|
let split1 = ver1.split('.');
|
||||||
|
let split2 = ver2.split('.');
|
||||||
|
|
||||||
|
// Detect each number
|
||||||
|
for (let i in split1)
|
||||||
|
{
|
||||||
|
// Get numbers
|
||||||
|
let num1 = split1[i];
|
||||||
|
let num2 = split2[i];
|
||||||
|
|
||||||
|
// Current number is equal
|
||||||
|
if (num1 == num2) continue;
|
||||||
|
|
||||||
|
// Current number is different
|
||||||
|
return +num1 < +num2 ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user