175175 {{ t('save') }}
176176 </span >
177177 </button >
178- <div class =" dropdown dropdown-end" >
178+ <div
179+ class =" dropdown dropdown-end"
180+ :class =" { 'dropdown-open': isDownloading }"
181+ >
179182 <label
180183 tabindex =" 0"
181184 class =" base-button !rounded-l-none !pl-1 !pr-2 !border-l-neutral-500"
209212 <span class =" whitespace-nowrap" >{{ t('preferences') }}</span >
210213 </a >
211214 </li >
215+ <li v-if =" withDownload" >
216+ <button
217+ class =" flex space-x-2"
218+ :disabled =" isDownloading"
219+ @click.stop.prevent =" download"
220+ >
221+ <IconInnerShadowTop
222+ v-if =" isDownloading"
223+ class =" animate-spin w-6 h-6 flex-shrink-0"
224+ />
225+ <IconDownload
226+ v-else
227+ class =" w-6 h-6 flex-shrink-0"
228+ />
229+ <span
230+ v-if =" isDownloading"
231+ class =" whitespace-nowrap"
232+ >{{ t('downloading_') }}</span >
233+ <span
234+ v-else
235+ class =" whitespace-nowrap"
236+ >{{ t('download') }}</span >
237+ </button >
238+ </li >
212239 </ul >
213240 </div >
214241 </span >
457484 :show-tour-start-form =" showTourStartForm"
458485 @add-field =" addField"
459486 @set-draw =" [drawField = $event.field, drawOption = $event.option]"
487+ @select-submitter =" selectedSubmitter = $event"
460488 @set-draw-type =" [drawFieldType = $event, showDrawField = true]"
461489 @set-drag =" dragField = $event"
462490 @set-drag-placeholder =" $refs.dragPlaceholder.dragPlaceholder = $event"
@@ -511,7 +539,7 @@ import DocumentPreview from './preview'
511539import DocumentControls from ' ./controls'
512540import MobileFields from ' ./mobile_fields'
513541import FieldSubmitter from ' ./field_submitter'
514- import { IconPlus , IconUsersPlus , IconDeviceFloppy , IconChevronDown , IconEye , IconWritingSign , IconInnerShadowTop , IconInfoCircle , IconAdjustments } from ' @tabler/icons-vue'
542+ import { IconPlus , IconUsersPlus , IconDeviceFloppy , IconChevronDown , IconEye , IconWritingSign , IconInnerShadowTop , IconInfoCircle , IconAdjustments , IconDownload } from ' @tabler/icons-vue'
515543import { v4 } from ' uuid'
516544import { ref , computed , toRaw } from ' vue'
517545import * as i18n from ' ./i18n'
@@ -537,6 +565,7 @@ export default {
537565 Contenteditable,
538566 IconUsersPlus,
539567 IconChevronDown,
568+ IconDownload,
540569 IconAdjustments,
541570 IconEye,
542571 IconDeviceFloppy
@@ -584,6 +613,11 @@ export default {
584613 required: false ,
585614 default: null
586615 },
616+ withDownload: {
617+ type: Boolean ,
618+ required: false ,
619+ default: false
620+ },
587621 backgroundColor: {
588622 type: String ,
589623 required: false ,
@@ -805,6 +839,7 @@ export default {
805839 return {
806840 documentRefs: [],
807841 isBreakpointLg: false ,
842+ isDownloading: false ,
808843 isLoadingBlankPage: false ,
809844 isSaving: false ,
810845 selectedSubmitter: null ,
@@ -963,6 +998,75 @@ export default {
963998 },
964999 methods: {
9651000 toRaw,
1001+ download () {
1002+ this .isDownloading = true
1003+
1004+ this .baseFetch (` /templates/${ this .template .id } /documents` ).then (async (response ) => {
1005+ if (response .ok ) {
1006+ const urls = await response .json ()
1007+ const isMobileSafariIos = ' ontouchstart' in window && navigator .maxTouchPoints > 0 && / AppleWebKit/ i .test (navigator .userAgent )
1008+ const isSafariIos = isMobileSafariIos || / iPhone| iPad| iPod/ i .test (navigator .userAgent )
1009+
1010+ if (isSafariIos && urls .length > 1 ) {
1011+ this .downloadSafariIos (urls)
1012+ } else {
1013+ this .downloadUrls (urls)
1014+ }
1015+ } else {
1016+ alert (this .t (' failed_to_download_files' ))
1017+ }
1018+ })
1019+ },
1020+ downloadUrls (urls ) {
1021+ const fileRequests = urls .map ((url ) => {
1022+ return () => {
1023+ return fetch (url).then (async (resp ) => {
1024+ const blobUrl = URL .createObjectURL (await resp .blob ())
1025+ const link = document .createElement (' a' )
1026+
1027+ link .href = blobUrl
1028+ link .setAttribute (' download' , decodeURI (url .split (' /' ).pop ()))
1029+
1030+ link .click ()
1031+
1032+ URL .revokeObjectURL (blobUrl)
1033+ })
1034+ }
1035+ })
1036+
1037+ fileRequests .reduce (
1038+ (prevPromise , request ) => prevPromise .then (() => request ()),
1039+ Promise .resolve ()
1040+ ).finally (() => {
1041+ this .isDownloading = false
1042+ })
1043+ },
1044+ downloadSafariIos (urls ) {
1045+ const fileRequests = urls .map ((url ) => {
1046+ return fetch (url).then (async (resp ) => {
1047+ const blob = await resp .blob ()
1048+ const blobUrl = URL .createObjectURL (blob .slice (0 , blob .size , ' application/octet-stream' ))
1049+ const link = document .createElement (' a' )
1050+
1051+ link .href = blobUrl
1052+ link .setAttribute (' download' , decodeURI (url .split (' /' ).pop ()))
1053+
1054+ return link
1055+ })
1056+ })
1057+
1058+ Promise .all (fileRequests).then ((links ) => {
1059+ links .forEach ((link , index ) => {
1060+ setTimeout (() => {
1061+ link .click ()
1062+
1063+ URL .revokeObjectURL (link .href )
1064+ }, index * 50 )
1065+ })
1066+ }).finally (() => {
1067+ this .isDownloading = false
1068+ })
1069+ },
9661070 onDragover (e ) {
9671071 if (this .$refs .dragPlaceholder ? .dragPlaceholder ) {
9681072 this .$refs .dragPlaceholder .isMask = e .target .id === ' mask'
0 commit comments