skillbox/client/src/pages/studentSubmission.vue

304 lines
8.1 KiB
Vue

<template>
<!-- eslint-disable vue/no-v-html -->
<div class="submission-page">
<div class="submission-page__header submission-header">
<h2>Aufgabe</h2>
<p v-html="assignmentText" />
</div>
<div class="submission-page__content submission-content">
<h2>Ergebnis von {{ fullName }}</h2>
<p v-html="text" />
<p
class="article-content__document"
v-if="studentSubmission.document && studentSubmission.document.length > 0"
>
<a
:href="studentSubmission.document"
class="entry-document__link link"
target="_blank"
>
<student-submission-document :document="studentSubmission.document" />
</a>
</p>
</div>
<div class="submission-page__feedback feedback">
<submission-form
:user-input="feedback"
:saved="!unsaved"
:read-only="readOnly"
placeholder="Feedback erfassen"
action="Feedback teilen"
shared-msg="Dieses Feedback wurde geteilt."
v-if="studentSubmission"
@turnIn="turnIn"
@saveInput="saveInput"
@reopen="reopen"
>
<emoji-bar
class="feedback-submission__emojis emojis"
v-if="!final"
@add-emoji="addEmoji"
/>
</submission-form>
</div>
</div>
</template>
<script>
import { newLineToParagraph, sanitizeAsHtml } from '@/helpers/text';
import debounce from 'lodash/debounce';
import cloneDeep from 'lodash/cloneDeep';
import log from 'loglevel';
import StudentSubmissionDocument from '@/components/StudentSubmissionDocument.vue';
import STUDENT_SUBMISSIONS_QUERY from '@/graphql/gql/queries/studentSubmissionQuery.gql';
import UPDATE_FEEDBACK_MUTATION from '@/graphql/gql/mutations/updateFeedback.gql';
import UPDATE_FEEDBACK_WITH_TEXT_MUTATION from '@/graphql/gql/mutations/updateFeedbackWithText.gql';
import SubmissionForm from '@/components/content-blocks/assignment/SubmissionForm.vue';
import me from '@/mixins/me';
import EmojiBar from '@/components/ui/EmojiBar.vue';
export default {
mixins: [me],
components: {
EmojiBar,
StudentSubmissionDocument,
SubmissionForm,
},
data() {
return {
studentSubmission: {
assignment: {
title: '',
},
student: {
firstName: '',
lastName: '',
},
text: '',
document: '',
submissionFeedback: {
text: '',
final: false,
},
},
unsaved: false,
saving: 0,
};
},
computed: {
final() {
return !!this.studentSubmission && this.studentSubmission.final;
},
text() {
return newLineToParagraph(this.studentSubmission.text);
},
fullName() {
return `${this.studentSubmission.student.firstName} ${this.studentSubmission.student.lastName}`;
},
feedback() {
return this.studentSubmission.submissionFeedback
? this.studentSubmission.submissionFeedback
: {
text: '',
final: false,
};
},
readOnly() {
return this.me.readOnly || this.me.selectedClass?.readOnly;
},
assignmentText() {
const text = this.studentSubmission.assignment.assignment;
if (text) {
return sanitizeAsHtml(text);
}
return '';
},
},
apollo: {
studentSubmission() {
return {
query: STUDENT_SUBMISSIONS_QUERY,
variables() {
return {
id: this.$route.params.id,
};
},
result({ data: { studentSubmission } }) {
if (!studentSubmission.submissionFeedback) {
// create something for the cache to find
this.create();
}
this.studentSubmission = cloneDeep(studentSubmission); // we don't want to update the value when the server updates
},
};
},
},
methods: {
addEmoji(emoji) {
const feedbackText = this.feedback.text + emoji;
this.updateFeedbackText(feedbackText);
},
_save: debounce(function () {
this.saving++;
this.$apollo
.mutate({
mutation: UPDATE_FEEDBACK_MUTATION,
variables: {
input: {
submissionFeedback: {
studentSubmission: this.studentSubmission.id,
text: this.studentSubmission.submissionFeedback.text,
},
},
},
update: this.updateCache,
})
.then(() => {
this.saving--;
if (this.saving === 0) {
this.unsaved = false;
}
});
}, 500),
saveInput: function (feedbackText) {
this.unsaved = true;
/*
We update the assignment on this component, so the changes are reflected on it. The server does not return
the updated entity, to prevent the UI to update when the user is entering his input
*/
this.updateFeedbackText(feedbackText);
this._save();
},
update({ withText, text, final }) {
if (this.readOnly) {
log.debug('read-only');
return;
}
let mutation = withText ? UPDATE_FEEDBACK_WITH_TEXT_MUTATION : UPDATE_FEEDBACK_MUTATION;
this.$apollo.mutate({
mutation,
variables: {
input: {
submissionFeedback: {
studentSubmission: this.studentSubmission.id,
text: text,
final: final,
},
},
},
update: this.updateCache,
});
},
create() {
log.debug('create');
this.update({
withText: true,
text: '',
final: false,
});
},
turnIn() {
log.debug('turnIn');
this.update({
withText: true,
text: this.studentSubmission.submissionFeedback.text,
final: true,
});
},
reopen() {
log.debug('reopen');
if (!this.studentSubmission.id) {
return;
}
this.update({
withText: false,
text: this.studentSubmission.submissionFeedback.text,
final: false,
});
},
updateCache(
store,
{
data: {
updateSubmissionFeedback: { successful, updatedSubmissionFeedback },
},
}
) {
try {
if (successful) {
const query = STUDENT_SUBMISSIONS_QUERY;
const variables = {
id: this.studentSubmission.id,
};
const { studentSubmission } = store.readQuery({ query, variables });
if (studentSubmission) {
let text;
if (updatedSubmissionFeedback.text !== undefined) {
// text is only being set on create and on turning in, then we'll update the cache with it. Otherwise, we'll trust the local state, as to not overwrite the input field
text = updatedSubmissionFeedback.text;
} else {
text = this.studentSubmission.submissionFeedback ? this.studentSubmission.submissionFeedback.text : '';
}
const submissionFeedback = Object.assign({}, studentSubmission.submissionFeedback, {
id: updatedSubmissionFeedback.id,
final: updatedSubmissionFeedback.final,
__typename: 'SubmissionFeedbackNode',
text,
});
const data = {
studentSubmission: {
...studentSubmission,
submissionFeedback,
},
};
store.writeQuery({ query, variables, data });
}
}
} catch (e) {
console.error(e);
// Query did not exist in the cache, and apollo throws a generic Error. Do nothing
}
},
updateFeedbackText(text) {
this.studentSubmission = Object.assign({}, this.studentSubmission, {
submissionFeedback: Object.assign({}, this.studentSubmission.submissionFeedback, { text: text }),
});
},
},
};
</script>
<style scoped lang="scss">
@import 'styles/helpers';
.submission-page {
&__content {
margin-top: 1.5 * $large-spacing;
}
&__feedback {
margin-top: $large-spacing;
}
}
.emojis {
font-size: toRem(32px);
&__emoji {
cursor: pointer;
}
}
</style>