skillbox/client/src/components/content-blocks/assignment/Assignment.vue

297 lines
8.3 KiB
Vue

<template>
<div
:data-scrollto="value.id"
class="assignment">
<p class="assignment__assignment-text">
{{ assignment.assignment }}
</p>
<solution
:value="solution"
v-if="assignment.solution"/>
<template v-if="isStudent">
<submission-form
:user-input="submission"
:spellcheck-loading="spellcheckLoading"
:saved="!unsaved"
:spellcheck="true"
:read-only="me.readOnly || me.selectedClass.readOnly"
placeholder="Ergebnis erfassen"
action="Ergebnis mit Lehrperson teilen"
shared-msg="Das Ergebnis wurde mit der Lehrperson geteilt."
v-if="isStudent"
@turnIn="turnIn"
@saveInput="saveInput"
@reopen="reopen"
@changeDocumentUrl="changeDocumentUrl"
@spellcheck="spellcheck"
/>
<spell-check
:corrections="corrections"
:text="submission.text"/>
<p
class="assignment__feedback"
v-if="assignment.submission.submissionFeedback"
v-html="feedbackText"/>
</template>
<template v-if="!isStudent">
<router-link
:to="{name: 'submissions', params: { id: assignment.id }}"
class="button button--primary">Zu den
Ergebnissen
</router-link>
</template>
</div>
</template>
<script>
import {mapActions, mapGetters} from 'vuex';
import ASSIGNMENT_QUERY from '@/graphql/gql/queries/assignmentQuery.gql';
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
import UPDATE_ASSIGNMENT_MUTATION from '@/graphql/gql/mutations/updateAssignmentMutation.gql';
import UPDATE_ASSIGNMENT_MUTATION_WITH_SUCCESS from '@/graphql/gql/mutations/updateAssignmentMutationWithSuccess.gql';
import SPELL_CHECK_MUTATION from '@/graphql/gql/mutations/spellCheck.gql';
import debounce from 'lodash/debounce';
import cloneDeep from 'lodash/cloneDeep';
import SubmissionForm from '@/components/content-blocks/assignment/SubmissionForm';
import Solution from '@/components/content-blocks/Solution';
import SpellCheck from '@/components/content-blocks/assignment/SpellCheck';
export default {
props: ['value'],
components: {
Solution,
SubmissionForm,
SpellCheck,
},
data() {
return {
assignment: {
submission: this.initialSubmission(),
},
me: {
permissions: [],
},
inputType: 'text',
unsaved: false,
saving: 0,
corrections: '',
spellcheckLoading: false,
};
},
computed: {
...mapGetters(['scrollToAssignmentId']),
final() {
return !!this.submission && this.submission.final;
},
submission() {
return this.assignment.submission ? this.assignment.submission : {};
},
isStudent() {
return !this.me.permissions.includes('users.can_manage_school_class_content');
},
solution() {
return {
text: this.assignment.solution,
};
},
id() {
return this.assignment.id ? this.assignment.id.replace(/=/g, '') : '';
},
feedbackText() {
let feedback = this.assignment.submission.submissionFeedback;
return `<span class="inline-title">Feedback von ${feedback.teacher.firstName} ${feedback.teacher.lastName}:</span> ${feedback.text}`;
},
},
methods: {
...mapActions(['scrollToAssignmentReady']),
_save: debounce(function (submission) {
this.saving++;
this.$apollo.mutate({
mutation: UPDATE_ASSIGNMENT_MUTATION_WITH_SUCCESS,
variables: {
input: {
assignment: {
id: this.assignment.id,
answer: this.assignment.submission.text,
document: this.assignment.submission.document,
},
},
},
update(store, {data: {updateAssignment: {successful, updatedAssignment}}}) {
try {
if (successful) {
const query = ASSIGNMENT_QUERY;
const variables = {
id: updatedAssignment.id,
};
const data = store.readQuery({query, variables});
data.assignment = Object.assign({}, updatedAssignment, {
submission,
});
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
}
},
}).then(() => {
this.saving--;
if (this.saving === 0) {
this.unsaved = false;
}
});
}, 500),
saveInput: function (answer) {
// reset corrections on input
this.corrections = '';
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.assignment.submission.text = answer;
this._save(this.assignment.submission);
},
changeDocumentUrl(documentUrl) {
this.assignment.submission.document = documentUrl;
this._save(this.assignment.submission);
},
turnIn() {
// reset corrections on turn in
this.corrections = '';
this.$apollo.mutate({
mutation: UPDATE_ASSIGNMENT_MUTATION,
variables: {
input: {
assignment: {
id: this.assignment.id,
answer: this.assignment.submission.text,
document: this.assignment.submission.document,
final: true,
},
},
},
});
},
reopen() {
this.$apollo.mutate({
mutation: UPDATE_ASSIGNMENT_MUTATION,
variables: {
input: {
assignment: {
id: this.assignment.id,
answer: this.assignment.submission.text,
document: this.assignment.submission.document,
final: false,
},
},
},
});
},
initialSubmission() {
return {
text: '',
document: '',
final: false,
};
},
spellcheck() {
let self = this;
this.spellcheckLoading = true;
this.$apollo.mutate({
mutation: SPELL_CHECK_MUTATION,
variables: {
input: {
assignment: this.assignment.id,
text: this.assignment.submission.text,
},
},
update(store, {data: {spellCheck: {results}}}) {
self.corrections = results;
},
}).then(() => {
this.spellcheckLoading = false;
});
},
},
apollo: {
assignment: {
query: ASSIGNMENT_QUERY,
variables() {
return {
id: this.value.id,
};
},
result(response) {
const data = response.data;
this.assignment = cloneDeep(data.assignment);
this.assignment.submission = Object.assign(this.initialSubmission(), this.assignment.submission);
if (this.assignment.id === this.scrollToAssignmentId && 'stale' in response) {
this.$nextTick(() => this.scrollToAssignmentReady(true));
}
},
},
me: {
query: ME_QUERY,
},
},
};
</script>
<style scoped lang="scss">
@import '@/styles/_variables.scss';
@import '@/styles/_functions.scss';
@import '@/styles/_mixins.scss';
.assignment {
margin-bottom: 3rem;
position: relative;
&__title {
font-size: toRem(17px);
margin-bottom: 1rem;
}
&__toggle-input-container {
display: flex;
margin-bottom: 15px;
}
&__toggle-input {
border: 0;
font-family: $sans-serif-font-family;
background: transparent;
font-size: toRem(14px);
padding: 5px 0;
margin-right: 15px;
outline: 0;
color: $color-silver-dark;
cursor: pointer;
border-bottom: 2px solid transparent;
&--active {
border-bottom-color: $color-charcoal-dark;
color: $color-charcoal-dark;
}
}
&__feedback {
@include regular-text;
}
}
</style>