From d53deb54869871f853d14d5950d56612d30b78a6 Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Tue, 6 Feb 2018 12:12:24 +0100 Subject: [PATCH] subscription Sidebar component / Routing rework Routing is ow dynmacilly handled by selector components. E.g. the LayoutSelector will render as the correct Layout depending on the logged in users role. This allows to simply route to e.g. '/home/ and the StartPageSelector will determine if StudentPage or TutorStartPage should be displayed. --- frontend/README.md | 2 + frontend/src/App.vue | 7 +- frontend/src/components/BaseLayout.vue | 33 ++++---- .../submission_notes/SubmissionCorrection.vue | 16 +++- .../subscriptions/SubscriptionList.vue | 28 ++++--- frontend/src/main.js | 2 +- frontend/src/pages/LayoutSelector.vue | 39 ++++++++++ frontend/src/pages/Login.vue | 7 +- frontend/src/pages/PageNotFound.vue | 17 ++++ frontend/src/pages/StartPageSelector.vue | 35 +++++++++ frontend/src/pages/SubscriptionWorkPage.vue | 77 +++++++++++-------- frontend/src/pages/student/StudentLayout.vue | 12 +-- frontend/src/pages/student/StudentPage.vue | 2 +- .../pages/student/StudentSubmissionPage.vue | 5 +- frontend/src/pages/tutor/TutorLayout.vue | 16 ++-- frontend/src/router/index.js | 71 +++++++++-------- frontend/src/store/actions.js | 4 +- frontend/src/store/modules/authentication.js | 10 ++- frontend/src/store/modules/student-page.js | 47 ++++++----- .../src/store/modules/submission-notes.js | 5 ++ frontend/src/store/modules/ui.js | 21 +++++ frontend/src/store/mutations.js | 4 +- frontend/src/store/store.js | 29 +++---- 23 files changed, 322 insertions(+), 167 deletions(-) create mode 100644 frontend/src/pages/LayoutSelector.vue create mode 100644 frontend/src/pages/PageNotFound.vue create mode 100644 frontend/src/pages/StartPageSelector.vue create mode 100644 frontend/src/store/modules/ui.js diff --git a/frontend/README.md b/frontend/README.md index 216c4d32..b2a08d5a 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,5 +1,7 @@ # Frontend +[](https://standardjs.com) + > Vue.js frontend for Grady ## Build Setup diff --git a/frontend/src/App.vue b/frontend/src/App.vue index accd0862..681f5c0a 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -19,11 +19,11 @@ </v-card-text> <v-card-actions> <v-btn flat color="grey lighten-0" - @click="logout" + @click="logout" >Logout now</v-btn> <v-spacer/> <v-btn flat color="blue darken-2" - @click="continueWork" + @click="continueWork" >Continue</v-btn> </v-card-actions> </v-card> @@ -34,6 +34,7 @@ <script> import {mapState} from 'vuex' + export default { name: 'app', data () { @@ -76,7 +77,7 @@ mounted () { const oneAndHalfMinute = 90 * 1e3 this.timer = setInterval(() => { - if (this.$route.path !== '/') { + if (this.$route.name !== 'login' && this.$store.getters.isLoggedIn) { if (Date.now() > this.tokenCreationTime + this.jwtTimeDelta) { this.logoutDialog = false this.$store.dispatch('logout', "You've been logged out due to inactivity.") diff --git a/frontend/src/components/BaseLayout.vue b/frontend/src/components/BaseLayout.vue index 717af16c..f54d42f3 100644 --- a/frontend/src/components/BaseLayout.vue +++ b/frontend/src/components/BaseLayout.vue @@ -39,9 +39,11 @@ class="grady-toolbar" > <v-toolbar-title> - <v-avatar> - <img src="../assets/brand.png"> - </v-avatar> + <router-link to="/home"> + <v-avatar> + <img src="../assets/brand.png"> + </v-avatar> + </router-link> </v-toolbar-title> <span class="pl-2 grady-speak">{{ gradySpeak }}</span> <div class="toolbar-content"> @@ -49,23 +51,15 @@ </div> <v-btn color="blue darken-1" to="/" @click.native="logout">Logout</v-btn> </v-toolbar> - <v-content> - <router-view></router-view> - </v-content> </div> </template> <script> import { mapGetters, mapState } from 'vuex' - // import {act} from '@/store/actions' + import {uiMut} from '@/store/modules/ui' export default { name: 'base-layout', - data () { - return { - mini: false - } - }, computed: { ...mapGetters([ 'gradySpeak' @@ -73,17 +67,20 @@ ...mapState({ username: state => state.authentication.username, userRole: state => state.authentication.userRole - }) + }), + mini: { + get: function () { + return this.$store.state.ui.sideBarCollapsed + }, + set: function (collapsed) { + this.$store.commit(uiMut.SET_SIDEBAR_COLLAPSED, collapsed) + } + } }, methods: { logout () { this.$store.dispatch('logout') } - }, - watch: { - mini: function () { - this.$emit('sidebarMini', this.mini) - } } } </script> diff --git a/frontend/src/components/submission_notes/SubmissionCorrection.vue b/frontend/src/components/submission_notes/SubmissionCorrection.vue index 83611dd1..f2d1151c 100644 --- a/frontend/src/components/submission_notes/SubmissionCorrection.vue +++ b/frontend/src/components/submission_notes/SubmissionCorrection.vue @@ -6,7 +6,7 @@ slot="header" /> <template slot="table-content"> - <tr v-for="(code, lineNo) in submission" :key="lineNo"> + <tr v-for="(code, lineNo) in submission" :key="`${submissionObj.pk}${lineNo}`"> <submission-line :code="code" :line-no="lineNo" @@ -80,6 +80,8 @@ assignment: { type: Object }, + + // either pass in an assignment or a submission and feedback submissionWithoutAssignment: { type: Object }, @@ -138,13 +140,23 @@ this.$store.commit(subNotesNamespace(subNotesMut.RESET_STATE)) this.$store.commit(subNotesNamespace(subNotesMut.SET_RAW_SUBMISSION), this.submissionObj.text) this.$store.commit(subNotesNamespace(subNotesMut.SET_ORIG_FEEDBACK), this.feedbackObj) + } + }, + watch: { + assignment: function () { + this.init() this.$nextTick(() => { window.PR.prettyPrint() }) } }, - mounted () { + created () { this.init() + }, + mounted () { + this.$nextTick(() => { + window.PR.prettyPrint() + }) } } </script> diff --git a/frontend/src/components/subscriptions/SubscriptionList.vue b/frontend/src/components/subscriptions/SubscriptionList.vue index bf7d2c6f..bc165e4b 100644 --- a/frontend/src/components/subscriptions/SubscriptionList.vue +++ b/frontend/src/components/subscriptions/SubscriptionList.vue @@ -1,11 +1,12 @@ <template> <v-card> - <v-toolbar color="teal"> - <v-toolbar-title> + <v-toolbar color="teal" :dense="sidebar"> + <v-toolbar-side-icon><v-icon>assignment</v-icon></v-toolbar-side-icon> + <v-toolbar-title v-if="!sidebar || (sidebar && !sideBarCollapsed)"> Your subscriptions </v-toolbar-title> </v-toolbar> - <v-list> + <v-list :dense="sidebar" v-if="!sidebar || (sidebar && !sideBarCollapsed)"> <div v-for="item in subscriptionTypes" :key="item.type"> <v-list-tile> <v-list-tile-content> @@ -44,10 +45,11 @@ </v-list-tile-action> </v-list-tile> <v-list-tile + exact v-if="subscriptions[item.type].length > 0 && item.expanded" v-for="subscription in subscriptions[item.type]" :key="subscription.pk" - @click="workOnSubscription(subscription)" + :to="{name: 'subscription', params: {pk: subscription['pk']}}" > <v-list-tile-content class="ml-3"> {{subscription.query_key ? subscription.query_key : 'Active'}} @@ -62,12 +64,18 @@ </template> <script> - import {mapGetters, mapActions} from 'vuex' + import {mapGetters, mapActions, mapState} from 'vuex' import SubscriptionCreation from '@/components/subscriptions/SubscriptionCreation' export default { components: {SubscriptionCreation}, name: 'subscription-list', + props: { + sidebar: { + type: Boolean, + default: false + } + }, data () { return { subscriptionCreateMenu: {}, @@ -80,7 +88,7 @@ { name: 'Random', type: 'random', - description: 'Random submissions of all mut.', + description: 'Random submissions of all types.', expanded: true }, { @@ -108,6 +116,9 @@ } }, computed: { + ...mapState({ + sideBarCollapsed: state => state.ui.sideBarCollapsed + }), ...mapGetters({ subscriptions: 'getSubscriptionsGroupedByType' }), @@ -130,10 +141,7 @@ 'updateSubmissionTypes', 'getCurrentAssignment', 'getExamTypes' - ]), - workOnSubscription (subscription) { - this.$router.push(`tutor/subscription/${subscription['pk']}`) - } + ]) }, created () { if (Object.keys(this.$store.state.subscriptions).length === 0) { diff --git a/frontend/src/main.js b/frontend/src/main.js index 5cdc9a46..cb7060e5 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -20,7 +20,7 @@ Vue.use(Notifications) Vue.config.productionTip = false /* eslint-disable no-new */ -new Vue({ +export default new Vue({ el: '#app', store, router, diff --git a/frontend/src/pages/LayoutSelector.vue b/frontend/src/pages/LayoutSelector.vue new file mode 100644 index 00000000..231a7d29 --- /dev/null +++ b/frontend/src/pages/LayoutSelector.vue @@ -0,0 +1,39 @@ +<template> + <div> + <component :is="layout"></component> + <v-content> + <router-view></router-view> + </v-content> + </div> +</template> + +<script> + import {mapGetters} from 'vuex' + import TutorLayout from '@/pages/tutor/TutorLayout' + import StudentLayout from '@/pages/student/StudentLayout' + + export default { + components: { + StudentLayout, + TutorLayout}, + name: 'layout-selector', + computed: { + ...mapGetters([ + 'isStudent', + 'isTutor', + 'isReviewer' + ]), + layout () { + if (this.isStudent) { + return 'student-layout' + } else if (this.isTutor) { + return 'tutor-layout' + } + } + } + } +</script> + +<style scoped> + +</style> diff --git a/frontend/src/pages/Login.vue b/frontend/src/pages/Login.vue index cf5fcfaf..1e6bdd9d 100644 --- a/frontend/src/pages/Login.vue +++ b/frontend/src/pages/Login.vue @@ -64,12 +64,7 @@ this.loading = true this.getJWT(this.credentials).then(() => { this.getUserRole().then(() => { - switch (this.userRole) { - case 'Student': this.$router.push('/student') - break - case 'Tutor': this.$router.push('/tutor') - break - } + this.$router.push({name: 'home'}) }) this.getJWTTimeDelta() this.loading = false diff --git a/frontend/src/pages/PageNotFound.vue b/frontend/src/pages/PageNotFound.vue new file mode 100644 index 00000000..ba712e1f --- /dev/null +++ b/frontend/src/pages/PageNotFound.vue @@ -0,0 +1,17 @@ +<template> + <v-container fill-height> + <v-layout align-center justify-center> + <h1>Ooops, something went wrong. There is nothing here.</h1> + </v-layout> + </v-container> +</template> + +<script> + export default { + name: 'page-not-found' + } +</script> + +<style scoped> + +</style> diff --git a/frontend/src/pages/StartPageSelector.vue b/frontend/src/pages/StartPageSelector.vue new file mode 100644 index 00000000..bfcb052b --- /dev/null +++ b/frontend/src/pages/StartPageSelector.vue @@ -0,0 +1,35 @@ +<template> + <component :is="startPage"> + </component> +</template> + +<script> + import {mapGetters} from 'vuex' + import TutorStartPage from '@/pages/tutor/TutorStartPage' + import StudentPage from '@/pages/student/StudentPage' + export default { + name: 'start-page-selector', + components: { + StudentPage, + TutorStartPage + }, + computed: { + ...mapGetters([ + 'isStudent', + 'isTutor', + 'isReviewer' + ]), + startPage () { + if (this.isStudent) { + return 'student-page' + } else if (this.isTutor) { + return 'tutor-start-page' + } + } + } + } +</script> + +<style scoped> + +</style> diff --git a/frontend/src/pages/SubscriptionWorkPage.vue b/frontend/src/pages/SubscriptionWorkPage.vue index 77d7e763..6f1094d0 100644 --- a/frontend/src/pages/SubscriptionWorkPage.vue +++ b/frontend/src/pages/SubscriptionWorkPage.vue @@ -1,11 +1,11 @@ <template> <v-layout - v-if="loaded" row wrap > <v-flex xs12 md6> <submission-correction :assignment="currentAssignment" + :key="subscription.pk" @feedbackCreated="startWorkOnNextAssignment" class="ma-4 autofocus" /> @@ -14,6 +14,7 @@ <v-flex md6> <submission-type v-bind="submissionType" + :key="submissionType.pk" :reverse="true" :expandedByDefault="{ Description: false, Solution: true }" /> @@ -24,36 +25,52 @@ <script> import SubmissionCorrection from '@/components/submission_notes/SubmissionCorrection' import SubmissionType from '@/components/SubmissionType' + import store from '@/store/store' + import {mut} from '@/store/mutations' + + function onRouteEnterOrUpdate (to, from, next) { + if (to.name === 'subscription') { + let subscription = store.state.subscriptions[to.params['pk']] + if (!subscription.currentAssignment) { + store.dispatch('getCurrentAssignment', subscription['pk']).then(() => { + next() + }) + store.dispatch('getNextAssignment', subscription['pk']) + } else { + next() + } + } + } export default { components: { SubmissionType, - SubmissionCorrection}, - name: 'subscription-work-page', - data () { - return { - currentAssignment: {}, - nextAssignment: {}, - loaded: false - } + SubmissionCorrection }, + name: 'subscription-work-page', computed: { subscription () { return this.$store.state.subscriptions[this.$route.params['pk']] }, + currentAssignment () { + return this.subscription.currentAssignment + }, submission () { - return this.loaded ? this.currentAssignment.submission : {} + return this.currentAssignment.submission }, submissionType () { - return this.loaded ? this.$store.state.submissionTypes[this.submission['type_pk']] : {} + return this.$store.state.submissionTypes[this.submission['type_pk']] } }, + beforeRouteEnter (to, from, next) { + onRouteEnterOrUpdate(to, from, next) + }, + beforeRouteUpdate (to, from, next) { + onRouteEnterOrUpdate(to, from, next) + }, methods: { prefetchAssignment () { - this.$store.dispatch('getNextAssignment', this.subscription['pk']).then(assignment => { - this.nextAssignment = assignment - }).catch(err => { - this.nextAssignment = null + this.$store.dispatch('getNextAssignment', this.subscription['pk']).catch(err => { if (err.statusCode === 410) { this.$notify({ title: 'Last submission here!', @@ -64,22 +81,20 @@ }) }, startWorkOnNextAssignment () { - this.currentAssignment = this.nextAssignment - this.prefetchAssignment() - } - }, - created () { - if (!this.subscription.currentAssignment) { - this.$store.dispatch('getCurrentAssignment', this.subscription['pk']).then(assignment => { - this.currentAssignment = assignment - this.loaded = true - }).catch(err => { - console.log('Unable to fetch current Assignment. Err:' + err) - }) - this.prefetchAssignment() - } else { - this.currentAssignment = this.subscription.currentAssignment - this.loaded = true + if (this.subscription.nextAssignment) { + this.$store.commit(mut.UPDATE_ASSIGNMENT, { + key: 'currentAssignment', + assignment: this.subscription.nextAssignment, + subscriptionPk: this.subscription['pk'] + }) + this.prefetchAssignment() + } else { + this.$notify({ + title: 'This subscription has ended', + text: 'This subscription has ended', + type: 'info' + }) + } } } } diff --git a/frontend/src/pages/student/StudentLayout.vue b/frontend/src/pages/student/StudentLayout.vue index 82566130..c3100896 100644 --- a/frontend/src/pages/student/StudentLayout.vue +++ b/frontend/src/pages/student/StudentLayout.vue @@ -1,5 +1,5 @@ <template> - <base-layout @sidebarMini="mini = $event"> + <base-layout> <template slot="header"> {{ module_reference }} @@ -49,17 +49,16 @@ name: 'student-layout', data () { return { - mini: false, generalNavItems: [ { name: 'Overview', icon: 'home', - route: '/student/' + route: '/home' }, { name: 'Statistics', icon: 'show_chart', - route: '/student/' + route: '/home' } ] } @@ -69,14 +68,15 @@ module_reference: state => state.studentPage.exam.module_reference, submissions: state => state.studentPage.submissionsForList, exam: state => state.studentPage.exam, - visited: state => state.studentPage.visited + visited: state => state.studentPage.visited, + mini: state => state.ui.sideBarCollapsed }), submissionNavItems: function () { return this.submissions.map((sub, index) => { return { name: sub.type.name, id: sub.type.pk, - route: `/student/submission/${sub.type.pk}` + route: `/submission/${sub.type.pk}` } }) } diff --git a/frontend/src/pages/student/StudentPage.vue b/frontend/src/pages/student/StudentPage.vue index f42887fa..392bcbc9 100644 --- a/frontend/src/pages/student/StudentPage.vue +++ b/frontend/src/pages/student/StudentPage.vue @@ -4,7 +4,7 @@ <template v-if="loaded"> <v-flex md10 mt-5 offset-xs1> <h2>Submissions of {{ studentName }}</h2> - <submission-list :submissions="submissions"></submission-list> + <submission-list :submissions="submissions"/> </v-flex> </template> </v-layout> diff --git a/frontend/src/pages/student/StudentSubmissionPage.vue b/frontend/src/pages/student/StudentSubmissionPage.vue index 2a4ca5e0..e7946ff8 100644 --- a/frontend/src/pages/student/StudentSubmissionPage.vue +++ b/frontend/src/pages/student/StudentSubmissionPage.vue @@ -48,6 +48,7 @@ import SubmissionLine from '@/components/submission_notes/base/SubmissionLine' import FeedbackComment from '@/components/submission_notes/base/FeedbackComment' import {studentPageMut} from '@/store/modules/student-page' + import {subNotesMut} from '@/store/modules/submission-notes' export default { name: 'student-submission-page', @@ -66,7 +67,7 @@ id: function () { return this.$route.params.id }, - ...mapGetters([ + ...mapGetters('submissionNotes', [ 'submission' ]), ...mapState({ @@ -80,7 +81,7 @@ methods: { onRouteMountOrUpdate (routeId) { this.$store.commit(studentPageMut.SET_VISITED, { index: routeId, visited: true }) - this.$store.commit(studentPageMut.SET_RAW_SUBMISSION, + this.$store.commit('submissionNotes/' + subNotesMut.SET_RAW_SUBMISSION, this.$store.state.studentPage.submissionData[this.id].text) } }, diff --git a/frontend/src/pages/tutor/TutorLayout.vue b/frontend/src/pages/tutor/TutorLayout.vue index 3966da43..df21a894 100644 --- a/frontend/src/pages/tutor/TutorLayout.vue +++ b/frontend/src/pages/tutor/TutorLayout.vue @@ -19,7 +19,7 @@ <v-divider></v-divider> - + <subscription-list :sidebar="true"/> </v-list> </base-layout> </template> @@ -27,9 +27,12 @@ <script> import BaseLayout from '@/components/BaseLayout' + import SubscriptionList from '@/components/subscriptions/SubscriptionList' export default { - components: {BaseLayout}, + components: { + SubscriptionList, + BaseLayout}, name: 'tutor-layout', data () { return { @@ -37,17 +40,12 @@ { name: 'Overview', icon: 'home', - route: '/tutor' + route: '/home' }, { name: 'Progress', icon: 'trending_up', - route: '/tutor' - }, - { - name: 'Assignments', - icon: 'assignment_turned_in', - route: '/tutor' + route: '/home' } ] } diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index db01a025..c49a411d 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -1,61 +1,64 @@ import Vue from 'vue' import Router from 'vue-router' import Login from '@/pages/Login' -import TutorLayout from '@/pages/tutor/TutorLayout' -import TutorStartPage from '@/pages/tutor/TutorStartPage' -import StudentPage from '@/pages/student/StudentPage' -import StudentLayout from '@/pages/student/StudentLayout' import StudentSubmissionPage from '@/pages/student/StudentSubmissionPage' import SubscriptionWorkPage from '@/pages/SubscriptionWorkPage' -import ReviewerPage from '@/pages/reviewer/ReviewerPage' -import StudentListOverview from '@/pages/reviewer/StudentListOverview' +import PageNotFound from '@/pages/PageNotFound' +import StartPageSelector from '@/pages/StartPageSelector' +import LayoutSelector from '@/pages/LayoutSelector' +import VueInstance from '@/main' + +import store from '@/store/store' Vue.use(Router) +function tutorOrReviewerOnly (to, from, next) { + next() + if (store.getters.isTutorOrReviewer) { + next() + } else { + next(from.path) + VueInstance.$notify({ + title: 'Access denied', + text: "You don't have permission to view this.", + type: 'error' + }) + } +} + const router = new Router({ routes: [ { - path: '/', - name: 'grady-login', + path: '/login/', + name: 'login', component: Login }, { - path: '/student/', - component: StudentLayout, - children: [ - { - path: '', - component: StudentPage - }, - { - path: 'submission/:id', - component: StudentSubmissionPage - } - ] - }, - { - path: '/tutor/', - component: TutorLayout, + path: '', + redirect: 'home', + component: LayoutSelector, children: [ { - path: '', - component: TutorStartPage + path: 'home', + name: 'home', + component: StartPageSelector }, { path: 'subscription/:pk', + name: 'subscription', + beforeEnter: tutorOrReviewerOnly, component: SubscriptionWorkPage + }, + { + path: 'submission/:id', + component: StudentSubmissionPage } ] }, { - path: '/reviewer/', - name: 'reviewer-page', - component: ReviewerPage - }, - { - path: 'reviewer/student-overview/', - name: 'student-overview', - component: StudentListOverview + path: '*', + name: 'page-not-found', + component: PageNotFound } ] }) diff --git a/frontend/src/store/actions.js b/frontend/src/store/actions.js index 48b0790d..52ac528f 100644 --- a/frontend/src/store/actions.js +++ b/frontend/src/store/actions.js @@ -1,5 +1,6 @@ import {mut} from './mutations' import {authMut} from '@/store/modules/authentication' +import {subNotesMut} from '@/store/modules/submission-notes' import * as api from '@/api' import router from '@/router/index' @@ -76,8 +77,9 @@ const actions = { }, logout ({ commit }, message = '') { commit(mut.RESET_STATE) + commit('submissionNotes/' + subNotesMut.RESET_STATE) commit(authMut.SET_MESSAGE, message) - router.push('/') + router.push({name: 'login'}) } } diff --git a/frontend/src/store/modules/authentication.js b/frontend/src/store/modules/authentication.js index 2d70e13e..a4972a48 100644 --- a/frontend/src/store/modules/authentication.js +++ b/frontend/src/store/modules/authentication.js @@ -24,9 +24,7 @@ export const authMut = Object.freeze({ }) const authentication = { - state: { - ...initialState() - }, + state: initialState(), getters: { gradySpeak: () => { return gradySays[Math.floor(Math.random() * gradySays.length)] @@ -39,7 +37,11 @@ const authentication = { }, isReviewer: state => { return state.userRole === 'Reviewer' - } + }, + isTutorOrReviewer: (state, getters) => { + return getters.isTutor || getters.isReviewer + }, + isLoggedIn: state => !!state.token }, mutations: { [authMut.SET_MESSAGE]: function (state, message) { diff --git a/frontend/src/store/modules/student-page.js b/frontend/src/store/modules/student-page.js index 2206a0a4..15c94ebf 100644 --- a/frontend/src/store/modules/student-page.js +++ b/frontend/src/store/modules/student-page.js @@ -1,4 +1,15 @@ -import {fetchStudentSelfData, fetchStudentSubmissions} from '../../api' +import {fetchStudentSelfData, fetchStudentSubmissions} from '@/api' + +function initialState () { + return { + studentName: '', + exam: {}, + submissionsForList: [], + submissionData: {}, + visited: {}, + loaded: false + } +} export const studentPageMut = Object.freeze({ SET_STUDENT_NAME: 'SET_STUDENT_NAME', @@ -7,18 +18,12 @@ export const studentPageMut = Object.freeze({ SET_SUBMISSIONS_FOR_LIST: 'SET_SUBMISSIONS_FOR_LIST', SET_FULL_SUBMISSION_DATA: 'SET_FULL_SUBMISSION_DATA', SET_VISITED: 'SET_VISITED', - SET_LOADED: 'SET_LOADED' + SET_LOADED: 'SET_LOADED', + RESET_STATE: 'RESET_STATE' }) const studentPage = { - state: { - studentName: '', - exam: {}, - submissionsForList: [], - submissionData: {}, - visited: {}, - loaded: false - }, + state: initialState(), mutations: { [studentPageMut.SET_STUDENT_NAME]: function (state, name) { state.studentName = name @@ -49,6 +54,9 @@ const studentPage = { }, [studentPageMut.SET_LOADED]: function (state, loaded) { state.loaded = loaded + }, + [studentPageMut.RESET_STATE]: function (state) { + Object.assign(state, initialState()) } }, actions: { @@ -56,16 +64,11 @@ const studentPage = { async getStudentData (context) { try { const studentData = await fetchStudentSelfData() - context.commit(studentData.SET_STUDENT_NAME, studentData.name) - context.commit(studentData.SET_EXAM, studentData.exam) - context.commit(studentData.SET_SUBMISSIONS_FOR_LIST, studentData.submissions) - context.commit(studentData.SET_LOADED, true) + context.commit(studentPageMut.SET_STUDENT_NAME, studentData.name) + context.commit(studentPageMut.SET_EXAM, studentData.exam) + context.commit(studentPageMut.SET_SUBMISSIONS_FOR_LIST, studentData.submissions) + context.commit(studentPageMut.SET_LOADED, true) } catch (e) { - this.$notify({ - title: 'API Fail', - text: 'Unable to fetch student data', - type: 'error' - }) console.log(e) } }, @@ -75,11 +78,7 @@ const studentPage = { const submissions = await fetchStudentSubmissions() context.commit(studentPageMut.SET_FULL_SUBMISSION_DATA, submissions) } catch (e) { - this.$notify({ - title: 'API Fail', - text: 'Unable to fetch student submissions', - type: 'error' - }) + console.log(e) } } } diff --git a/frontend/src/store/modules/submission-notes.js b/frontend/src/store/modules/submission-notes.js index cd75a44f..a63fd120 100644 --- a/frontend/src/store/modules/submission-notes.js +++ b/frontend/src/store/modules/submission-notes.js @@ -48,6 +48,11 @@ const submissionNotes = { }, score: state => { return state.updated.score !== null ? state.updated.score : state.orig.score + }, + openEditorOrWrittenFeedback: state => { + const openEdit = Object.values(state.ui.showEditorOnLine).some(bool => bool) + const hasWrittenFeedback = Object.keys(state.updated.feedbackLines).length > 0 + return openEdit || hasWrittenFeedback } }, mutations: { diff --git a/frontend/src/store/modules/ui.js b/frontend/src/store/modules/ui.js new file mode 100644 index 00000000..80d5ae06 --- /dev/null +++ b/frontend/src/store/modules/ui.js @@ -0,0 +1,21 @@ + +function initialState () { + return { + sideBarCollapsed: false + } +} + +export const uiMut = Object.freeze({ + SET_SIDEBAR_COLLAPSED: 'SET_SIDEBAR_COLLAPSED' +}) + +const ui = { + state: initialState(), + mutations: { + [uiMut.SET_SIDEBAR_COLLAPSED] (state, collapsed) { + state.sideBarCollapsed = collapsed + } + } +} + +export default ui diff --git a/frontend/src/store/mutations.js b/frontend/src/store/mutations.js index 81fd8220..00a02fcd 100644 --- a/frontend/src/store/mutations.js +++ b/frontend/src/store/mutations.js @@ -8,10 +8,10 @@ export const mut = Object.freeze({ SET_SUBSCRIPTION: 'SET_SUBSCRIPTION', UPDATE_SUBMISSION_TYPE: 'UPDATE_SUBMISSION_TYPE', UPDATE_ASSIGNMENT: 'UPDATE_ASSIGNMENT', - UPDATE_NEXT_ASSIGNMENT: 'UPDATE_NEXT_ASSIGNMENT', RESET_STATE: 'RESET_STATE', SET_LAST_INTERACTION: 'SET_LAST_INTERACTION', - SET_EXAM_TYPES: 'SET_EXAM_TYPES' + SET_EXAM_TYPES: 'SET_EXAM_TYPES', + SET_NOTIFY_MESSAGE: 'SET_NOTIFY_MESSAGE' }) const mutations = { diff --git a/frontend/src/store/store.js b/frontend/src/store/store.js index b036d787..c48d87c0 100644 --- a/frontend/src/store/store.js +++ b/frontend/src/store/store.js @@ -5,6 +5,7 @@ import createPersistedState from 'vuex-persistedstate' import studentPage from './modules/student-page' import submissionNotes from './modules/submission-notes' import authentication from './modules/authentication' +import ui from './modules/ui' import actions from './actions' import getters from './getters' @@ -16,7 +17,6 @@ Vue.use(Vuex) export function initialState () { return { lastAppInteraction: Date.now(), - currentTime: Date.now(), examTypes: {}, submissionTypes: {}, submissions: {}, @@ -26,29 +26,32 @@ export function initialState () { } } +export const persistedStateKey = 'grady' + const store = new Vuex.Store({ // TODO only enable this in dev and not in deployment (use env variable) strict: true, modules: { authentication, studentPage, - submissionNotes + submissionNotes, + ui }, - plugins: [createPersistedState({ - storage: window.sessionStorage, - // authentication.token is manually saved since using it with this plugin caused issues - // when manually reloading the page - paths: Object.keys(initialState()).concat( - ['studentPage', 'submissionNotes', 'authentication.username', 'authentication.userRole', - 'authentication.jwtTimeDelta']) - }), + plugins: [ + createPersistedState({ + key: persistedStateKey, + storage: window.sessionStorage, + // authentication.token is manually saved since using it with this plugin caused issues + // when manually reloading the page + paths: Object.keys(initialState()).concat( + ['ui', 'studentPage', 'submissionNotes', 'authentication.username', 'authentication.userRole', + 'authentication.jwtTimeDelta']) + }), lastInteraction], actions, getters, mutations, - state: { - ...initialState() - } + state: initialState() }) export default store -- GitLab