Skip to content

Commit f18a298

Browse files
committed
feat: downloading forms (#1425)
Signed-off-by: TimedIn <git@timedin.net>
1 parent a138698 commit f18a298

3 files changed

Lines changed: 75 additions & 2 deletions

File tree

src/Forms.vue

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
@openSharing="openSharing"
3333
@mobileCloseNavigation="mobileCloseNavigation"
3434
@clone="onCloneForm"
35+
@download="onDownloadForm"
3536
@delete="onDeleteForm" />
3637
</ul>
3738
</template>
@@ -51,6 +52,7 @@
5152
readOnly
5253
@openSharing="openSharing"
5354
@clone="onCloneForm"
55+
@download="onDownloadForm"
5456
@mobileCloseNavigation="mobileCloseNavigation" />
5557
</ul>
5658
</template>
@@ -170,6 +172,7 @@ import AppNavigationForm from './components/AppNavigationForm.vue'
170172
import ArchivedFormsModal from './components/ArchivedFormsModal.vue'
171173
import Sidebar from './views/Sidebar.vue'
172174
import FormsIcon from '../img/forms-dark.svg?raw'
175+
import { version } from '../package.json'
173176
import PermissionTypes from './mixins/PermissionTypes.js'
174177
import { FormState } from './models/Constants.ts'
175178
import logger from './utils/Logger.js'
@@ -440,6 +443,58 @@ export default {
440443
}
441444
}
442445
446+
const onDownloadForm = async (id) => {
447+
try {
448+
const response = await axios.get(
449+
generateOcsUrl('apps/forms/api/v3/forms/{id}', {
450+
id,
451+
}),
452+
)
453+
const form = OcsResponse2Data(response)
454+
455+
// download only required values
456+
const download = {
457+
appVersion: version,
458+
form: {
459+
...form,
460+
// Remove unused values
461+
...[
462+
'hash',
463+
'ownerId',
464+
'created',
465+
'access',
466+
'lastUpdated',
467+
'lockedBy',
468+
'lockedUntil',
469+
'shares',
470+
'permissions',
471+
'canSubmit',
472+
'isMaxSubmissionsReached',
473+
'submissionCount',
474+
].reduce((prev, curr) => {
475+
prev[curr] = undefined
476+
return prev
477+
}, {}),
478+
479+
id: undefined,
480+
questions: form.questions,
481+
},
482+
}
483+
// create blob and download
484+
const blob = new Blob([JSON.stringify(download)])
485+
const url = URL.createObjectURL(blob)
486+
const a = document.createElement('a')
487+
a.href = url
488+
const formTitle = form.title ? form.title : t('forms', 'New form')
489+
a.download = `${formTitle}.json`
490+
a.click()
491+
URL.revokeObjectURL(url)
492+
} catch (error) {
493+
logger.error(`Unable to download form ${id}`, { error })
494+
showError(t('forms', 'Unable to download form'))
495+
}
496+
}
497+
443498
const onDeleteForm = async (id) => {
444499
const formIndex = forms.value.findIndex((form) => form.id === id)
445500
const deletedHash = forms.value[formIndex].hash
@@ -513,6 +568,7 @@ export default {
513568
fetchPartialForm,
514569
onNewForm,
515570
onCloneForm,
571+
onDownloadForm,
516572
onDeleteForm,
517573
onLastUpdatedByEventBus,
518574
IconPlus,

src/components/AppNavigationForm.vue

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@
6464
</template>
6565
{{ t('forms', 'Copy form') }}
6666
</NcActionButton>
67+
<NcActionButton v-if="canEdit" closeAfterClick @click="onDownloadForm">
68+
<template #icon>
69+
<NcIconSvgWrapper :svg="IconDownload" />
70+
</template>
71+
{{ t('forms', 'Download form') }}
72+
</NcActionButton>
6773
<NcActionSeparator v-if="canEdit && !readOnly" />
6874
<NcActionButton
6975
v-if="canEdit && !readOnly"
@@ -103,6 +109,7 @@ import IconPoll from '@material-symbols/svg-400/outlined/bar_chart.svg?raw'
103109
import IconCheck from '@material-symbols/svg-400/outlined/check.svg?raw'
104110
import IconContentCopy from '@material-symbols/svg-400/outlined/content_copy.svg?raw'
105111
import IconDelete from '@material-symbols/svg-400/outlined/delete.svg?raw'
112+
import IconDownload from '@material-symbols/svg-400/outlined/download.svg?raw'
106113
import IconPencil from '@material-symbols/svg-400/outlined/edit.svg?raw'
107114
import IconShareVariant from '@material-symbols/svg-400/outlined/share.svg?raw'
108115
import IconArchiveOff from '@material-symbols/svg-400/outlined/unarchive.svg?raw'
@@ -154,7 +161,7 @@ export default {
154161
},
155162
},
156163
157-
emits: ['mobileCloseNavigation', 'openSharing', 'clone', 'delete'],
164+
emits: ['mobileCloseNavigation', 'openSharing', 'clone', 'delete', 'download'],
158165
159166
setup() {
160167
return {
@@ -164,6 +171,7 @@ export default {
164171
IconCheck,
165172
IconContentCopy,
166173
IconDelete,
174+
IconDownload,
167175
IconPencil,
168176
IconPoll,
169177
IconShareVariant,
@@ -292,6 +300,10 @@ export default {
292300
this.$emit('clone', this.form.id)
293301
},
294302
303+
onDownloadForm() {
304+
this.$emit('download', this.form.id)
305+
},
306+
295307
async onConfirmDelete() {
296308
const shouldDelete = await showConfirmation({
297309
name: t('forms', 'Delete form'),

src/components/ArchivedFormsModal.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
:form="form"
1818
forceDisplayActions
1919
@clone="onCloneForm(form.id)"
20+
@download="onDownloadForm(form.id)"
2021
@delete="onDelete(form)"
2122
@mobileCloseNavigation="$emit('update:open', false)" />
2223
</ul>
@@ -49,7 +50,7 @@ export default defineComponent({
4950
},
5051
},
5152
52-
emits: ['update:open', 'clone'],
53+
emits: ['update:open', 'clone', 'download'],
5354
5455
data() {
5556
return {
@@ -74,6 +75,10 @@ export default defineComponent({
7475
this.$emit('update:open', false)
7576
},
7677
78+
onDownloadForm(formId) {
79+
this.$emit('download', formId)
80+
},
81+
7782
onDelete(form) {
7883
this.shownForms = this.shownForms.filter(({ id }) => id !== form.id)
7984
},

0 commit comments

Comments
 (0)