Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 970a058ba3 | |||
| 5d295db1b7 | |||
| b22aac7ca2 | |||
| eef29a4611 | |||
| 131952ed37 | |||
| 2e95c2550e | |||
| 0db6e0d693 | |||
| 27198ad4e0 | |||
| 3cbb6ebee6 | |||
| 2308df65b5 | |||
| c8f82cc991 | |||
| a6e1b905ed | |||
| 9432e9a806 | |||
| d90280a10f | |||
| db30b7f807 | |||
| 20910b1562 | |||
| 5659a049e5 | |||
| 085812d859 | |||
| e60a4669ac | |||
| ac84907a98 | |||
| 04ee69e8e2 | |||
| a2445aca6a | |||
| 336a58b23d | |||
| 5a5cf9bd4d | |||
| 44d262f457 | |||
| 27fda43373 | |||
| b3de3b8405 | |||
| 633918aa40 | |||
| 3bc59e87d3 | |||
| af7d9e9dca | |||
| 3b8884dc84 | |||
| 92158684c0 | |||
| b37de9cf24 | |||
| cd2dff5559 | |||
| d6a85af15c | |||
| 0d333879ca | |||
| 905db3c73a | |||
| c5dfad8be8 | |||
| c9454a3832 | |||
| c732d475f1 | |||
| a4b7e0fd46 | |||
| bcee069b32 | |||
| 182208f8c3 |
@@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>404: Redirecting...</title>
|
||||||
|
<meta http-equiv = "refresh" content = "0; url = https://vera.hydev.org/" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
404 Not Found! Redirecting to (<a href="https://vera.hydev.org">https://vera.hydev.org</a>)...
|
||||||
|
<script>
|
||||||
|
window.location.href = 'https://vera.hydev.org';
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
|
margin-bottom: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#app-content
|
#app-content
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {Component, Vue} from 'vue-property-decorator';
|
import {Component, Vue} from 'vue-property-decorator';
|
||||||
import Login from '@/components/login/login';
|
import Login from '@/components/login/login';
|
||||||
import Navigation from '@/components/navigation/navigation';
|
import Navigation from '@/components/navigation/navigation';
|
||||||
import Overall from '@/pages/overall/overall';
|
import Overall from '@/pages/overall/overall.vue';
|
||||||
import Constants from '@/constants';
|
import Constants from '@/constants';
|
||||||
import JsonUtils from '@/utils/json-utils';
|
import JsonUtils from '@/utils/json-utils';
|
||||||
import pWaitFor from 'p-wait-for';
|
import pWaitFor from 'p-wait-for';
|
||||||
@@ -9,7 +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';
|
import CoursePage from '@/pages/course/course-page.vue';
|
||||||
|
import {FormatUtils} from '@/utils/format-utils';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -146,6 +147,9 @@ export default class App extends Vue
|
|||||||
// Save courses
|
// Save courses
|
||||||
this.courses = response.data;
|
this.courses = response.data;
|
||||||
|
|
||||||
|
// Post processing
|
||||||
|
CourseUtils.postProcess(this.courses);
|
||||||
|
|
||||||
// Load assignments
|
// Load assignments
|
||||||
this.loadAssignments();
|
this.loadAssignments();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,11 +34,9 @@
|
|||||||
})
|
})
|
||||||
export default class Loading extends Vue
|
export default class Loading extends Vue
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
@Prop({required: true}) text: string;
|
||||||
@Prop() text: string;
|
|
||||||
|
|
||||||
// @ts-ignore
|
@Prop({required: true}) error: boolean;
|
||||||
@Prop() error: boolean;
|
|
||||||
|
|
||||||
getText()
|
getText()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
import {Component, Vue} from 'vue-property-decorator';
|
||||||
import Constants from '@/constants';
|
import Constants from '@/constants';
|
||||||
import {HttpUtils} from '@/utils/http-utils';
|
|
||||||
import App from '@/components/app/app';
|
import App from '@/components/app/app';
|
||||||
import VersionUtils from '@/utils/version-utils';
|
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.
|
||||||
* TODO: Press enter to login
|
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
components: {},
|
components: {},
|
||||||
@@ -24,21 +22,26 @@ export default class Login extends Vue
|
|||||||
*/
|
*/
|
||||||
public created()
|
public created()
|
||||||
{
|
{
|
||||||
// Check cookies version
|
|
||||||
if (this.needToUpdateCookies())
|
|
||||||
{
|
|
||||||
console.log('Version Updated! Clearing cookies...');
|
|
||||||
|
|
||||||
// Clear all cookies
|
|
||||||
this.$cookies.keys().forEach(key => this.$cookies.remove(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check login cookies
|
// Check login cookies
|
||||||
if (this.$cookies.isKey('va.token'))
|
if (this.$cookies.isKey('va.token'))
|
||||||
{
|
{
|
||||||
|
// Check cookies version
|
||||||
|
if (this.needToUpdateCookies())
|
||||||
|
{
|
||||||
|
console.log('Version Updated! Clearing cookies...');
|
||||||
|
|
||||||
|
// Clear all cookies
|
||||||
|
this.$cookies.keys().forEach(key => this.$cookies.remove(key));
|
||||||
|
}
|
||||||
|
|
||||||
// Already contains valid token / TODO: Validate
|
// Already contains valid token / TODO: Validate
|
||||||
|
// TODO: Update token each access
|
||||||
this.$emit('login:token', this.$cookies.get('va.token'));
|
this.$emit('login:token', this.$cookies.get('va.token'));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.log('Cookies doesn\'t exist');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,7 +75,7 @@ export default class Login extends Vue
|
|||||||
{
|
{
|
||||||
// Save token to cookies
|
// Save token to cookies
|
||||||
this.$cookies.set('va.token', response.data, '27d');
|
this.$cookies.set('va.token', response.data, '27d');
|
||||||
this.$cookies.set('va.version', Constants.VERSION);
|
this.$cookies.set('va.version', Constants.VERSION, '27d');
|
||||||
|
|
||||||
// Call custom event with token
|
// Call custom event with token
|
||||||
this.$emit('login:token', response.data);
|
this.$emit('login:token', response.data);
|
||||||
|
|||||||
@@ -5,23 +5,24 @@
|
|||||||
<img alt="Vue logo" src="../../assets/logo.png">
|
<img alt="Vue logo" src="../../assets/logo.png">
|
||||||
|
|
||||||
<h1>Veracross Analyzer</h1>
|
<h1>Veracross Analyzer</h1>
|
||||||
|
<form id="login-form">
|
||||||
|
<el-input v-model="username"
|
||||||
|
placeholder="School Username"
|
||||||
|
:class="{'input-error': error !== ''}"
|
||||||
|
@keyup.enter.native="onEnter">
|
||||||
|
</el-input>
|
||||||
|
|
||||||
<el-input v-model="username"
|
<el-input v-model="password"
|
||||||
placeholder="School Username"
|
placeholder="Veracross Password"
|
||||||
:class="{'input-error': error !== ''}"
|
show-password=""
|
||||||
@keyup.enter.native="onEnter">
|
:class="{'input-error': error !== ''}"
|
||||||
</el-input>
|
@keyup.enter.native="onEnter">
|
||||||
|
</el-input>
|
||||||
|
|
||||||
<el-input v-model="password"
|
<div class="el-form-item__error custom">{{error}}</div>
|
||||||
placeholder="Veracross Password"
|
|
||||||
show-password=""
|
|
||||||
:class="{'input-error': error !== ''}"
|
|
||||||
@keyup.enter.native="onEnter">
|
|
||||||
</el-input>
|
|
||||||
|
|
||||||
<div class="el-form-item__error custom">{{error}}</div>
|
<el-button plain type="primary" @click="onLoginClick" :loading="loading">Login</el-button>
|
||||||
|
</form>
|
||||||
<el-button plain type="primary" @click="onLoginClick" :loading="loading">Login</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -57,9 +57,6 @@
|
|||||||
left: 25%;
|
left: 25%;
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
box-shadow: 0 -2px 9px 0 #ecebeb;
|
box-shadow: 0 -2px 9px 0 #ecebeb;
|
||||||
|
|
||||||
// Trapezoid
|
|
||||||
transform: perspective(50px) rotateX(10deg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#prev-course
|
#prev-course
|
||||||
@@ -71,9 +68,6 @@
|
|||||||
left: 25%;
|
left: 25%;
|
||||||
padding-bottom: 2px;
|
padding-bottom: 2px;
|
||||||
box-shadow: 0 2px 9px 0 #ecebeb;
|
box-shadow: 0 2px 9px 0 #ecebeb;
|
||||||
|
|
||||||
// Trapezoid
|
|
||||||
transform: perspective(50px) rotateX(-10deg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-course-operations
|
.nav-course-operations
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
import Constants from '@/constants';
|
import App, {Course} from '@/components/app/app';
|
||||||
import {Course} from '@/components/app/app';
|
|
||||||
import {CourseUtils} from '@/utils/course-utils';
|
import {CourseUtils} from '@/utils/course-utils';
|
||||||
import {FormatUtils} from '@/utils/format-utils';
|
import {FormatUtils} from '@/utils/format-utils';
|
||||||
|
import pWaitFor from 'p-wait-for';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component is the top navigation bar
|
* This component is the top navigation bar
|
||||||
@@ -12,11 +12,9 @@ import {FormatUtils} from '@/utils/format-utils';
|
|||||||
})
|
})
|
||||||
export default class Navigation extends Vue
|
export default class Navigation extends Vue
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
@Prop({required: true}) activeIndex: string;
|
||||||
@Prop() activeIndex: string;
|
|
||||||
|
|
||||||
// @ts-ignore
|
@Prop({required: true}) courses: Course[];
|
||||||
@Prop() courses: Course[];
|
|
||||||
|
|
||||||
// Instance
|
// Instance
|
||||||
public static instance: Navigation;
|
public static instance: Navigation;
|
||||||
@@ -24,18 +22,21 @@ export default class Navigation extends Vue
|
|||||||
/**
|
/**
|
||||||
* This is called when the instance is created.
|
* This is called when the instance is created.
|
||||||
*/
|
*/
|
||||||
public created()
|
public mounted()
|
||||||
{
|
{
|
||||||
// Set instance
|
// Set instance
|
||||||
Navigation.instance = this;
|
Navigation.instance = this;
|
||||||
|
|
||||||
// Set history state
|
// Set history state
|
||||||
let url = window.location.pathname;
|
let url = '/' + window.location.hash;
|
||||||
if (url == '/' || url == '') url = '/overall';
|
if (url == '/' || url == '') url = '/#overall';
|
||||||
window.history.replaceState({lastTab: url.substring(1)}, '', url);
|
window.history.replaceState({lastTab: url.substring(1)}, '', url);
|
||||||
|
|
||||||
// Update initial index
|
// Update initial index after loading is done
|
||||||
this.updateIndex(url.substring(1), false);
|
pWaitFor(() => this.courses.length > 1 && App.instance.loading != '').then(() =>
|
||||||
|
{
|
||||||
|
this.updateIndex(url.substring(2), false);
|
||||||
|
});
|
||||||
|
|
||||||
// Create history state listener
|
// Create history state listener
|
||||||
window.onpopstate = e =>
|
window.onpopstate = e =>
|
||||||
@@ -80,11 +81,8 @@ export default class Navigation extends Vue
|
|||||||
// Record or not
|
// Record or not
|
||||||
if (history == null || history)
|
if (history == null || history)
|
||||||
{
|
{
|
||||||
// Debug output TODO: Remove this
|
|
||||||
console.log(`onNavigate: Previous: ${this.activeIndex}, New: ${newIndex}`);
|
|
||||||
|
|
||||||
// Check url
|
// Check url
|
||||||
let url = `/${newIndex}`;
|
let url = `/#${newIndex}`;
|
||||||
|
|
||||||
// Push history state
|
// Push history state
|
||||||
window.history.pushState({lastTab: newIndex}, '', url);
|
window.history.pushState({lastTab: newIndex}, '', url);
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ export default class Constants
|
|||||||
public static API_URL: string = 'https://va.hydev.org/api';
|
public static API_URL: string = 'https://va.hydev.org/api';
|
||||||
|
|
||||||
/** Current version */
|
/** Current version */
|
||||||
public static VERSION: string = '0.3.6.741';
|
public static VERSION: string = '0.3.6.784';
|
||||||
|
|
||||||
/** Minimum version that still supports the same cookies */
|
/** Minimum version that still supports the same cookies */
|
||||||
public static MIN_SUPPORTED_VERSION: string = '0.3.4.561';
|
public static MIN_SUPPORTED_VERSION: string = '0.3.4.561';
|
||||||
|
|||||||
+2
-2
@@ -1,10 +1,10 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import ElementUI from 'element-ui';
|
import ElementUI from 'element-ui';
|
||||||
const VCharts = require('v-charts');
|
|
||||||
|
|
||||||
import App from './components/app/app.vue';
|
import App from './components/app/app.vue';
|
||||||
import VueCookies from 'vue-cookies';
|
import VueCookies from 'vue-cookies';
|
||||||
|
|
||||||
|
const VCharts = require('v-charts');
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
// Use Element UI
|
// Use Element UI
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
|
||||||
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: {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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-card id="course-card" class="course-card">
|
<el-card id="course-card" class="course-card">
|
||||||
<course-head :course="course" :unread="countUnread()"></course-head>
|
<course-head :clickable="false" :course="course" :unread="countUnread()"></course-head>
|
||||||
|
|
||||||
<div class="course-card-content expand">
|
<div class="course-card-content expand">
|
||||||
<el-row>
|
<el-row>
|
||||||
@@ -13,9 +13,44 @@
|
|||||||
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
<!--AssignmentEntry v-for="assignment in course.assignments"
|
||||||
|
:assignment="assignment" :unread="false">
|
||||||
|
</AssignmentEntry-->
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./course-page.ts" lang="ts"></script>
|
<script lang="ts">
|
||||||
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
|
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';
|
||||||
|
import AssignmentEntry from '@/pages/overall/overall-course/assignment-entry/assignment-entry.vue';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {AssignmentEntry, CourseHead, CourseScatter}
|
||||||
|
})
|
||||||
|
export default class CoursePage extends Vue
|
||||||
|
{
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<style src="./course-page.scss" lang="scss" scoped></style>
|
<style src="./course-page.scss" lang="scss" scoped></style>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
import {Assignment, Course} from '@/components/app/app';
|
import {Assignment, Course} from '@/components/app/app';
|
||||||
import {GPAUtils} from '@/utils/gpa-utils';
|
|
||||||
import Constants from '@/constants';
|
import Constants from '@/constants';
|
||||||
import {FormatUtils} from '@/utils/format-utils';
|
import {FormatUtils} from '@/utils/format-utils';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
@@ -11,7 +10,6 @@ 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>';
|
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;
|
@Prop({required: true}) course: Course;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {FormatUtils} from '@/utils/format-utils';
|
|||||||
})
|
})
|
||||||
export default class OverallBar extends Vue
|
export default class OverallBar extends Vue
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
|
||||||
@Prop({required: true}) courses: Course[];
|
@Prop({required: true}) courses: Course[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+37
-3
@@ -5,6 +5,7 @@
|
|||||||
<span class="month">{{getMoment(assignment.date).format("MMM Do")}}</span>
|
<span class="month">{{getMoment(assignment.date).format("MMM Do")}}</span>
|
||||||
<span class="now">({{getMoment(assignment.date).fromNow()}})</span>
|
<span class="now">({{getMoment(assignment.date).fromNow()}})</span>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :span="15" class="description">
|
<el-col :span="15" class="description">
|
||||||
<span class="type entry-box"
|
<span class="type entry-box"
|
||||||
:style="`border-color: var(--assignment-type-${assignment.typeId})`">
|
:style="`border-color: var(--assignment-type-${assignment.typeId})`">
|
||||||
@@ -12,6 +13,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span class="text">{{assignment.description}}</span>
|
<span class="text">{{assignment.description}}</span>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :span="6" class="grade">
|
<el-col :span="6" class="grade">
|
||||||
<span class="percent entry-box">
|
<span class="percent entry-box">
|
||||||
{{(assignment.score / assignment.scoreMax * 100).toFixed(1)}}
|
{{(assignment.score / assignment.scoreMax * 100).toFixed(1)}}
|
||||||
@@ -19,13 +21,45 @@
|
|||||||
</span>
|
</span>
|
||||||
<span class="score entry-box">{{assignment.score}}</span>
|
<span class="score entry-box">{{assignment.score}}</span>
|
||||||
<span class="max entry-box">{{assignment.scoreMax}}</span>
|
<span class="max entry-box">{{assignment.scoreMax}}</span>
|
||||||
|
|
||||||
<el-button class="mark-as-read" size="mini" type="text"
|
<el-button class="mark-as-read" size="mini" type="text"
|
||||||
icon="el-icon-close" @click="markAsRead">
|
icon="el-icon-close" @click="markAsRead" v-if="unread">
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./unread-entry.ts" lang="ts"></script>
|
<script lang="ts">
|
||||||
<style src="./unread-entry.scss" lang="scss" scoped></style>
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
|
import {Assignment} from '@/components/app/app';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class AssignmentEntry extends Vue
|
||||||
|
{
|
||||||
|
@Prop({required: true}) assignment: Assignment;
|
||||||
|
|
||||||
|
@Prop({default: false}) unread: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a date to the displayed format
|
||||||
|
*
|
||||||
|
* @param date Date
|
||||||
|
*/
|
||||||
|
getMoment(date: string)
|
||||||
|
{
|
||||||
|
return moment(new Date(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark this unread assignment as read
|
||||||
|
*/
|
||||||
|
markAsRead()
|
||||||
|
{
|
||||||
|
// Call custom event
|
||||||
|
this.$emit('mark-as-read', this.assignment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style src="./assignment-entry.scss" lang="scss" scoped></style>
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,23 +25,18 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
import App, {Course} from '@/components/app/app';
|
import {Course} from '@/components/app/app';
|
||||||
import {CourseUtils} from '@/utils/course-utils';
|
import {CourseUtils} from '@/utils/course-utils';
|
||||||
import Navigation from '@/components/navigation/navigation';
|
import Navigation from '@/components/navigation/navigation';
|
||||||
|
|
||||||
@Component({
|
@Component
|
||||||
components: {}
|
|
||||||
})
|
|
||||||
export default class CourseHead extends Vue
|
export default class CourseHead extends Vue
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
@Prop({required: true}) unread: number;
|
||||||
@Prop() unread: number;
|
|
||||||
|
|
||||||
// @ts-ignore
|
@Prop({required: true}) course: Course;
|
||||||
@Prop() course: Course;
|
|
||||||
|
|
||||||
// @ts-ignore
|
@Prop({required: true}) clickable: boolean;
|
||||||
@Prop() clickable: boolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirect to the course page
|
* Redirect to the course page
|
||||||
@@ -54,97 +49,4 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style src="./course-head.scss" lang="scss"></style>
|
||||||
// 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>
|
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
import App, {Assignment, Course} from '@/components/app/app';
|
import App, {Assignment, Course} from '@/components/app/app';
|
||||||
import {GPAUtils} from '@/utils/gpa-utils';
|
import AssignmentEntry from '@/pages/overall/overall-course/assignment-entry/assignment-entry.vue';
|
||||||
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';
|
import CourseHead from '@/pages/overall/overall-course/course-head/course-head.vue';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {UnreadEntry, CourseHead}
|
components: {UnreadEntry: AssignmentEntry, CourseHead}
|
||||||
})
|
})
|
||||||
export default class OverallCourse extends Vue
|
export default class OverallCourse extends Vue
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
|
||||||
@Prop({required: true}) course: Course;
|
@Prop({required: true}) course: Course;
|
||||||
|
|
||||||
private unread: number = -1;
|
private unread: number = -1;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<unread-entry v-for="assignment in unreadAssignments"
|
<unread-entry v-for="assignment in unreadAssignments"
|
||||||
:assignment="assignment"
|
:assignment="assignment"
|
||||||
:key="assignment.id"
|
:key="assignment.id"
|
||||||
|
unread="true"
|
||||||
v-on:mark-as-read="markAsRead">
|
v-on:mark-as-read="markAsRead">
|
||||||
</unread-entry>
|
</unread-entry>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
|
||||||
import {Assignment, Course} from '@/components/app/app';
|
|
||||||
import moment from 'moment';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
})
|
|
||||||
export default class UnreadEntry extends Vue
|
|
||||||
{
|
|
||||||
// @ts-ignore
|
|
||||||
@Prop({required: true}) assignment: Assignment;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format a date to the displayed format
|
|
||||||
*
|
|
||||||
* @param date Date
|
|
||||||
*/
|
|
||||||
getMoment(date: string)
|
|
||||||
{
|
|
||||||
return moment(new Date(date));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark this unread assignment as read
|
|
||||||
*/
|
|
||||||
markAsRead()
|
|
||||||
{
|
|
||||||
// Call custom event
|
|
||||||
this.$emit('mark-as-read', this.assignment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
import {Course} from '@/components/app/app';
|
import {Course} from '@/components/app/app';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
})
|
})
|
||||||
export default class OverallLine extends Vue
|
export default class OverallLine extends Vue
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
|
||||||
@Prop({required: true}) courses: Course[];
|
@Prop({required: true}) courses: Course[];
|
||||||
|
|
||||||
private settings =
|
private settings =
|
||||||
@@ -38,9 +38,8 @@ export default class OverallLine extends Vue
|
|||||||
// Zoom bar
|
// Zoom bar
|
||||||
dataZoom:
|
dataZoom:
|
||||||
[
|
[
|
||||||
// TODO: Calculate real value for startValue
|
|
||||||
{
|
{
|
||||||
startValue: '9/13/2019'
|
startValue: moment().subtract(30, 'days').format('M/D/YYYY')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'inside'
|
type: 'inside'
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
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';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
components: {OverallLine, OverallBar, OverallCourse}
|
|
||||||
})
|
|
||||||
export default class Overall extends Vue
|
|
||||||
{
|
|
||||||
// @ts-ignore
|
|
||||||
@Prop({required: true}) courses: Course[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is called to get gpa since I can't import another
|
|
||||||
* class in the Vue file.
|
|
||||||
*/
|
|
||||||
public getGPA()
|
|
||||||
{
|
|
||||||
return GPAUtils.getGPA(this.courses);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,5 +32,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./overall.ts" lang="ts"></script>
|
<script lang="ts">
|
||||||
|
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';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {OverallLine, OverallBar, OverallCourse}
|
||||||
|
})
|
||||||
|
export default class Overall extends Vue
|
||||||
|
{
|
||||||
|
@Prop({required: true}) courses: Course[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called to get gpa since I can't import another
|
||||||
|
* class in the Vue file.
|
||||||
|
*/
|
||||||
|
public getGPA()
|
||||||
|
{
|
||||||
|
return GPAUtils.getGPA(this.courses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<style src="./overall.scss" lang="scss" scoped></style>
|
<style src="./overall.scss" lang="scss" scoped></style>
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import Vue, { VNode } from 'vue';
|
import Vue, {VNode} from 'vue';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace JSX {
|
namespace JSX {
|
||||||
|
|||||||
Vendored
+4
-3
@@ -1,4 +1,5 @@
|
|||||||
declare module '*.vue' {
|
declare module '*.vue'
|
||||||
import Vue from 'vue';
|
{
|
||||||
export default Vue;
|
import Vue from 'vue';
|
||||||
|
export default Vue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,21 @@
|
|||||||
import {Course} from '@/components/app/app';
|
import {Course} from '@/components/app/app';
|
||||||
|
import {FormatUtils} from '@/utils/format-utils';
|
||||||
|
|
||||||
|
const LEVEL_AP = {level: 'AP', scaleUp: 1};
|
||||||
|
const LEVEL_H = {level: 'H', scaleUp: 0.75};
|
||||||
|
const LEVEL_A = {level: 'A', scaleUp: 0.5};
|
||||||
|
const LEVEL_CP = {level: 'CP', scaleUp: 0.25};
|
||||||
|
const LEVEL_CLUB = {level: 'Club', scaleUp: -1};
|
||||||
|
|
||||||
|
const UNKNOWN_COURSE_LIST = new Map();
|
||||||
|
UNKNOWN_COURSE_LIST.set('Piano Masterclass', LEVEL_H);
|
||||||
|
UNKNOWN_COURSE_LIST.set('Multivariable Calculus with Differential Equations', LEVEL_H);
|
||||||
|
UNKNOWN_COURSE_LIST.set('Introduction to Algorithmic Thinking and Computational Technologies', LEVEL_A);
|
||||||
|
UNKNOWN_COURSE_LIST.set('Ceramics 1', LEVEL_CP);
|
||||||
|
UNKNOWN_COURSE_LIST.set('Ceramics 2', LEVEL_A);
|
||||||
|
UNKNOWN_COURSE_LIST.set('Sculpture', LEVEL_CP);
|
||||||
|
UNKNOWN_COURSE_LIST.set('Drawing', LEVEL_CP);
|
||||||
|
UNKNOWN_COURSE_LIST.set('Painting', LEVEL_CP);
|
||||||
|
|
||||||
export class CourseUtils
|
export class CourseUtils
|
||||||
{
|
{
|
||||||
@@ -19,8 +36,8 @@ export class CourseUtils
|
|||||||
// Skip future or past courses
|
// Skip future or past courses
|
||||||
if (course.status != 'active') return;
|
if (course.status != 'active') return;
|
||||||
|
|
||||||
// Skip courses without levels
|
// Skip courses without levels TODO: Ask for user input
|
||||||
if (course.level == 'None') return;
|
if (course.level == 'None' || course.level == 'Unknown' || course.scaleUp == -1) return;
|
||||||
|
|
||||||
// Skip courses without graded assignments
|
// Skip courses without graded assignments
|
||||||
if (course.assignments.filter(a => a.complete == 'Complete').length == 0) return;
|
if (course.assignments.filter(a => a.complete == 'Complete').length == 0) return;
|
||||||
@@ -45,4 +62,66 @@ export class CourseUtils
|
|||||||
{
|
{
|
||||||
return `course/${course.id}/${course.name.toLowerCase().split(' ').join('-')}`;
|
return `course/${course.id}/${course.name.toLowerCase().split(' ').join('-')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post process course list
|
||||||
|
*
|
||||||
|
* @param courses Course list
|
||||||
|
*/
|
||||||
|
public static postProcess(courses: Course[])
|
||||||
|
{
|
||||||
|
for (let course of courses)
|
||||||
|
{
|
||||||
|
// Parse name
|
||||||
|
course.name = FormatUtils.parseText(course.name).trim();
|
||||||
|
|
||||||
|
// Detect level
|
||||||
|
let level = this.detectLevel(course.name);
|
||||||
|
if (level != undefined)
|
||||||
|
{
|
||||||
|
course.level = level.level;
|
||||||
|
course.scaleUp = level.scaleUp;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
course.level = 'Unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect course level based on course name
|
||||||
|
*
|
||||||
|
* @param name Course name
|
||||||
|
*/
|
||||||
|
private static detectLevel(name: string)
|
||||||
|
{
|
||||||
|
// Common ones
|
||||||
|
if (name.startsWith('AP')) return LEVEL_AP;
|
||||||
|
if (name.endsWith(' H')) return LEVEL_H;
|
||||||
|
if (name.endsWith(' A')) return LEVEL_A;
|
||||||
|
if (name.endsWith(' CP')) return LEVEL_CP;
|
||||||
|
if (name.startsWith('HS ')) return LEVEL_CLUB;
|
||||||
|
if (name.startsWith('MS ')) return LEVEL_CLUB;
|
||||||
|
|
||||||
|
// Uncommon ones
|
||||||
|
let lower = name.toLowerCase();
|
||||||
|
|
||||||
|
if (name.startsWith('Pre-AP')) return LEVEL_AP;
|
||||||
|
if (lower.endsWith(' acc')) return LEVEL_A;
|
||||||
|
if (name.endsWith('H')) return LEVEL_H;
|
||||||
|
if (name.endsWith('A')) return LEVEL_A;
|
||||||
|
if (name.endsWith('CP')) return LEVEL_CP;
|
||||||
|
|
||||||
|
// Even more uncommon
|
||||||
|
if (lower.includes('honors')) return LEVEL_H;
|
||||||
|
if (lower.includes('accelerated')) return LEVEL_A;
|
||||||
|
if (name.includes('Advanced')) return LEVEL_A;
|
||||||
|
|
||||||
|
// Unknown course list
|
||||||
|
if (UNKNOWN_COURSE_LIST.has(name)) return UNKNOWN_COURSE_LIST.get(name);
|
||||||
|
|
||||||
|
// Really unknown
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,4 +37,14 @@ export class FormatUtils
|
|||||||
{
|
{
|
||||||
return str.replace(/\w\S*/g, s => s.charAt(0).toUpperCase() + s.substr(1).toLowerCase())
|
return str.replace(/\w\S*/g, s => s.charAt(0).toUpperCase() + s.substr(1).toLowerCase())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse html text
|
||||||
|
*
|
||||||
|
* @param str
|
||||||
|
*/
|
||||||
|
public static parseText(str: string): string
|
||||||
|
{
|
||||||
|
return str.replace(/&/g, '&');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -24,7 +24,10 @@
|
|||||||
"dom",
|
"dom",
|
||||||
"dom.iterable",
|
"dom.iterable",
|
||||||
"scripthost"
|
"scripthost"
|
||||||
]
|
],
|
||||||
|
|
||||||
|
// Custom
|
||||||
|
"strictPropertyInitialization": false
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
|
|||||||
Reference in New Issue
Block a user