Compare commits

...

142 Commits

Author SHA1 Message Date
Hykilpikonna 1e28191e67 [U] Pre-Release v0.3.5.703 2019-10-20 20:27:46 -04:00
Hykilpikonna ef4d9d38e7 [O] Add '%' sign 2019-10-20 20:26:57 -04:00
Hykilpikonna 52626406bf [O] Use FormatUtils.limit 2019-10-20 20:26:18 -04:00
Hykilpikonna fec6c77dc2 [+] Create method to format string length 2019-10-20 20:23:47 -04:00
Hykilpikonna 4db8ee7837 [+] Add tooltip formatter 2019-10-20 20:20:58 -04:00
Hykilpikonna 9bfde0e39d [+] Add assignment description to the data 2019-10-20 20:20:45 -04:00
Hykilpikonna cfac8493cd [U] Pre-Release v0.3.5.697 2019-10-20 19:54:57 -04:00
Hykilpikonna 0d003a4f7e [O] Make new update clickable as well 2019-10-20 19:53:56 -04:00
Hykilpikonna 106a4759ae [O] Make clickable text cursor show as pointer 2019-10-20 19:53:42 -04:00
Hykilpikonna 06d09220c5 [O] Move clickable to global css 2019-10-20 19:46:19 -04:00
Hykilpikonna a19960b18f [O] Do nothing if not clickable 2019-10-20 19:44:06 -04:00
Hykilpikonna 656dd7d884 [+] Implement redirect on click in course-head 2019-10-20 19:42:06 -04:00
Hykilpikonna b965859537 [U] Use formatCourseIndex() in navigation 2019-10-20 19:41:50 -04:00
Hykilpikonna 707e96d2e9 [+] Encapsulate method to format course tab index 2019-10-20 19:41:28 -04:00
Hykilpikonna ab795674b2 [+] Add instance in App class 2019-10-20 19:36:38 -04:00
Hykilpikonna eb74feb659 [+] Create onClick function in course-head 2019-10-20 19:33:51 -04:00
Hykilpikonna 4b4c49b5c7 [+] Make courses in overall clickable 2019-10-20 19:29:52 -04:00
Hykilpikonna 1c134e02f9 [+] Add clickable prop to course-head 2019-10-20 19:29:18 -04:00
Hykilpikonna 85305f987f [O] Optimize course-head grid distribution 2019-10-20 19:23:12 -04:00
Hykilpikonna ec6e2b8c7e [F] Fix logical mistake 2019-10-20 19:22:55 -04:00
Hykilpikonna ab34435395 [U] Use VersionUtils.compare() in Login class 2019-10-20 19:17:21 -04:00
Hykilpikonna da96d846eb [+] Create function to compare version numbers 2019-10-20 19:11:22 -04:00
Hykilpikonna 9f6cb2658a [+] Create VersionUtils class 2019-10-20 19:00:47 -04:00
Hykilpikonna 33402012b6 [-] Remove deprecated file 2019-10-20 19:00:04 -04:00
Hykilpikonna 97cf1651f9 [+] Add MIN_SUPPORTED_VERSION 2019-10-20 18:59:26 -04:00
Hykilpikonna 97bfe69986 [O] Add logging before clearing cookies 2019-10-20 18:55:05 -04:00
Hykilpikonna b08fd7f433 [U] Use needToUpdateCookies() to check 2019-10-20 18:54:52 -04:00
Hykilpikonna 83c6a690ec [O] Optimize variable name lengths 2019-10-20 18:53:21 -04:00
Hykilpikonna cb54ae4dee [+] Create function to check version number 2019-10-20 18:53:03 -04:00
Hykilpikonna be2dcbf085 [O] Extend token saving period 2019-10-20 18:44:56 -04:00
Hykilpikonna 4df2726b95 [O] Make the chart full width (for now) 2019-10-20 18:40:35 -04:00
Hykilpikonna aa78e850bb [O] Adjust graph size 2019-10-20 18:39:50 -04:00
Hykilpikonna c05f18c12c [O] Add drop shadow 2019-10-20 18:38:16 -04:00
Hykilpikonna b4041a11a0 [O] Add cross tooltip pointer 2019-10-20 18:31:43 -04:00
Hykilpikonna bcf222a38b [O] Format x axis dates to MMM DD format 2019-10-20 18:28:07 -04:00
Hykilpikonna 7cfe928fcc [O] Adjust y label position 2019-10-20 18:25:12 -04:00
Hykilpikonna b5ad7c12e3 [O] Add '%' signs to y axis labels 2019-10-20 18:24:33 -04:00
Hykilpikonna 6286c5736e [O] Lighten legend label color 2019-10-20 18:20:35 -04:00
Hykilpikonna 0773d3ef0e [O] Make legend smaller 2019-10-20 18:20:21 -04:00
Hykilpikonna 7b912a9325 [O] Make title larger 2019-10-20 18:20:09 -04:00
Hykilpikonna 008155fa61 [O] Adjust legend icon width 2019-10-20 18:16:42 -04:00
Hykilpikonna 117cd9f568 [O] Adjust legend position 2019-10-20 18:16:15 -04:00
Hykilpikonna 3b14727f81 [O] Remove x axis name 2019-10-20 18:15:13 -04:00
Hykilpikonna 444a3733bd [O] Align legend to the bottom 2019-10-20 18:14:09 -04:00
Hykilpikonna de63ff9bb5 [+] Add legend 2019-10-20 18:13:56 -04:00
Hykilpikonna 2b2a4c59a8 [O] Adjust y-axis label position 2019-10-20 18:11:47 -04:00
Hykilpikonna 30f737bd7c [O] Center align y-axis label 2019-10-20 18:10:39 -04:00
Hykilpikonna 1e2c6ca66f [O] Make min x value of course-scatter a whole number 2019-10-20 18:04:43 -04:00
Hykilpikonna 7b13716ce3 [O] Make min x value of overall-line a whole number 2019-10-20 18:04:00 -04:00
Hykilpikonna 7a1b21a2ac [F] Fake course object usage to update computed value when tab switched 2019-10-20 17:36:12 -04:00
Hykilpikonna 2345bb6442 [F] Show only completed assignments 2019-10-20 17:30:00 -04:00
Hykilpikonna 47fd9e1db3 [O] Set max x value as today 2019-10-20 17:28:11 -04:00
Hykilpikonna f8eee081b3 [O] Adjust x axis label position 2019-10-20 17:26:28 -04:00
Hykilpikonna 84e0647c35 [O] Display x axis name 2019-10-20 17:23:22 -04:00
Hykilpikonna 60804dd98c [O] Add tooltip to scatter graph 2019-10-20 17:23:04 -04:00
Hykilpikonna 5789fac985 [-] Remove unnecessary :entend tag 2019-10-20 17:14:03 -04:00
Hykilpikonna 2a89c6316f [O] Override VE chart settings 2019-10-20 17:13:48 -04:00
Hykilpikonna 4c0a26a900 [-] Remove unused notification class 2019-10-20 17:02:22 -04:00
Hykilpikonna 6915617980 [+] Add inner top shadow to course card content 2019-10-20 17:01:58 -04:00
Hykilpikonna 0a09e14021 [-] Remove the todo 2019-10-20 16:58:48 -04:00
Hykilpikonna 34446494a9 [O] Round data 2019-10-20 13:06:30 -04:00
Hykilpikonna 8477c2f63b [O] Use percent score instead of 0 to 1 2019-10-20 13:00:52 -04:00
Hykilpikonna 105d1f7619 [+] Define colors 2019-10-20 12:59:32 -04:00
Hykilpikonna a4be712bd7 [F] Format date so that the chart can read it 2019-10-20 12:58:30 -04:00
Hykilpikonna 3a9a65920e [F] Use Array.from instead of map.forEach 2019-10-20 12:58:03 -04:00
Hykilpikonna ff62847758 [+] Debug with afterConfig() 2019-10-20 12:57:40 -04:00
Hykilpikonna 0207f617c2 [O] Optimize code format 2019-10-20 12:40:00 -04:00
Hykilpikonna 5c530952d3 [O] Add more colors 2019-10-20 12:39:46 -04:00
Hykilpikonna bdcefefd92 [O] Map assignments first 2019-10-20 12:12:25 -04:00
Hykilpikonna 68cebfa68c [O] Show tooltip 2019-10-20 12:12:00 -04:00
Hykilpikonna 94b337e772 [O] Change title of the chart 2019-10-20 12:06:39 -04:00
Hykilpikonna fd1f6223c0 [F] Fix date format 2019-10-20 12:06:29 -04:00
Hykilpikonna 0d86e461cd [+] Create function to format chart date 2019-10-20 12:03:01 -04:00
Hykilpikonna 9f06748b63 [+] Create format-utils 2019-10-20 11:59:41 -04:00
Hykilpikonna 9769fca6af [O] Label x and y axis 2019-10-20 11:57:37 -04:00
Hykilpikonna 416ef0991d [O] Make the x axis type time 2019-10-20 11:57:19 -04:00
Hykilpikonna 2150a563eb [F] Fix null case detection 2019-10-20 11:47:40 -04:00
Hykilpikonna 1ef08c17ec [F] Fix typo: 'course' instead of 'courses' 2019-10-20 11:46:34 -04:00
Hykilpikonna 15e375900c [+] Complete series 2019-10-20 11:45:42 -04:00
Hykilpikonna d70d54d3f7 [+] Create function to convert assignments to series data 2019-10-20 11:45:35 -04:00
Hykilpikonna 0159f639d2 [O] Optimize type with Map<string, Assignment[]> 2019-10-20 11:40:09 -04:00
Hykilpikonna 19d03536b5 [O] Specify return type 2019-10-20 11:36:35 -04:00
Hykilpikonna 214a716f16 [O] Clarify format in jsdocs 2019-10-20 11:35:11 -04:00
Hykilpikonna 1f5ecedf9f [+] Create function to map assignments 2019-10-20 11:34:48 -04:00
Hykilpikonna 3b52dab371 [+] Add course-scatter component to course-page 2019-10-20 09:53:01 -04:00
Hykilpikonna 40340a0abd [F] Fix "This dependency is not found" 2019-10-20 09:52:41 -04:00
Hykilpikonna c2311464f0 [+] Import course-scatter 2019-10-20 09:52:03 -04:00
Hykilpikonna f4f6fa2523 [+] Create course scatter vue component 2019-10-20 09:51:13 -04:00
Hykilpikonna 44d01eaec0 [O] Remove max height limit for course page 2019-10-20 09:48:30 -04:00
Hykilpikonna 300ff04f2e [F] Fix empty pathname detection with '/' 2019-10-14 22:14:46 -04:00
Hykilpikonna 6a34e9f706 [+] Add todo text 2019-10-14 22:13:22 -04:00
Hykilpikonna 97c8953dbd [-] Remove unnecessary overall-line.scss 2019-10-14 22:10:05 -04:00
Hykilpikonna 179d0bc567 [O] Make it scoped 2019-10-14 22:09:04 -04:00
Hykilpikonna 2d1242f3c0 [O] Inline overall-bar scss 2019-10-14 22:08:51 -04:00
Hykilpikonna b7f7e168b9 [O] Make css in unread-entry scoped 2019-10-14 22:07:47 -04:00
Hykilpikonna 83244ec496 [O] Make the css in overall-course and course-page scoped 2019-10-14 22:06:22 -04:00
Hykilpikonna eb3221fa4b [M] Move card padding to global css 2019-10-14 22:06:00 -04:00
Hykilpikonna 4e37ca4c44 [+] Add row on the expansion area 2019-10-14 21:36:57 -04:00
Hykilpikonna ae6172068e [+] Make course-page almost the same with overall-course 2019-10-14 21:35:55 -04:00
Hykilpikonna 65134c02af [+] Import course-head in course-page 2019-10-14 21:34:30 -04:00
Hykilpikonna fa9e1aae7c [O] Fix ' and " issue 2019-10-14 21:31:21 -04:00
Hykilpikonna 3d1401e1c2 [+] Add course-head element in overall-course 2019-10-14 20:33:25 -04:00
Hykilpikonna 8c102f5d5f [M] Move css to course-head 2019-10-14 20:33:05 -04:00
Hykilpikonna c4e1790444 [+] Import course-head in overall-course 2019-10-14 20:27:25 -04:00
Hykilpikonna 68c1fa1216 [M] Separate course-head from overall-course 2019-10-14 20:27:07 -04:00
Hykilpikonna eb094be9af [F] Move global css outside of overall scope 2019-10-14 20:26:13 -04:00
Hykilpikonna 8527e9860d [O] Make overall.scss scoped 2019-10-14 19:35:16 -04:00
Hykilpikonna ceb1ed1404 [O] Surround the course with a card 2019-10-14 19:34:12 -04:00
Hykilpikonna 968fa63f51 [O] Make course name font size 36px 2019-10-14 19:09:55 -04:00
Hykilpikonna 2d530d204e [O] Align text to the left 2019-10-14 19:08:21 -04:00
Hykilpikonna 65efde05ee [O] Add some margins 2019-10-14 19:08:12 -04:00
Hykilpikonna a5d261ac93 [+] Use grid to display basic info 2019-10-14 19:01:17 -04:00
Hykilpikonna b61cd3839a [O] Move v-if assignmentReady detection up an element 2019-10-14 18:45:01 -04:00
Hykilpikonna 665567bf88 [+] Bind the displayed course to the course page 2019-10-14 18:28:37 -04:00
Hykilpikonna a074a9fbed [+] Show course page only if select tab is a course 2019-10-14 18:27:37 -04:00
Hykilpikonna 0e30954f6e [+] Add course page 2019-10-14 18:27:09 -04:00
Hykilpikonna 0e98cffc64 [+] Update initial index 2019-10-14 18:26:26 -04:00
Hykilpikonna e7d5e766e3 [+] Add default path name when there are none 2019-10-14 18:02:02 -04:00
Hykilpikonna 88b0ef752c [+] Dynamically initialize first state based on pathname 2019-10-14 18:01:43 -04:00
Hykilpikonna 1a8fc9eab4 [-] Remove duplicate update 2019-10-14 17:57:56 -04:00
Hykilpikonna 76dd8f73e4 [O] Use updateIndex() instead 2019-10-14 17:57:43 -04:00
Hykilpikonna 4807c4babb [+] Encapsulate method to update index 2019-10-14 17:57:18 -04:00
Hykilpikonna 9b4f5291f0 [O] Use v-bind.sync to sync variable value 2019-10-14 17:57:04 -04:00
Hykilpikonna 5edc4c55f5 [-] Remove testing log 2019-10-14 16:43:21 -04:00
Hykilpikonna 563bf06b8e [-] Remove onNavigate event 2019-10-14 16:42:38 -04:00
Hykilpikonna cdaa2679d1 [M] Move pushState to navigation.ts as well 2019-10-14 16:42:09 -04:00
Hykilpikonna 4efac6023f [O] Bind activeIndex with selectedTab 2019-10-14 16:40:41 -04:00
Hykilpikonna c6e3806024 [M] Move history stuff to navigation.ts 2019-10-14 16:40:27 -04:00
Hykilpikonna e3df5b8e3b [+] Create history state listener 2019-10-14 16:35:00 -04:00
Hykilpikonna 5afc0aebd5 [+] Set state on start 2019-10-14 16:34:39 -04:00
Hykilpikonna d23446b409 [F] Push state after updating tab 2019-10-14 16:33:41 -04:00
Hykilpikonna bc63b457d3 [+] Clarify logging 2019-10-14 16:24:20 -04:00
Hykilpikonna 30a8d62402 [+] Keep last selected tabb 2019-10-14 16:23:16 -04:00
Hykilpikonna 349683de24 [F] Use absolute path for history 2019-10-14 09:40:18 -04:00
Hykilpikonna 06db7f48bd [F] Fix: can't replace all with replace() 2019-10-14 09:38:36 -04:00
Hykilpikonna 986a38d81d [O] Replace spaces in course name with '-' 2019-10-14 09:37:55 -04:00
Hykilpikonna 8b7ee75ca7 [+] Add course id to the navigation index 2019-10-14 09:36:37 -04:00
Hykilpikonna f59bed777b [O] Use all lowercase for course navigation 2019-10-14 09:36:02 -04:00
Hykilpikonna 87634f0df5 [+] Push history state on navigation 2019-10-14 09:32:19 -04:00
Hykilpikonna bb618e74c9 [+] Import course page in app 2019-10-14 09:31:54 -04:00
Hykilpikonna 92d2a7673b [O] Make text lighter 2019-10-13 22:25:48 -04:00
Hykilpikonna 232fe43eb5 [O] Add gradient to loading screen 2019-10-13 22:24:50 -04:00
30 changed files with 648 additions and 341 deletions
+45
View File
@@ -26,3 +26,48 @@
--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 -15
View File
@@ -9,6 +9,8 @@ import {HttpUtils} from '@/utils/http-utils';
import {CourseUtils} from '@/utils/course-utils';
import {GPAUtils} from '@/utils/gpa-utils';
import Loading from '@/components/loading/loading.vue';
import CoursePage from '@/pages/course/course-page';
/**
* Objects of this interface represent assignment grades.
@@ -58,7 +60,7 @@ export interface Course
}
@Component({
components: {Login, Navigation, Overall, Loading},
components: {Login, Navigation, Overall, Loading, CoursePage},
})
export default class App extends Vue
{
@@ -89,6 +91,9 @@ export default class App extends Vue
// Http Client
public static http: HttpUtils = new HttpUtils();
// Instance
public static instance: App;
/**
* This is called when the instance is created.
*/
@@ -96,6 +101,9 @@ export default class App extends Vue
{
// Show splash
console.log(Constants.SPLASH);
// Update instance
App.instance = this;
}
/**
@@ -249,20 +257,6 @@ export default class App extends Vue
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
*/
+8 -5
View File
@@ -2,14 +2,17 @@
<div id="app" class="theme-default">
<login v-if="showLogin" v-on:login:token="onLogin"></login>
<navigation :courses="filteredCourses"
v-on:sign-out="signOut()"
v-on:navigation:select="onNavigate">
:activeIndex.sync="selectedTab"
v-on:sign-out="signOut">
</navigation>
<div id="app-content">
<overall :courses="filteredCourses"
v-if="selectedTab === 'overall' && assignmentsReady">
<div id="app-content" v-if="assignmentsReady && loading === ''">
<overall v-if="selectedTab === 'overall'"
:courses="filteredCourses">
</overall>
<course-page v-if="selectedTab.split('/')[0] === 'course'"
:course="filteredCourses.find(c => +c.id === +selectedTab.split('/')[1])">
</course-page>
</div>
<loading v-if="loading !== ''" :text="loading" :error="loadingError"></loading>
+3 -2
View File
@@ -60,7 +60,8 @@
position: absolute;
top: 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;
}
@@ -92,7 +93,7 @@
margin-top: -5px;
font-size: 16px;
color: #eeeeee;
color: #f9f9f9;
}
#error-details
+19 -2
View File
@@ -2,6 +2,7 @@ import {Component, Prop, Vue} from 'vue-property-decorator';
import Constants from '@/constants';
import {HttpUtils} from '@/utils/http-utils';
import App from '@/components/app/app';
import VersionUtils from '@/utils/version-utils';
/**
* This component handles user login, and obtains data from the server.
@@ -24,8 +25,10 @@ export default class Login extends Vue
public created()
{
// 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
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.
*/
@@ -54,7 +71,7 @@ export default class Login extends Vue
if (response.success)
{
// 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);
// Call custom event with token
+53 -4
View File
@@ -1,5 +1,7 @@
import {Component, Prop, Vue} from 'vue-property-decorator';
import Constants from '@/constants';
import {Course} from '@/components/app/app';
import {CourseUtils} from '@/utils/course-utils';
/**
* This component is the top navigation bar
@@ -9,10 +11,41 @@ import Constants from '@/constants';
})
export default class Navigation extends Vue
{
public activeIndex: string = 'overall';
// @ts-ignore
@Prop() activeIndex: string;
@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.
*
@@ -22,10 +55,26 @@ export default class Navigation extends Vue
public onSelect(index: string, indexPath: string)
{
// Update active index
this.activeIndex = index;
this.updateIndex(index);
// Call custom event
this.$emit('navigation:select', this.activeIndex);
// Debug output TODO: Remove this
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);
}
/**
+3 -3
View File
@@ -1,7 +1,7 @@
<template>
<div id="navigation">
<el-menu style="margin-bottom: 10px;"
class="centered" :default-active="activeIndex" mode="horizontal" @select="onSelect">
<el-menu style="margin-bottom: 10px;" class="centered" mode="horizontal"
:default-active="activeIndex" @select="onSelect">
<!--div id="nav-title">
Veracross Analyzer
@@ -12,7 +12,7 @@
<el-submenu index="courses">
<template slot="title">Courses</template>
<el-menu-item v-for="course in courses"
:index="`course-${course.name}`"
:index="formatCourseIndex(course)"
:key="course.name">{{course.name}}</el-menu-item>
</el-submenu>
+19 -11
View File
@@ -3,12 +3,14 @@
*/
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 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';
@@ -27,13 +29,19 @@ export default class Constants
// Colors
colors:
[
'#18cea5',
'#4fa8ed',
'#f9627b',
'#ffb075',
'#005c9c',
'#bcabe0',
'#d36e75',
'#19d4ae',
'#5ab1ef',
'#fa6e86',
'#ffb980',
'#0067a6',
'#c4b4e4',
'#d87a80',
'#9cbbff',
'#d9d0c7',
'#87a997',
'#d49ea2',
'#5b4947',
'#7ba3a8',
'#fc97af',
'#919e8b',
'#d7ab82',
+20
View File
@@ -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);
}
+20 -6
View File
@@ -1,15 +1,29 @@
import {Component, Prop, Vue} from 'vue-property-decorator';
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';
import {Assignment, Course} from '@/components/app/app';
import CourseHead from '@/pages/overall/overall-course/course-head/course-head.vue';
import CourseScatter from '@/pages/course/course-scatter/course-scatter';
@Component({
components: {OverallLine, OverallBar, OverallCourse}
components: {CourseHead, CourseScatter}
})
export default class CoursePage extends Vue
{
// @ts-ignore
@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;
}
}
+17 -3
View File
@@ -1,7 +1,21 @@
<template>
<div id="course-page">
</div>
<el-card id="course-card" class="course-card">
<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>
<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 -1
View File
@@ -2,6 +2,7 @@ import {Component, Prop, Vue} from 'vue-property-decorator';
import {Course} from '@/components/app/app';
import {GPAUtils} from '@/utils/gpa-utils';
import Constants from '@/constants';
import {FormatUtils} from '@/utils/format-utils';
@Component({
})
@@ -40,7 +41,7 @@ export default class OverallBar extends Vue
rotate: 90,
// 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>
<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
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 Constants from '@/constants';
import UnreadEntry from '@/pages/overall/overall-course/unread-entry/unread-entry';
import CourseHead from '@/pages/overall/overall-course/course-head/course-head.vue';
@Component({
components: {UnreadEntry}
components: {UnreadEntry, CourseHead}
})
export default class OverallCourse extends Vue
{
@@ -1,37 +1,8 @@
<template>
<div id="overall-course">
<el-card class="course-card">
<div class="course-card-content main vertical-center">
<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">
&nbsp;
</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"
<course-head :clickable="true" :course="course" :unread="countUnread()"></course-head>
<div class="course-card-content expand"
v-if="countUnread() !== 0">
<unread-entry v-for="assignment in unreadAssignments"
:assignment="assignment"
@@ -44,4 +15,4 @@
</template>
<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>
<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:
{
min: (value: any) => value.min,
min: (value: any) => Math.floor(value.min),
max: (value: any) => value.max
}
};
@@ -5,4 +5,6 @@
</template>
<script src="./overall-line.ts" lang="ts"></script>
<style src="./overall-line.scss" lang="scss"></style>
<style lang="scss" scoped>
</style>
-28
View File
@@ -1,16 +1,4 @@
// Add some margins
.el-card
{
margin: 10px;
padding: 0;
}
.el-card.large
{
height: 494px;
}
.gpa-card
{
margin-left: 20px;
@@ -52,19 +40,3 @@
margin-right: 20px;
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;
}
+1 -1
View File
@@ -33,4 +33,4 @@
</template>
<script src="./overall.ts" lang="ts"></script>
<style src="./overall.scss" lang="scss"></style>
<style src="./overall.scss" lang="scss" scoped></style>
+11
View File
@@ -34,4 +34,15 @@ export class CourseUtils
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('-')}`;
}
}
-100
View File
@@ -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;
}
+29
View File
@@ -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) + '...'
}
}
+41
View File
@@ -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;
}
}