From f7fe8cd62ba29d44316b0c3254844482bf2c5ba6 Mon Sep 17 00:00:00 2001 From: Kostiantyn Miakshyn Date: Thu, 12 Sep 2024 00:50:31 +0200 Subject: [PATCH] feat: Ask for restarting submission if form was changed Signed-off-by: Kostiantyn Miakshyn --- src/components/Questions/QuestionDate.vue | 12 +- src/components/Questions/QuestionDropdown.vue | 17 +-- src/components/Questions/QuestionLong.vue | 15 +- src/views/Submit.vue | 128 ++++++++++++++---- 4 files changed, 127 insertions(+), 45 deletions(-) diff --git a/src/components/Questions/QuestionDate.vue b/src/components/Questions/QuestionDate.vue index 6c318657d..d45c4555c 100644 --- a/src/components/Questions/QuestionDate.vue +++ b/src/components/Questions/QuestionDate.vue @@ -28,7 +28,7 @@ v-on="commonListeners">
this.options.find((option) => option.id === id), ) - this.selectedOption = this.isMultiple ? selected : selected[0] - } + + return this.isMultiple ? selected : selected[0] + }, }, methods: { diff --git a/src/components/Questions/QuestionLong.vue b/src/components/Questions/QuestionLong.vue index 2e5e13384..fbbf6728d 100644 --- a/src/components/Questions/QuestionLong.vue +++ b/src/components/Questions/QuestionLong.vue @@ -71,18 +71,27 @@ export default { }, }, - mounted() { - this.autoSizeText() + watch: { + values: { + handler() { + this.$nextTick(() => { + this.autoSizeText() + }) + }, + immediate: true, + }, }, methods: { onInput() { const textarea = this.$refs.textarea this.$emit('update:values', [textarea.value]) - this.autoSizeText() }, autoSizeText() { const textarea = this.$refs.textarea + if (!textarea) { + return + } textarea.style.cssText = 'height:auto; padding:0' textarea.style.cssText = `height: ${textarea.scrollHeight + 28}px` }, diff --git a/src/views/Submit.vue b/src/views/Submit.vue index d399bbc2b..384ba1ece 100644 --- a/src/views/Submit.vue +++ b/src/views/Submit.vue @@ -145,17 +145,31 @@ @keydown.ctrl.enter="onKeydownCtrlEnter" @update:values="(values) => onUpdate(question, values)" /> - - - {{ t('forms', 'Submit') }} - +
+ + + {{ t('forms', 'Clear form') }} + + + + {{ t('forms', 'Submit') }} + +
@@ -179,6 +193,27 @@ :buttons="confirmLeaveFormButtons" :can-close="false" :close-on-click-outside="false" /> + + + + @@ -201,6 +236,7 @@ import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js' import IconCancelSvg from '@mdi/svg/svg/cancel.svg?raw' import IconCheckSvg from '@mdi/svg/svg/check.svg?raw' +import IconRefreshSvg from '@mdi/svg/svg/refresh.svg?raw' import IconSendSvg from '@mdi/svg/svg/send.svg?raw' import { FormState } from '../models/FormStates.ts' @@ -277,6 +313,7 @@ export default { // Non reactive properties return { IconCheckSvg, + IconRefreshSvg, IconSendSvg, maxStringLengths: loadState('forms', 'maxStringLengths'), @@ -297,6 +334,8 @@ export default { submitForm: false, showConfirmEmptyModal: false, showConfirmLeaveDialog: false, + showClearFormDialog: false, + showClearFormDueToChangeDialog: false, } }, @@ -316,6 +355,10 @@ export default { }) }, + validQuestionsIds() { + return new Set(this.validQuestions.map((question) => question.id)) + }, + isRequiredUsed() { return this.form.questions.reduce( (isUsed, question) => isUsed || question.isRequired, @@ -413,6 +456,29 @@ export default { }, ] }, + + /** + * Buttons for the "confirm clear form" dialog + */ + confirmClearFormButtons() { + return [ + { + label: t('forms', 'Abort'), + icon: IconCancelSvg, + callback: () => {}, + }, + { + label: t('forms', 'Clear'), + icon: IconCheckSvg, + type: 'primary', + callback: () => this.onResetSubmission(), + }, + ] + }, + + hasAnswers() { + return Object.keys(this.answers).length > 0 + }, }, watch: { @@ -437,17 +503,19 @@ export default { window.addEventListener('beforeunload', this.beforeWindowUnload) }, - beforeMount() { + async beforeMount() { // Public Views get their form by initial-state from parent. No fetch necessary. if (this.publicView) { this.isLoadingForm = false } else { - this.fetchFullForm(this.form.id) + await this.fetchFullForm(this.form.id) } - SetWindowTitle(this.formTitle) + if (this.isLoggedIn) { this.initFromLocalStorage() } + + SetWindowTitle(this.formTitle) }, methods: { @@ -473,8 +541,16 @@ export default { if (!savedState) { return } + const answers = {} for (const [questionId, answer] of Object.entries(savedState)) { + // Clean up answers for questions that do not exist anymore + if (!this.validQuestionsIds.has(parseInt(questionId))) { + this.showClearFormDueToChangeDialog = true + logger.debug('Question does not exist anymore', { questionId }) + continue + } + answers[questionId] = answer.type === 'QuestionMultiple' ? answer.value.map(String) @@ -585,17 +661,6 @@ export default { async (question) => await question.validate(), ) - // Clean up answers for questions that do not exist anymore - const questionIds = new Map( - this.validQuestions.map((question) => [question.id, true]), - ) - for (const questionId of Object.keys(this.answers)) { - if (!questionIds.has(parseInt(questionId))) { - logger.debug('Question does not exist anymore', { questionId }) - delete this.answers[questionId] - } - } - try { // wait for all to be validated const result = await Promise.all(validation) @@ -654,6 +719,11 @@ export default { } }, + onResetSubmission() { + this.deleteFormFieldFromLocalStorage() + this.resetData() + }, + /** * Reset View-Data */ @@ -661,6 +731,8 @@ export default { this.answers = {} this.loading = false this.showConfirmLeaveDialog = false + this.showClearFormDialog = false + this.showClearFormDueToChangeDialog = false this.success = false this.submitForm = false }, @@ -749,8 +821,12 @@ export default { padding-inline-start: var(--default-clickable-area); } + .form-buttons { + display: flex; + justify-content: flex-end; + } + .submit-button { - align-self: flex-end; margin: 5px; margin-block-end: 160px; padding-inline-start: 20px;