From c04810ffa672b51960232218557177cf051a4d96 Mon Sep 17 00:00:00 2001 From: Rakantor Date: Fri, 11 Nov 2022 20:08:45 +0100 Subject: [PATCH] Add optional chapter field to open-ended questions --- components/AddOpenEndedQuestion.vue | 109 ++++++++++++------ components/OpenEndedQuestionsList.vue | 155 +++++++++++++++++--------- plugins/open-ended-question.js | 8 +- 3 files changed, 184 insertions(+), 88 deletions(-) diff --git a/components/AddOpenEndedQuestion.vue b/components/AddOpenEndedQuestion.vue index d21ab82..989b7b7 100644 --- a/components/AddOpenEndedQuestion.vue +++ b/components/AddOpenEndedQuestion.vue @@ -3,7 +3,7 @@
- Eigene Frage einreichen + Eigene Frage einsenden {{ showBody ? 'mdi-chevron-up' : 'mdi-chevron-down' }} @@ -13,26 +13,41 @@ - + + + + + + - + @@ -58,7 +73,7 @@ import { collection, doc, addDoc, writeBatch } from 'firebase/firestore' // We use vee-validate@3 for form validation. // https://vee-validate.logaretm.com/v3/guide/basics.html import { ValidationProvider, ValidationObserver, extend } from 'vee-validate' -import { required, min } from 'vee-validate/dist/rules' +import { required, min, max, regex } from 'vee-validate/dist/rules' import dedent from 'dedent' import { OpenEndedQuestion, OpenEndedQuestionConverter } from '~/plugins/open-ended-question' @@ -72,6 +87,16 @@ extend('min', { message: '{_field_} muss mindestens {length} Zeichen lang sein.' }) +extend('max', { + ...max, + message: '{_field_} darf maximal {length} Zeichen lang sein.' +}) + +extend('regex', { + ...regex, + message: '{_field_} muss eine Zahl von 1-9 sein.' +}) + export default { components: { ValidationProvider, @@ -79,43 +104,60 @@ export default { }, data () { return { - showBody: false, - loading: false, + questionMaxLength: 250, + solutionMaxLength: 1000, + chapter: null, question: '', - solution: '' + solution: '', + showBody: false, + loading: false } }, methods: { submit () { - this.loading = true + // Remove extra spaces at the start and end of the string (trim), + // then remove extra spaces between words (1st replace), + // then remove extra line breaks so that there's max 1 empty line between paragraphs (2nd replace). + this.question = this.question.trim().replace(/ +/g, ' ').replace(/[\r\n]{3,}/g, '\n\n') + this.solution = this.solution.trim().replace(/ +/g, ' ').replace(/[\r\n]{3,}/g, '\n\n') - const q = new OpenEndedQuestion( - null, - this.question, - this.solution, - this.$auth.currentUser.uid, // Ref: https://firebase.google.com/docs/reference/js/v8/firebase.User - Date.now() / 1000, // Current UNIX timestamp in seconds - [], - {} - ) + // Wait until the models are updated in the UI + this.$nextTick(() => { + this.$refs.observer.validate().then((success) => { + if (!success) return - // Add a new document with a generated id. - addDoc(collection(this.$db, `kurse/${this.$store.state.selectedCourse}/fragenOffen`).withConverter(OpenEndedQuestionConverter), q) - .then((docRef) => { - // Successfully added new question to database - q.id = docRef.id - this.$emit('added', q) - this.$toast({ content: 'Deine Frage wurde hinzugefügt!', color: 'success' }) - }) - .catch((error) => { - // Failed to add question to database; display error message - this.$toast({content: error, color: 'error'}) - }) - .then(() => { - this.loading = false + this.loading = true + const q = new OpenEndedQuestion( + null, + this.chapter, + this.question, + this.solution, + this.$auth.currentUser.uid, // Ref: https://firebase.google.com/docs/reference/js/v8/firebase.User + Date.now() / 1000, // Current UNIX timestamp in seconds + [], + {} + ) + + // Add a new document with a generated id. + addDoc(collection(this.$db, `kurse/${this.$store.state.selectedCourse}/fragenOffen`).withConverter(OpenEndedQuestionConverter), q) + .then((docRef) => { + // Successfully added new question to database + q.id = docRef.id + this.$emit('added', q) + this.$toast({ content: 'Deine Frage wurde hinzugefügt!', color: 'success' }) + }) + .catch((error) => { + // Failed to add question to database; display error message + this.$toast({content: error, color: 'error'}) + }) + .then(() => { + this.loading = false + }) + }) }) }, clear () { + this.chapter = '' this.question = '' this.solution = '' this.$refs.observer.reset() @@ -145,11 +187,14 @@ export default { const batch = writeBatch(this.$db) for (let i = 1; i <= 10; i++) { - // Generate random integer between 1 (inlcusive) and 1000 (inclusive) + // Generate random integer between 1 (inclusive) and 1000 (inclusive) const randomNumber = Math.floor(Math.random() * (1000 - 1 + 1) + 1) + // Generate random integer between 1 (inclusive) and 9 (inclusive) + const chapter = Math.floor(Math.random() * (9 - 1 + 1) + 1) const q = new OpenEndedQuestion( null, + chapter, `Frage ${randomNumber}: Erläutere den Sinn des Lebens.`, solution, this.$auth.currentUser.uid, // Ref: https://firebase.google.com/docs/reference/js/v8/firebase.User diff --git a/components/OpenEndedQuestionsList.vue b/components/OpenEndedQuestionsList.vue index 452cc5d..61eda8b 100644 --- a/components/OpenEndedQuestionsList.vue +++ b/components/OpenEndedQuestionsList.vue @@ -1,21 +1,18 @@ + + + @@ -142,15 +126,16 @@ export default { }, data () { return { - itemsPerPageArray: [10, 20, 30], + items: [], + itemsPerPageArray: [10, 25, 50], search: '', - filter: {}, sortDesc: true, page: 1, itemsPerPage: 10, sortBy: 'created', keys: [ { key: 'Neueste', value: 'created' }, + { key: 'Kapitel', value: 'chapter' }, { key: 'Hilfreich', value: 'helpful' }, { key: 'Schwierigkeit', value: 'difficultyLevel' } ], @@ -165,8 +150,51 @@ export default { numberOfPages () { return Math.ceil(this.questions.length / this.itemsPerPage) }, - filteredKeys () { - return this.keys.filter(key => key !== 'Name') + actionButtons () { + const self = this + return [ + // Button "Helpful" + { + active: this.helpful, + iconActive: 'mdi-thumb-up', + iconInactive: 'mdi-thumb-up-outline', + iconColor: 'primary', + caption: this.selectedQuestion && this.selectedQuestion.helpful.length, + tooltip: 'Hilfreich', + class: 'mr-4', + onClick() { self.toggleHelpful() } + }, + // Button "Easy Question" + { + active: this.difficulty === 'easy', + iconActive: 'mdi-emoticon', + iconInactive: 'mdi-emoticon-outline', + iconColor: 'amber', + caption: this.selectedQuestion && this.selectedQuestion.difficulty.easy.length, + tooltip: 'Leichte Frage', + onClick() { self.toggleDifficulty('easy') } + }, + // Button "Moderate Question" + { + active: this.difficulty === 'medium', + iconActive: 'mdi-emoticon-happy', + iconInactive: 'mdi-emoticon-happy-outline', + iconColor: 'amber', + caption: this.selectedQuestion && this.selectedQuestion.difficulty.medium.length, + tooltip: 'Mittelschwere Frage', + onClick() { self.toggleDifficulty('medium') } + }, + // Button "Hard Question" + { + active: this.difficulty === 'hard', + iconActive: 'mdi-emoticon-confused', + iconInactive: 'mdi-emoticon-confused-outline', + iconColor: 'amber', + caption: this.selectedQuestion && this.selectedQuestion.difficulty.hard.length, + tooltip: 'Schwere Frage', + onClick() { self.toggleDifficulty('hard') } + } + ] }, helpful () { return this.selectedQuestion && this.selectedQuestion.helpful.includes(this.$auth.currentUser.uid) @@ -178,15 +206,33 @@ export default { else return null } }, + watch: { + questions () { + this.items = [...this.questions] + this.sort() + }, + sortBy () { + this.sort() + }, + sortDesc () { + this.sort() + } + }, methods: { - nextPage () { - if (this.page + 1 <= this.numberOfPages) this.page += 1 - }, - formerPage () { - if (this.page - 1 >= 1) this.page -= 1 - }, - updateItemsPerPage (number) { - this.itemsPerPage = number + sort () { + if (this.items && this.items.length > 0) { + this.items.sort((a, b) => { + if (this.sortBy === 'helpful') { + return this.sortDesc + ? b[this.sortBy].length - a[this.sortBy].length + : a[this.sortBy].length - b[this.sortBy].length + } else { + return this.sortDesc + ? b[this.sortBy] - a[this.sortBy] + : a[this.sortBy] - b[this.sortBy] + } + }) + } }, setSelectedQuestion(q) { this.selectedQuestion = q @@ -238,6 +284,7 @@ export default { if (indexHard !== -1) this.selectedQuestion.difficulty.hard.splice(indexHard, 1) this.selectedQuestion.difficulty[type].push(this.$auth.currentUser.uid) } + this.selectedQuestion.updateDifficultyLevel() }).catch((error) => { // Failed to update the question; display error message this.$toast({ content: error, color: 'error' }) diff --git a/plugins/open-ended-question.js b/plugins/open-ended-question.js index ee7d21a..ddba819 100644 --- a/plugins/open-ended-question.js +++ b/plugins/open-ended-question.js @@ -1,6 +1,7 @@ export class OpenEndedQuestion { - constructor (id, question, solution, creator, created, helpful, difficulty) { + constructor (id, chapter, question, solution, creator, created, helpful, difficulty) { this.id = id + this.chapter = chapter this.question = question this.solution = solution this.creator = creator @@ -40,6 +41,7 @@ export const OpenEndedQuestionConverter = { toFirestore: (openEndedQuestion) => { return { frage: openEndedQuestion.question, + kapitel: openEndedQuestion.chapter, musterloesung: openEndedQuestion.solution, ersteller: openEndedQuestion.creator, erstellt: openEndedQuestion.created, @@ -53,6 +55,8 @@ export const OpenEndedQuestionConverter = { }, fromFirestore: (snapshot, options) => { const data = snapshot.data(options) - return new OpenEndedQuestion(snapshot.id, data.frage, data.musterloesung, data.ersteller, data.erstellt, data.hilfreich, data.schwierigkeit) + return new OpenEndedQuestion(snapshot.id, + data.kapitel, data.frage, data.musterloesung, data.ersteller, data.erstellt, data.hilfreich, data.schwierigkeit + ) } } \ No newline at end of file