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..445b91d74 100644 --- a/src/components/Questions/QuestionLong.vue +++ b/src/components/Questions/QuestionLong.vue @@ -71,18 +71,16 @@ export default { }, }, - mounted() { - this.autoSizeText() - }, - 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` }, @@ -90,6 +88,17 @@ export default { this.$emit('keydown', event) }, }, + + watch: { + values: { + handler() { + this.$nextTick(() => { + this.autoSizeText() + }) + }, + immediate: true, + }, + }, } diff --git a/src/views/Submit.vue b/src/views/Submit.vue index d399bbc2b..cdb67bbe8 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', 'Restart submission') }} + + + + {{ 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, + showRestartSubmissionDialog: false, + showRestartSubmissionDueToChangeDialog: 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,13 +456,36 @@ export default { }, ] }, + + /** + * Buttons for the "confirm restart submission" dialog + */ + confirmRestartSubmissionButtons() { + return [ + { + label: t('forms', 'Abort'), + icon: IconCancelSvg, + callback: () => {}, + }, + { + label: t('forms', 'Restart'), + icon: IconCheckSvg, + type: 'primary', + callback: () => this.onResetSubmission(), + }, + ] + }, + + hasAnswers() { + return Object.keys(this.answers).length > 0 + }, }, watch: { hash() { // If public view, abort. Should normally not occur. if (this.publicView) { - logger.error('Hash changed on public View. Aborting.') + logger.error('Hash changsed on public View. Aborting.') return } this.resetData() @@ -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.showRestartSubmissionDueToChangeDialog = 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.showRestartSubmissionDialog = false + this.showRestartSubmissionDueToChangeDialog = 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;