Files
docuseal/app/javascript/template_builder/builder.vue
T

1917 lines
59 KiB
Vue
Raw Normal View History

2023-05-21 17:59:36 +03:00
<template>
<div
2025-02-17 19:47:46 +02:00
ref="dragContainer"
2023-05-31 00:50:43 +03:00
style="max-width: 1600px"
2024-12-08 22:34:52 +02:00
class="mx-auto pl-3 h-full"
:class="isMobile ? 'pl-4' : 'md:pl-4'"
2025-02-17 19:47:46 +02:00
@dragover="onDragover"
2025-05-09 00:01:50 +03:00
@drop="isDragFile = false"
2023-05-21 17:59:36 +03:00
>
2025-05-09 00:01:50 +03:00
<HoverDropzone
v-if="sortedDocuments.length && withUploadButton && editable"
:is-dragging="isDragFile"
:template-id="template.id"
:accept-file-types="acceptFileTypes"
:with-replace-and-clone="withReplaceAndCloneUpload"
@add="[updateFromUpload($event), isDragFile = false]"
@replace="[onDocumentsReplace($event), isDragFile = false]"
@replace-and-clone="onDocumentsReplaceAndTemplateClone($event)"
@error="[onUploadFailed($event), isDragFile = false]"
/>
2025-02-17 19:47:46 +02:00
<DragPlaceholder
ref="dragPlaceholder"
:field="fieldsDragFieldRef.value || toRaw(dragField)"
:is-field="template.fields.includes(fieldsDragFieldRef.value)"
:is-default="defaultFields.includes(toRaw(dragField))"
:is-required="defaultRequiredFields.includes(toRaw(dragField))"
/>
2024-06-22 13:16:22 +03:00
<div
2024-11-14 14:38:28 +02:00
v-if="pendingFieldAttachmentUuids.length && editable"
2024-06-22 13:16:22 +03:00
class="top-1.5 sticky h-0 z-20 max-w-2xl mx-auto"
>
2024-06-29 00:04:10 +03:00
<div class="alert border-base-content/30 py-2 px-2.5">
2024-06-22 13:16:22 +03:00
<IconInfoCircle
class="stroke-info shrink-0 w-6 h-6"
/>
<span>{{ t('uploaded_pdf_contains_form_fields_keep_or_remove_them') }}</span>
<div>
<button
class="btn btn-sm"
@click.prevent="removePendingFields"
>
{{ t('remove') }}
</button>
<button
class="btn btn-sm btn-neutral text-white"
@click.prevent="save"
>
{{ t('keep') }}
</button>
</div>
</div>
</div>
2023-10-08 19:28:01 +03:00
<div
2023-11-24 22:44:27 +02:00
v-if="$slots.buttons || withTitle"
2024-02-01 20:59:03 +02:00
id="title_container"
2025-03-18 17:34:11 +02:00
class="flex justify-between py-1.5 items-center pr-4 top-0 z-10 title-container"
:class="{ sticky: withStickySubmitters || isBreakpointLg }"
2023-10-08 19:28:01 +03:00
:style="{ backgroundColor }"
>
2023-11-30 01:50:22 +02:00
<div class="flex items-center space-x-3">
2023-08-19 21:05:54 +03:00
<a
2023-09-30 15:14:29 +03:00
v-if="withLogo"
2023-08-19 21:05:54 +03:00
href="/"
>
2023-06-01 00:46:50 +03:00
<Logo />
</a>
<Contenteditable
2023-11-24 22:44:27 +02:00
v-if="withTitle"
2023-06-01 00:46:50 +03:00
:model-value="template.name"
2023-11-04 01:19:32 +02:00
:editable="editable"
2025-03-18 17:34:11 +02:00
class="text-xl md:text-3xl font-semibold focus:text-clip template-name"
2023-06-04 01:08:45 +03:00
:icon-stroke-width="2.3"
2023-06-01 00:46:50 +03:00
@update:model-value="updateName"
/>
</div>
2024-04-05 19:08:59 +03:00
<div class="space-x-3 flex items-center flex-shrink-0">
2023-08-19 21:05:54 +03:00
<slot
v-if="$slots.buttons"
name="buttons"
/>
<template v-else>
2025-06-01 15:16:00 +03:00
<form
v-if="withSignYourselfButton && template.submitters.length < 2"
target="_blank"
data-turbo="false"
class="inline"
method="post"
:action="`/d/${template.slug}`"
@submit="maybeShowErrorTemplateAlert"
>
<input
type="hidden"
name="_method"
value="put"
autocomplete="off"
>
<input
type="hidden"
name="authenticity_token"
:value="authenticityToken"
autocomplete="off"
>
<input
type="hidden"
name="selfsign"
value="true"
autocomplete="off"
>
<button
class="btn btn-primary btn-ghost text-base hidden md:flex"
type="submit"
>
<IconWritingSign
width="22"
class="inline"
/>
<span class="hidden md:inline">
{{ t('sign_yourself') }}
</span>
</button>
</form>
2023-11-30 01:50:22 +02:00
<a
2025-06-01 15:16:00 +03:00
v-else-if="withSignYourselfButton"
2025-02-21 23:56:18 +02:00
id="sign_yourself_button"
2025-06-01 15:16:00 +03:00
:href="`/templates/${template.id}/submissions/new?selfsign=true`"
2023-11-30 01:50:22 +02:00
class="btn btn-primary btn-ghost text-base hidden md:flex"
2025-06-01 15:16:00 +03:00
data-turbo-frame="modal"
2024-04-15 16:34:44 +03:00
@click="maybeShowErrorTemplateAlert"
2023-11-30 01:50:22 +02:00
>
<IconWritingSign
2024-04-05 19:08:59 +03:00
width="22"
2023-11-30 01:50:22 +02:00
class="inline"
/>
<span class="hidden md:inline">
2024-01-04 01:05:36 +02:00
{{ t('sign_yourself') }}
2023-11-30 01:50:22 +02:00
</span>
</a>
2023-08-19 21:05:54 +03:00
<a
2024-08-26 11:30:52 +03:00
v-if="withSendButton"
2025-02-21 23:56:18 +02:00
id="send_button"
2024-01-13 17:43:34 +02:00
:href="`/templates/${template.id}/submissions/new?with_link=true`"
2023-08-19 21:05:54 +03:00
data-turbo-frame="modal"
2023-11-30 01:50:22 +02:00
class="white-button md:!px-6"
2024-04-15 16:34:44 +03:00
@click="maybeShowErrorTemplateAlert"
2023-08-19 21:05:54 +03:00
>
<IconUsersPlus
width="20"
class="inline"
/>
<span class="hidden md:inline">
2024-01-04 01:05:36 +02:00
{{ t('send') }}
2023-08-19 21:05:54 +03:00
</span>
</a>
2024-07-02 20:27:25 +03:00
<span
v-if="editable"
2025-02-21 23:56:18 +02:00
id="save_button_container"
2024-07-02 20:27:25 +03:00
class="flex"
2023-08-19 21:05:54 +03:00
>
2024-07-02 20:27:25 +03:00
<button
class="base-button !rounded-r-none !pr-2"
:class="{ disabled: isSaving }"
v-bind="isSaving ? { disabled: true } : {}"
@click.prevent="onSaveClick"
>
<IconInnerShadowTop
v-if="isSaving"
width="22"
class="animate-spin"
/>
<IconDeviceFloppy
v-else
width="22"
/>
<span class="hidden md:inline">
{{ t('save') }}
</span>
</button>
<div class="dropdown dropdown-end">
<label
tabindex="0"
class="base-button !rounded-l-none !pl-1 !pr-2 !border-l-neutral-500"
>
<span class="text-sm align-text-top">
<IconChevronDown class="w-5 h-5 flex-shrink-0" />
</span>
</label>
<ul
tabindex="0"
class="dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box text-right"
>
<li>
<a
:href="`/templates/${template.id}/form`"
data-turbo="false"
class="flex items-center justify-center space-x-2"
>
<IconEye class="w-6 h-6 flex-shrink-0" />
2024-09-18 19:47:47 +03:00
<span class="whitespace-nowrap">{{ t('save_and_preview') }}</span>
2024-07-02 20:27:25 +03:00
</a>
</li>
2024-09-03 00:37:31 +03:00
<li>
<a
:href="`/templates/${template.id}/preferences`"
data-turbo-frame="modal"
class="flex space-x-2"
@click="closeDropdown"
>
<IconAdjustments class="w-6 h-6 flex-shrink-0" />
2024-09-18 19:47:47 +03:00
<span class="whitespace-nowrap">{{ t('preferences') }}</span>
2024-09-03 00:37:31 +03:00
</a>
</li>
2024-07-02 20:27:25 +03:00
</ul>
</div>
</span>
<a
v-else
:href="`/templates/${template.id}`"
class="base-button"
>
<span class="hidden md:inline">
{{ t('back') }}
</span>
</a>
2023-08-19 21:05:54 +03:00
</template>
2023-05-31 00:50:43 +03:00
</div>
2023-05-21 17:59:36 +03:00
</div>
2023-11-24 22:44:27 +02:00
<div
2024-03-07 18:59:19 +02:00
id="main_container"
2025-03-18 17:34:11 +02:00
class="flex main-container"
2024-12-08 22:34:52 +02:00
:class="$slots.buttons || withTitle ? (isMobile ? 'max-h-[calc(100%_-_60px)]' : 'md:max-h-[calc(100%_-_60px)]') : (isMobile ? 'max-h-[100%]' : 'md:max-h-[100%]')"
2023-11-24 22:44:27 +02:00
>
2023-05-21 17:59:36 +03:00
<div
2023-12-20 22:13:19 +02:00
v-if="withDocumentsList"
2024-03-07 18:59:19 +02:00
id="documents_container"
2023-05-31 00:50:43 +03:00
ref="previews"
2023-08-19 21:05:54 +03:00
:style="{ 'display': isBreakpointLg ? 'none' : 'initial' }"
2023-07-02 00:14:26 +03:00
class="overflow-y-auto overflow-x-hidden w-52 flex-none pr-3 mt-0.5 pt-0.5 hidden lg:block"
2023-05-21 17:59:36 +03:00
>
2023-05-31 00:50:43 +03:00
<DocumentPreview
2023-05-31 23:19:20 +03:00
v-for="(item, index) in template.schema"
2023-05-31 00:50:43 +03:00
:key="index"
2023-05-31 23:19:20 +03:00
:with-arrows="template.schema.length > 1"
2023-05-31 00:50:43 +03:00
:item="item"
:document="sortedDocuments[index]"
2025-01-10 16:00:14 +02:00
:data-document-uuid="item.attachment_uuid"
2023-09-05 20:33:40 +03:00
:accept-file-types="acceptFileTypes"
2023-11-09 10:54:56 +02:00
:with-replace-button="withUploadButton"
2023-11-04 01:19:32 +02:00
:editable="editable"
2023-07-26 20:35:53 +03:00
:template="template"
2023-05-31 00:50:43 +03:00
@scroll-to="scrollIntoDocument(item)"
@remove="onDocumentRemove"
2023-07-26 20:35:53 +03:00
@replace="onDocumentReplace"
2023-05-31 00:50:43 +03:00
@up="moveDocument(item, -1)"
2025-01-04 14:01:09 +02:00
@reorder="reorderFields"
2023-05-31 00:50:43 +03:00
@down="moveDocument(item, 1)"
@change="save"
2023-05-21 17:59:36 +03:00
/>
2023-08-28 23:43:33 +03:00
<div
2024-07-09 23:47:46 +03:00
class="sticky bottom-0 py-2 space-y-2"
2024-01-18 21:12:08 +02:00
:style="{ backgroundColor }"
2023-08-28 23:43:33 +03:00
>
2023-05-31 00:50:43 +03:00
<Upload
2023-11-09 10:54:56 +02:00
v-if="sortedDocuments.length && editable && withUploadButton"
2023-09-05 20:33:40 +03:00
:accept-file-types="acceptFileTypes"
2023-05-31 23:19:20 +03:00
:template-id="template.id"
2023-05-31 00:50:43 +03:00
@success="updateFromUpload"
/>
2024-07-09 23:47:46 +03:00
<button
v-if="sortedDocuments.length && editable && withAddPageButton"
id="add_blank_page_button"
2025-03-18 17:34:11 +02:00
class="btn btn-outline w-full add-blank-page-button"
2024-07-09 23:47:46 +03:00
@click.prevent="addBlankPage"
>
<IconInnerShadowTop
v-if="isLoadingBlankPage"
class="animate-spin w-5 h-5"
/>
<IconPlus
v-else
class="w-5 h-5"
/>
{{ t('add_blank_page') }}
</button>
2023-05-31 00:50:43 +03:00
</div>
</div>
2024-03-07 18:59:19 +02:00
<div
id="pages_container"
2025-01-15 01:19:17 +02:00
class="w-full overflow-x-hidden mt-0.5 pt-0.5"
:class="isMobile ? 'overflow-y-auto' : 'overflow-y-hidden md:overflow-y-auto'"
2024-03-07 18:59:19 +02:00
>
2023-06-04 01:08:45 +03:00
<div
ref="documents"
class="pr-3.5 pl-0.5"
>
2024-07-09 23:47:46 +03:00
<template v-if="!sortedDocuments.length && (withUploadButton || withAddPageButton)">
<Dropzone
v-if="withUploadButton"
:template-id="template.id"
:accept-file-types="acceptFileTypes"
@success="updateFromUpload"
/>
<button
v-if="withAddPageButton"
id="add_blank_page_button"
2025-03-18 17:34:11 +02:00
class="btn btn-outline w-full mt-4 add-blank-page-button"
2024-07-09 23:47:46 +03:00
@click.prevent="addBlankPage"
>
<IconInnerShadowTop
v-if="isLoadingBlankPage"
class="animate-spin w-5 h-5"
/>
<IconPlus
v-else
class="w-5 h-5"
/>
{{ t('add_blank_page') }}
</button>
</template>
2023-07-01 00:15:15 +03:00
<template v-else>
2023-08-19 21:05:54 +03:00
<template
2023-07-01 00:15:15 +03:00
v-for="document in sortedDocuments"
:key="document.uuid"
2023-08-19 21:05:54 +03:00
>
<Document
:ref="setDocumentRefs"
:areas-index="fieldAreasIndex[document.uuid]"
:selected-submitter="selectedSubmitter"
:document="document"
2023-11-11 23:36:03 +02:00
:is-drag="!!dragField"
2024-07-16 19:01:44 +03:00
:input-mode="inputMode"
2024-04-15 16:34:44 +03:00
:default-fields="[...defaultRequiredFields, ...defaultFields]"
2025-03-12 19:14:00 +02:00
:allow-draw="!onlyDefinedFields || drawField"
2025-07-17 10:54:44 +03:00
:with-signature-id="withSignatureId"
2025-07-27 10:27:12 +03:00
:with-prefillable="withPrefillable"
2025-01-10 16:00:14 +02:00
:data-document-uuid="document.uuid"
2024-01-23 23:20:19 +02:00
:default-submitters="defaultSubmitters"
2025-02-17 19:47:46 +02:00
:drag-field-placeholder="fieldsDragFieldRef.value || dragField"
2024-07-15 14:03:36 +03:00
:with-field-placeholder="withFieldPlaceholder"
2023-08-19 21:05:54 +03:00
:draw-field="drawField"
2024-03-27 13:47:17 +02:00
:draw-field-type="drawFieldType"
2023-11-04 01:19:32 +02:00
:editable="editable"
2023-11-11 21:16:29 +02:00
:base-url="baseUrl"
2024-03-27 13:47:17 +02:00
@draw="[onDraw($event), withSelectedFieldType ? '' : drawFieldType = '', showDrawField = false]"
2023-08-19 21:05:54 +03:00
@drop-field="onDropfield"
@remove-area="removeArea"
/>
<DocumentControls
2023-11-04 01:19:32 +02:00
v-if="isBreakpointLg && editable"
2023-08-19 21:05:54 +03:00
:with-arrows="template.schema.length > 1"
:item="template.schema.find((item) => item.attachment_uuid === document.uuid)"
2023-11-09 10:54:56 +02:00
:with-replace-button="withUploadButton"
2024-04-06 21:55:46 +03:00
:accept-file-types="acceptFileTypes"
2023-08-19 21:05:54 +03:00
:document="document"
:template="template"
class="pb-2 mb-2 border-b border-base-300 border-dashed"
@remove="onDocumentRemove"
@replace="onDocumentReplace"
@up="moveDocument(template.schema.find((item) => item.attachment_uuid === document.uuid), -1)"
@down="moveDocument(template.schema.find((item) => item.attachment_uuid === document.uuid), 1)"
@change="save"
/>
</template>
<div
2023-11-04 01:19:32 +02:00
v-if="sortedDocuments.length && isBreakpointLg && editable"
2024-07-09 23:47:46 +03:00
class="pb-4 space-y-2"
2023-08-19 21:05:54 +03:00
>
<Upload
2023-11-09 10:54:56 +02:00
v-if="withUploadButton"
2023-08-19 21:05:54 +03:00
:template-id="template.id"
2024-01-13 23:53:55 +02:00
:accept-file-types="acceptFileTypes"
2023-08-19 21:05:54 +03:00
@success="updateFromUpload"
/>
2024-07-09 23:47:46 +03:00
<button
v-if="withAddPageButton"
id="add_blank_page_button"
2025-03-18 17:34:11 +02:00
class="btn btn-outline w-full mt-4 add-blank-page-button"
2024-07-09 23:47:46 +03:00
@click.prevent="addBlankPage"
>
<IconInnerShadowTop
v-if="isLoadingBlankPage"
class="animate-spin w-5 h-5"
/>
<IconPlus
v-else
class="w-5 h-5"
/>
{{ t('add_blank_page') }}
</button>
2023-08-19 21:05:54 +03:00
</div>
2023-07-01 00:15:15 +03:00
</template>
2023-05-31 00:50:43 +03:00
</div>
</div>
<div
2024-12-08 22:34:52 +02:00
v-if="withFieldsList && !isMobile"
2024-03-07 18:59:19 +02:00
id="fields_list_container"
2025-03-18 17:34:11 +02:00
class="relative w-80 flex-none mt-1 pr-4 pl-0.5 hidden md:block fields-list-container"
2023-12-31 17:23:27 +02:00
:class="drawField ? 'overflow-hidden' : 'overflow-y-auto overflow-x-hidden'"
2023-05-31 00:50:43 +03:00
>
<div
2024-03-27 13:47:17 +02:00
v-if="showDrawField || drawField"
2024-01-01 13:18:09 +02:00
class="sticky inset-0 h-full z-20"
2023-11-24 22:44:27 +02:00
:style="{ backgroundColor }"
2023-05-31 00:50:43 +03:00
>
2025-03-18 17:34:11 +02:00
<div class="bg-base-200 rounded-lg p-5 text-center space-y-4 draw-field-container">
2023-06-08 00:20:42 +03:00
<p>
2024-10-02 17:57:31 +03:00
{{ t('draw_field_on_the_document') }}
2023-06-08 00:20:42 +03:00
</p>
2024-01-06 21:16:27 +02:00
<div>
2023-06-08 00:20:42 +03:00
<button
2025-03-18 17:34:11 +02:00
class="base-button cancel-draw-button"
2024-01-06 21:16:27 +02:00
@click="clearDrawField"
2023-06-08 00:20:42 +03:00
>
2024-01-04 01:05:36 +02:00
{{ t('cancel') }}
2023-06-08 00:20:42 +03:00
</button>
2024-01-06 21:16:27 +02:00
<a
2024-07-11 21:40:15 +03:00
v-if="!drawField && !drawOption && !['stamp', 'signature', 'initials', 'heading'].includes(drawField?.type || drawFieldType)"
2024-01-06 21:16:27 +02:00
href="#"
class="link block mt-3 text-sm"
2024-03-27 13:47:17 +02:00
@click.prevent="[addField(drawFieldType), drawField = null, drawOption = null, withSelectedFieldType ? '' : drawFieldType = '', showDrawField = false]"
2024-01-06 21:16:27 +02:00
>
{{ t('or_add_field_without_drawing') }}
</a>
</div>
2023-06-08 00:20:42 +03:00
</div>
2023-05-31 00:50:43 +03:00
</div>
<div>
<Fields
ref="fields"
2023-06-07 01:16:43 +03:00
:fields="template.fields"
:submitters="template.submitters"
:selected-submitter="selectedSubmitter"
2024-01-04 01:05:36 +02:00
:with-help="withHelp"
2023-12-17 16:10:17 +02:00
:default-submitters="defaultSubmitters"
2024-03-27 13:47:17 +02:00
:draw-field-type="drawFieldType"
2025-01-28 14:13:06 +02:00
:with-fields-search="withFieldsSearch"
2024-04-15 16:34:44 +03:00
:default-fields="[...defaultRequiredFields, ...defaultFields]"
2024-12-07 01:35:59 +02:00
:template="template"
2024-04-15 16:34:44 +03:00
:default-required-fields="defaultRequiredFields"
2024-01-10 23:18:30 +02:00
:field-types="fieldTypes"
2023-08-19 21:05:54 +03:00
:with-sticky-submitters="withStickySubmitters"
2025-07-17 10:54:44 +03:00
:with-signature-id="withSignatureId"
2025-07-27 10:27:12 +03:00
:with-prefillable="withPrefillable"
2024-01-05 18:40:45 +02:00
:only-defined-fields="onlyDefinedFields"
2023-11-04 01:19:32 +02:00
:editable="editable"
2025-02-21 23:56:18 +02:00
:show-tour-start-form="showTourStartForm"
2024-03-27 13:47:17 +02:00
@add-field="addField"
2023-11-14 01:30:00 +02:00
@set-draw="[drawField = $event.field, drawOption = $event.option]"
2024-03-27 13:47:17 +02:00
@set-draw-type="[drawFieldType = $event, showDrawField = true]"
2023-11-11 23:36:03 +02:00
@set-drag="dragField = $event"
2025-02-17 19:47:46 +02:00
@set-drag-placeholder="$refs.dragPlaceholder.dragPlaceholder = $event"
2023-06-07 01:16:43 +03:00
@change-submitter="selectedSubmitter = $event"
2025-02-17 19:47:46 +02:00
@drag-end="[dragField = null, $refs.dragPlaceholder.dragPlaceholder = null]"
2023-06-03 00:53:46 +03:00
@scroll-to-area="scrollToArea"
2023-05-31 00:50:43 +03:00
/>
</div>
2023-05-21 17:59:36 +03:00
</div>
</div>
2024-12-08 22:34:52 +02:00
<div class="sticky bottom-0 z-10">
2024-01-24 01:11:04 +02:00
<MobileDrawField
2024-12-08 22:34:52 +02:00
v-if="drawField && (isBreakpointLg || isMobile)"
2024-01-24 01:11:04 +02:00
:draw-field="drawField"
:fields="template.fields"
:submitters="template.submitters"
:selected-submitter="selectedSubmitter"
2024-12-08 22:34:52 +02:00
:class="{ 'md:hidden': !isMobile }"
2024-01-24 01:11:04 +02:00
:editable="editable"
@cancel="[drawField = null, drawOption = null]"
@change-submitter="[selectedSubmitter = $event, drawField.submitter_uuid = $event.uuid]"
/>
<MobileFields
v-if="sortedDocuments.length && !drawField && editable"
:fields="template.fields"
2024-04-15 16:34:44 +03:00
:default-fields="[...defaultRequiredFields, ...defaultFields]"
2024-04-16 10:42:11 +03:00
:default-required-fields="defaultRequiredFields"
2024-01-24 01:11:04 +02:00
:field-types="fieldTypes"
2024-12-08 22:34:52 +02:00
:class="{ 'md:hidden': !isMobile }"
2024-01-24 01:11:04 +02:00
:selected-submitter="selectedSubmitter"
@select="startFieldDraw($event)"
/>
</div>
2025-03-18 17:34:11 +02:00
<div
id="docuseal_modal_container"
class="modal-container"
/>
2023-05-21 17:59:36 +03:00
</div>
</template>
<script>
import Upload from './upload'
2023-07-01 00:15:15 +03:00
import Dropzone from './dropzone'
2025-04-25 21:19:28 +03:00
import HoverDropzone from './hover_dropzone'
2025-02-17 19:47:46 +02:00
import DragPlaceholder from './drag_placeholder'
2023-05-21 17:59:36 +03:00
import Fields from './fields'
2023-11-06 20:49:44 +02:00
import MobileDrawField from './mobile_draw_field'
2023-05-21 17:59:36 +03:00
import Document from './document'
2023-06-01 00:46:50 +03:00
import Logo from './logo'
2023-05-31 00:50:43 +03:00
import Contenteditable from './contenteditable'
import DocumentPreview from './preview'
2023-08-19 21:05:54 +03:00
import DocumentControls from './controls'
import MobileFields from './mobile_fields'
2024-10-30 23:02:03 +02:00
import FieldSubmitter from './field_submitter'
2024-09-03 00:37:31 +03:00
import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments } from '@tabler/icons-vue'
2023-06-03 00:53:46 +03:00
import { v4 } from 'uuid'
2025-02-17 19:47:46 +02:00
import { ref, computed, toRaw } from 'vue'
2024-09-23 15:22:07 +03:00
import * as i18n from './i18n'
2023-06-04 01:08:45 +03:00
2023-05-21 17:59:36 +03:00
export default {
2023-05-31 23:19:20 +03:00
name: 'TemplateBuilder',
2023-05-21 17:59:36 +03:00
components: {
Upload,
2025-02-17 19:47:46 +02:00
DragPlaceholder,
2023-05-21 17:59:36 +03:00
Document,
2023-05-31 00:50:43 +03:00
Fields,
2024-06-22 13:16:22 +03:00
IconInfoCircle,
2023-11-06 20:49:44 +02:00
MobileDrawField,
2024-07-09 23:47:46 +03:00
IconPlus,
2023-11-30 01:50:22 +02:00
IconWritingSign,
MobileFields,
2023-06-01 00:46:50 +03:00
Logo,
2023-07-01 00:15:15 +03:00
Dropzone,
2025-04-25 21:19:28 +03:00
HoverDropzone,
2023-05-31 00:50:43 +03:00
DocumentPreview,
2023-08-19 21:05:54 +03:00
DocumentControls,
2023-06-24 16:17:07 +03:00
IconInnerShadowTop,
2023-05-31 00:50:43 +03:00
Contenteditable,
IconUsersPlus,
2024-07-02 20:27:25 +03:00
IconChevronDown,
2024-09-03 00:37:31 +03:00
IconAdjustments,
2024-07-02 20:27:25 +03:00
IconEye,
2023-05-31 00:50:43 +03:00
IconDeviceFloppy
2023-05-21 17:59:36 +03:00
},
2023-06-04 01:08:45 +03:00
provide () {
return {
template: this.template,
2023-06-24 16:17:07 +03:00
save: this.save,
2024-01-04 01:05:36 +02:00
t: this.t,
2025-02-17 19:47:46 +02:00
assignDropAreaSize: this.assignDropAreaSize,
2024-01-26 10:43:32 +02:00
currencies: this.currencies,
2024-08-05 19:38:47 +03:00
locale: this.locale,
2023-08-19 21:05:54 +03:00
baseFetch: this.baseFetch,
2024-01-10 23:18:30 +02:00
fieldTypes: this.fieldTypes,
2023-08-28 23:43:33 +03:00
backgroundColor: this.backgroundColor,
2023-09-05 01:00:21 +03:00
withPhone: this.withPhone,
2024-12-01 00:10:07 +02:00
withVerification: this.withVerification,
2023-12-14 21:22:26 +02:00
withPayment: this.withPayment,
2024-03-29 22:51:58 +02:00
isPaymentConnected: this.isPaymentConnected,
2024-02-24 01:01:36 +02:00
withFormula: this.withFormula,
2024-03-01 23:29:37 +02:00
withConditions: this.withConditions,
2025-07-14 12:53:15 +03:00
isInlineSize: this.isInlineSize,
2024-02-20 22:15:21 +02:00
defaultDrawFieldType: this.defaultDrawFieldType,
2024-02-27 01:41:57 +02:00
selectedAreaRef: computed(() => this.selectedAreaRef),
fieldsDragFieldRef: computed(() => this.fieldsDragFieldRef)
2023-06-04 01:08:45 +03:00
}
},
2023-05-21 17:59:36 +03:00
props: {
2023-05-31 23:19:20 +03:00
template: {
2023-05-25 22:47:52 +03:00
type: Object,
required: true
},
2024-01-04 01:05:36 +02:00
i18n: {
type: Object,
required: false,
default: () => ({})
},
2024-07-15 14:03:36 +03:00
withFieldPlaceholder: {
type: Boolean,
required: false,
default: false
},
2025-07-17 10:54:44 +03:00
withSignatureId: {
type: Boolean,
required: false,
default: null
},
2023-08-28 23:43:33 +03:00
backgroundColor: {
type: String,
required: false,
default: ''
},
2024-08-05 19:38:47 +03:00
locale: {
type: String,
required: false,
default: ''
},
2023-11-04 01:19:32 +02:00
editable: {
type: Boolean,
required: false,
default: true
},
2024-08-26 11:30:52 +03:00
withSendButton: {
type: Boolean,
required: false,
default: true
},
withSignYourselfButton: {
type: Boolean,
required: false,
default: true
},
2024-07-16 19:01:44 +03:00
inputMode: {
type: Boolean,
required: false,
default: false
},
2024-01-04 01:05:36 +02:00
withHelp: {
type: Boolean,
required: false,
default: true
},
2024-07-09 23:47:46 +03:00
withAddPageButton: {
type: Boolean,
required: false,
default: false
},
2024-01-03 21:44:04 +02:00
autosave: {
type: Boolean,
required: false,
default: true
},
2023-11-11 23:36:03 +02:00
defaultFields: {
type: Array,
required: false,
default: () => []
},
2024-04-15 16:34:44 +03:00
defaultRequiredFields: {
type: Array,
required: false,
default: () => []
},
2024-03-27 13:47:17 +02:00
withSelectedFieldType: {
type: Boolean,
required: false,
default: false
},
2024-02-20 22:15:21 +02:00
defaultDrawFieldType: {
type: String,
required: false,
default: 'text'
},
2024-01-26 10:43:32 +02:00
currencies: {
type: Array,
required: false,
default: () => []
},
2024-01-10 23:18:30 +02:00
fieldTypes: {
type: Array,
required: false,
default: () => []
},
2023-11-11 23:36:03 +02:00
defaultSubmitters: {
type: Array,
required: false,
default: () => []
},
2024-10-30 23:02:03 +02:00
defineSubmitters: {
type: Array,
required: false,
default: () => []
},
2023-09-05 20:33:40 +03:00
acceptFileTypes: {
type: String,
required: false,
2025-08-12 10:32:50 +03:00
default: 'image/*, application/pdf, application/zip'
2023-09-05 20:33:40 +03:00
},
2023-08-19 21:05:54 +03:00
baseUrl: {
type: String,
required: false,
default: ''
},
2023-09-30 15:14:29 +03:00
withLogo: {
2023-08-19 21:05:54 +03:00
type: Boolean,
required: false,
default: true
},
2023-12-19 19:27:07 +02:00
onUpload: {
type: Function,
required: false,
default () {
return () => {}
}
},
2024-01-10 17:54:35 +02:00
onSave: {
type: Function,
required: false,
default () {
return () => {}
}
},
2024-04-15 16:34:44 +03:00
onChange: {
type: Function,
required: false,
default () {
return () => {}
}
},
2023-08-19 21:05:54 +03:00
withStickySubmitters: {
type: Boolean,
required: false,
default: true
},
2023-11-09 10:54:56 +02:00
withUploadButton: {
type: Boolean,
required: false,
default: true
},
2023-11-24 22:44:27 +02:00
withTitle: {
type: Boolean,
required: false,
default: true
},
2025-01-28 14:13:06 +02:00
withFieldsSearch: {
type: Boolean,
required: false,
default: null
},
2023-12-20 22:13:19 +02:00
withFieldsList: {
type: Boolean,
required: false,
default: true
},
2024-03-23 23:16:40 +02:00
authenticityToken: {
type: String,
required: false,
default: ''
},
2023-12-20 22:13:19 +02:00
withDocumentsList: {
type: Boolean,
required: false,
default: true
},
2025-04-25 21:19:28 +03:00
withReplaceAndCloneUpload: {
type: Boolean,
required: false,
2025-05-09 00:01:50 +03:00
default: false
2025-04-25 21:19:28 +03:00
},
2023-09-05 01:00:21 +03:00
withPhone: {
type: Boolean,
required: false,
default: false
},
2024-12-01 00:10:07 +02:00
withVerification: {
type: Boolean,
required: false,
default: null
},
2023-12-14 21:22:26 +02:00
withPayment: {
type: Boolean,
required: false,
default: false
},
2024-03-29 22:51:58 +02:00
isPaymentConnected: {
type: Boolean,
required: false,
default: false
},
2024-02-24 01:01:36 +02:00
withFormula: {
type: Boolean,
required: false,
default: false
},
2024-03-01 23:29:37 +02:00
withConditions: {
type: Boolean,
required: false,
default: false
},
2024-01-05 18:40:45 +02:00
onlyDefinedFields: {
type: Boolean,
required: false,
default: false
},
2023-08-19 21:05:54 +03:00
fetchOptions: {
type: Object,
required: false,
default: () => ({ headers: {} })
2025-02-21 23:56:18 +02:00
},
showTourStartForm: {
type: Boolean,
required: false,
default: false
2023-05-21 17:59:36 +03:00
}
},
data () {
return {
2023-05-31 00:50:43 +03:00
documentRefs: [],
2023-08-19 21:05:54 +03:00
isBreakpointLg: false,
2024-07-09 23:47:46 +03:00
isLoadingBlankPage: false,
2023-05-31 00:50:43 +03:00
isSaving: false,
2023-06-07 01:16:43 +03:00
selectedSubmitter: null,
2024-03-27 13:47:17 +02:00
showDrawField: false,
2024-06-22 13:16:22 +03:00
pendingFieldAttachmentUuids: [],
2023-05-21 17:59:36 +03:00
drawField: null,
2024-06-27 15:14:32 +03:00
copiedArea: null,
2024-03-27 13:47:17 +02:00
drawFieldType: null,
2023-11-14 01:30:00 +02:00
drawOption: null,
2025-04-25 21:19:28 +03:00
dragField: null,
2025-05-09 00:01:50 +03:00
isDragFile: false
2023-05-21 17:59:36 +03:00
}
},
computed: {
2024-10-30 23:02:03 +02:00
submitterDefaultNames: FieldSubmitter.computed.names,
2023-06-09 01:43:18 +03:00
selectedAreaRef: () => ref(),
2024-02-27 01:41:57 +02:00
fieldsDragFieldRef: () => ref(),
2024-09-23 15:22:07 +03:00
language () {
return this.locale.split('-')[0].toLowerCase()
},
2025-07-27 10:27:12 +03:00
withPrefillable () {
if (this.template.fields) {
return this.template.fields.some((f) => f.prefillable)
} else {
return false
}
},
2025-07-14 12:53:15 +03:00
isInlineSize () {
return CSS.supports('container-type: size')
},
2024-12-08 22:34:52 +02:00
isMobile () {
2025-01-11 14:02:04 +02:00
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
2025-01-10 21:49:30 +02:00
return isMobileSafariIos || /android|iphone|ipad/i.test(navigator.userAgent)
2024-12-08 22:34:52 +02:00
},
2024-08-05 19:38:47 +03:00
defaultDateFormat () {
const isUsBrowser = Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US')
const isUsTimezone = new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/)
return this.localeDateFormats[this.locale] || ((isUsBrowser || isUsTimezone) ? 'MM/DD/YYYY' : 'DD/MM/YYYY')
},
localeDateFormats () {
return {
'de-DE': 'DD.MM.YYYY',
'fr-FR': 'DD/MM/YYYY',
'it-IT': 'DD/MM/YYYY',
2024-10-20 09:48:39 +03:00
'en-GB': 'DD/MM/YYYY',
2024-08-05 19:38:47 +03:00
'es-ES': 'DD/MM/YYYY'
}
},
2023-05-21 17:59:36 +03:00
fieldAreasIndex () {
const areas = {}
2023-05-31 23:19:20 +03:00
this.template.fields.forEach((f) => {
2023-05-21 17:59:36 +03:00
(f.areas || []).forEach((a) => {
areas[a.attachment_uuid] ||= {}
const acc = (areas[a.attachment_uuid][a.page] ||= [])
acc.push({ area: a, field: f })
})
})
return areas
},
2024-04-15 16:34:44 +03:00
isAllRequiredFieldsAdded () {
return !this.defaultRequiredFields?.some((f) => {
return !this.template.fields?.some((field) => field.name === f.name)
})
},
2023-06-04 01:08:45 +03:00
selectedField () {
2023-06-09 01:43:18 +03:00
return this.template.fields.find((f) => f.areas?.includes(this.selectedAreaRef.value))
2023-06-04 01:08:45 +03:00
},
2023-05-21 17:59:36 +03:00
sortedDocuments () {
2023-05-31 23:19:20 +03:00
return this.template.schema.map((item) => {
return this.template.documents.find(doc => doc.uuid === item.attachment_uuid)
2023-05-21 17:59:36 +03:00
})
}
},
2023-06-07 01:16:43 +03:00
created () {
2024-04-05 02:21:14 +03:00
if (!this.template.fields?.length && this.template.submitters?.length === 1) {
if (this.template.submitters[0]?.name === 'First Party') {
this.template.submitters[0].name = this.t('first_party')
}
}
2023-12-25 12:22:13 +02:00
const existingSubmittersUuids = this.defaultSubmitters.map((name) => {
return this.template.submitters.find(e => e.name === name)?.uuid
})
2023-11-11 23:36:03 +02:00
this.defaultSubmitters.forEach((name, index) => {
const submitter = (this.template.submitters[index] ||= {})
submitter.name = name
2023-12-25 12:22:13 +02:00
2024-10-30 23:02:03 +02:00
if (existingSubmittersUuids.filter(Boolean).length) {
submitter.uuid = existingSubmittersUuids[index] || submitter.uuid || v4()
} else {
submitter.uuid ||= v4()
}
})
2025-01-07 13:04:59 +02:00
const defineSubmittersUuids = this.defineSubmitters.map((name) => {
return this.template.submitters.find(e => e.name === name)?.uuid
})
2024-10-30 23:02:03 +02:00
this.defineSubmitters.forEach((name, index) => {
const submitter = (this.template.submitters[index] ||= {})
submitter.name = name || this.submitterDefaultNames[index]
2025-01-07 13:04:59 +02:00
if (defineSubmittersUuids.filter(Boolean).length || existingSubmittersUuids.filter(Boolean).length) {
submitter.uuid = defineSubmittersUuids[index] || existingSubmittersUuids[index] || submitter.uuid || v4()
2023-12-25 12:22:13 +02:00
} else {
submitter.uuid ||= v4()
}
2023-11-11 23:36:03 +02:00
})
2023-06-07 01:16:43 +03:00
this.selectedSubmitter = this.template.submitters[0]
},
2023-05-21 17:59:36 +03:00
mounted () {
2023-09-28 01:48:40 +03:00
this.undoStack = [JSON.stringify(this.template)]
this.redoStack = []
2023-08-19 21:05:54 +03:00
this.$nextTick(() => {
this.onWindowResize()
})
2023-06-07 01:16:43 +03:00
document.addEventListener('keyup', this.onKeyUp)
2023-09-28 01:48:40 +03:00
window.addEventListener('keydown', this.onKeyDown)
2023-08-19 21:05:54 +03:00
window.addEventListener('resize', this.onWindowResize)
2025-04-25 21:19:28 +03:00
window.addEventListener('dragleave', this.onWindowDragLeave)
2023-12-14 21:22:26 +02:00
this.$nextTick(() => {
if (document.location.search?.includes('stripe_connect_success')) {
document.querySelector('form[action="/auth/stripe_connect"]')?.closest('.dropdown')?.querySelector('label')?.focus()
}
})
2024-06-22 13:16:22 +03:00
this.template.schema.forEach((item) => {
if (item.pending_fields) {
this.pendingFieldAttachmentUuids.push(item.attachment_uuid)
}
})
2023-05-21 17:59:36 +03:00
},
unmounted () {
2023-06-07 01:16:43 +03:00
document.removeEventListener('keyup', this.onKeyUp)
2023-09-28 01:48:40 +03:00
window.removeEventListener('keydown', this.onKeyDown)
2023-08-19 21:05:54 +03:00
window.removeEventListener('resize', this.onWindowResize)
2025-04-25 21:19:28 +03:00
window.removeEventListener('dragleave', this.onWindowDragLeave)
2023-05-21 17:59:36 +03:00
},
2023-05-31 00:50:43 +03:00
beforeUpdate () {
this.documentRefs = []
},
2023-05-21 17:59:36 +03:00
methods: {
2025-02-17 19:47:46 +02:00
toRaw,
onDragover (e) {
if (this.$refs.dragPlaceholder?.dragPlaceholder) {
this.$refs.dragPlaceholder.isMask = e.target.id === 'mask'
const ref = this.$refs.dragPlaceholder.dragPlaceholder
ref.x = e.clientX - ref.offsetX
ref.y = e.clientY - ref.offsetY
2025-04-25 21:19:28 +03:00
} else if (e.dataTransfer?.types?.includes('Files')) {
2025-05-09 00:01:50 +03:00
this.isDragFile = true
2025-04-25 21:19:28 +03:00
}
},
onWindowDragLeave (event) {
if (event.clientX <= 0 || event.clientY <= 0 || event.clientX >= window.innerWidth || event.clientY >= window.innerHeight) {
2025-05-09 00:01:50 +03:00
this.isDragFile = false
2025-02-17 19:47:46 +02:00
}
},
2025-01-04 14:01:09 +02:00
reorderFields (item) {
const itemFields = []
const fields = []
const fieldAreasIndex = {}
const attachmentUuids = this.template.schema.map((e) => e.attachment_uuid)
this.template.fields.forEach((f) => {
if (f.areas?.length) {
const firstArea = f.areas.reduce((min, a) => {
return attachmentUuids.indexOf(a.attachment_uuid) < attachmentUuids.indexOf(min.attachment_uuid) ? a : min
}, f.areas[0])
if (firstArea.attachment_uuid === item.attachment_uuid) {
itemFields.push(f)
} else {
fields.push(f)
}
} else {
fields.push(f)
}
})
const sortArea = (aArea, bArea) => {
if (aArea.attachment_uuid === bArea.attachment_uuid) {
if (aArea.page === bArea.page) {
if (Math.abs(aArea.y - bArea.y) < 0.01) {
if (aArea.x === bArea.x) {
return 0
} else {
return aArea.x - bArea.x
}
} else {
return aArea.y - bArea.y
}
} else {
return aArea.page - bArea.page
}
} else {
return attachmentUuids.indexOf(aArea.attachment_uuid) - attachmentUuids.indexOf(bArea.attachment_uuid)
}
}
itemFields.sort((aField, bField) => {
const aArea = (fieldAreasIndex[aField.uuid] ||= [...(aField.areas || [])].sort(sortArea)[0])
const bArea = (fieldAreasIndex[bField.uuid] ||= [...(bField.areas || [])].sort(sortArea)[0])
return sortArea(aArea, bArea)
})
const insertBeforeAttachmentUuids = attachmentUuids.slice(this.template.schema.indexOf(item) + 1)
let sortedFields = []
if (insertBeforeAttachmentUuids.length) {
const insertAfterField = fields.find((f) => {
if (f.areas?.length) {
return f.areas.find((a) => insertBeforeAttachmentUuids.includes(a.attachment_uuid))
} else {
return false
}
})
if (insertAfterField) {
fields.splice(fields.indexOf(insertAfterField), 0, ...itemFields)
sortedFields = fields
} else {
sortedFields = fields.concat(itemFields)
}
} else {
if (fields.length && itemFields.length && this.template.fields.indexOf(fields[0]) > this.template.fields.indexOf(itemFields[0])) {
sortedFields = itemFields.concat(fields)
} else {
sortedFields = fields.concat(itemFields)
}
}
if (this.template.fields.length === sortedFields.length) {
this.template.fields = sortedFields
this.save()
}
},
2024-09-03 00:37:31 +03:00
closeDropdown () {
document.activeElement.blur()
},
2024-01-04 01:05:36 +02:00
t (key) {
2024-09-23 15:22:07 +03:00
return this.i18n[key] || i18n[this.language]?.[key] || i18n.en[key] || key
2024-01-04 01:05:36 +02:00
},
2024-06-22 13:16:22 +03:00
removePendingFields () {
this.template.fields = this.template.fields.filter((f) => {
return this.template.schema.find((item) => item.attachment_uuid === f.attachment_uuid && item.pending_fields)
})
this.save()
},
2024-03-27 13:47:17 +02:00
addField (type, area = null) {
const field = {
name: '',
uuid: v4(),
required: type !== 'checkbox',
areas: area ? [area] : [],
submitter_uuid: this.selectedSubmitter.uuid,
type
}
if (['select', 'multiple', 'radio'].includes(type)) {
2025-08-23 21:33:46 +03:00
field.options = [{ value: '', uuid: v4() }, { value: '', uuid: v4() }]
2024-03-27 13:47:17 +02:00
}
if (type === 'stamp') {
field.readonly = true
}
2025-06-19 19:35:04 +03:00
if (type === 'datenow') {
field.type = 'date'
field.readonly = true
field.default_value = '{{date}}'
}
2024-03-27 13:47:17 +02:00
if (type === 'date') {
field.preferences = {
2024-08-05 19:38:47 +03:00
format: this.defaultDateFormat
2024-03-27 13:47:17 +02:00
}
}
2025-07-17 10:54:44 +03:00
if (type === 'signature' && [true, false].includes(this.withSignatureId)) {
field.preferences ||= {}
field.preferences.with_signature_id = this.withSignatureId
}
2024-03-27 13:47:17 +02:00
this.template.fields.push(field)
this.save()
},
startFieldDraw ({ name, type }) {
const existingField = this.template.fields?.find((f) => f.submitter_uuid === this.selectedSubmitter.uuid && name && name === f.name)
2023-10-06 03:21:14 +03:00
if (existingField) {
this.drawField = existingField
} else {
const field = {
name: name || '',
uuid: v4(),
required: type !== 'checkbox',
areas: [],
submitter_uuid: this.selectedSubmitter.uuid,
type
}
2023-10-06 03:21:14 +03:00
if (['select', 'multiple', 'radio'].includes(type)) {
2025-08-23 21:33:46 +03:00
field.options = [{ value: '', uuid: v4() }, { value: '', uuid: v4() }]
}
if (type === 'stamp') {
field.readonly = true
}
2023-12-22 13:33:53 +02:00
if (type === 'date') {
field.preferences = {
2024-08-05 19:38:47 +03:00
format: this.defaultDateFormat
}
2023-12-17 23:32:05 +02:00
}
this.drawField = field
2023-12-17 23:32:05 +02:00
}
2023-11-14 01:30:00 +02:00
this.drawOption = null
2023-10-06 03:21:14 +03:00
},
2023-09-28 01:48:40 +03:00
undo () {
if (this.undoStack.length > 1) {
this.undoStack.pop()
const stringData = this.undoStack[this.undoStack.length - 1]
const currentStringData = JSON.stringify(this.template)
if (stringData && stringData !== currentStringData) {
this.redoStack.push(currentStringData)
Object.assign(this.template, JSON.parse(stringData))
this.save()
}
}
},
redo () {
const stringData = this.redoStack.pop()
this.lastRedoData = stringData
const currentStringData = JSON.stringify(this.template)
if (stringData && stringData !== currentStringData) {
if (this.undoStack[this.undoStack.length - 1] !== currentStringData) {
this.undoStack.push(currentStringData)
}
Object.assign(this.template, JSON.parse(stringData))
this.save()
}
},
2023-08-19 21:05:54 +03:00
onWindowResize (e) {
const breakpointLg = 1024
2023-09-14 01:43:08 +03:00
this.isBreakpointLg = this.$el.getRootNode().querySelector('div[data-v-app]').offsetWidth < breakpointLg
2023-08-19 21:05:54 +03:00
},
2023-05-31 00:50:43 +03:00
setDocumentRefs (el) {
if (el) {
this.documentRefs.push(el)
}
},
scrollIntoDocument (item) {
const ref = this.documentRefs.find((e) => e.document.uuid === item.attachment_uuid)
ref.$el.scrollIntoView({ behavior: 'smooth', block: 'start' })
},
2024-01-06 21:16:27 +02:00
clearDrawField () {
this.drawField = null
this.drawOption = null
2024-03-27 13:47:17 +02:00
this.showDrawField = false
if (!this.withSelectedFieldType) {
this.drawFieldType = ''
}
2024-01-06 21:16:27 +02:00
},
2023-06-07 01:16:43 +03:00
onKeyUp (e) {
2023-05-21 17:59:36 +03:00
if (e.code === 'Escape') {
2024-01-06 21:16:27 +02:00
this.clearDrawField()
2023-06-09 01:43:18 +03:00
this.selectedAreaRef.value = null
2023-06-07 01:16:43 +03:00
}
2023-11-29 21:59:14 +02:00
if (this.editable && ['Backspace', 'Delete'].includes(e.key) && this.selectedAreaRef.value && document.activeElement === document.body) {
2023-06-09 01:43:18 +03:00
this.removeArea(this.selectedAreaRef.value)
2023-06-07 01:16:43 +03:00
2023-06-09 01:43:18 +03:00
this.selectedAreaRef.value = null
2023-05-21 17:59:36 +03:00
}
},
2023-09-28 01:48:40 +03:00
onKeyDown (event) {
if ((event.metaKey && event.shiftKey && event.key === 'z') || (event.ctrlKey && event.key === 'Z')) {
event.stopImmediatePropagation()
event.preventDefault()
this.redo()
} else if ((event.ctrlKey || event.metaKey) && event.key === 'z') {
event.stopImmediatePropagation()
event.preventDefault()
this.undo()
2024-06-27 15:14:32 +03:00
} else if ((event.ctrlKey || event.metaKey) && event.key === 'c' && document.activeElement === document.body) {
event.preventDefault()
this.copiedArea = this.selectedAreaRef?.value
} else if ((event.ctrlKey || event.metaKey) && event.key === 'v' && this.copiedArea && document.activeElement === document.body) {
event.preventDefault()
this.pasteField()
2024-07-02 10:25:05 +03:00
} else if (this.selectedAreaRef.value && ['ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown'].includes(event.key) && document.activeElement === document.body) {
event.preventDefault()
this.handleAreaArrows(event)
}
},
handleAreaArrows (event) {
if (!this.editable) {
return
}
const area = this.selectedAreaRef.value
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
const page = documentRef.pageRefs[area.page].$refs.image
const rect = page.getBoundingClientRect()
const diff = (event.shiftKey ? 5.0 : 1.0)
if (event.key === 'ArrowRight' && event.altKey) {
area.w = Math.min(Math.max(area.w + diff / rect.width, 0), 1 - area.x)
} else if (event.key === 'ArrowLeft' && event.altKey) {
area.w = Math.min(Math.max(area.w - diff / rect.width, 0), 1 - area.x)
} else if (event.key === 'ArrowUp' && event.altKey) {
area.h = Math.min(Math.max(area.h - diff / rect.height, 0), 1 - area.y)
} else if (event.key === 'ArrowDown' && event.altKey) {
area.h = Math.min(Math.max(area.h + diff / rect.height, 0), 1 - area.y)
} else if (event.key === 'ArrowRight') {
area.x = Math.min(Math.max(area.x + diff / rect.width, 0), 1 - area.w)
} else if (event.key === 'ArrowLeft') {
area.x = Math.min(Math.max(area.x - diff / rect.width, 0), 1 - area.w)
} else if (event.key === 'ArrowUp') {
area.y = Math.min(Math.max(area.y - diff / rect.height, 0), 1 - area.h)
} else if (event.key === 'ArrowDown') {
area.y = Math.min(Math.max(area.y + diff / rect.height, 0), 1 - area.h)
2023-09-28 01:48:40 +03:00
}
2024-07-02 10:25:05 +03:00
this.debouncedSave()
},
debouncedSave () {
clearTimeout(this._saveTimeout)
this._saveTimeout = setTimeout(() => {
this.save()
}, 700)
2023-09-28 01:48:40 +03:00
},
2023-06-09 01:43:18 +03:00
removeArea (area) {
2023-06-07 01:16:43 +03:00
const field = this.template.fields.find((f) => f.areas?.includes(area))
2023-06-04 01:08:45 +03:00
field.areas.splice(field.areas.indexOf(area), 1)
if (!field.areas.length) {
this.template.fields.splice(this.template.fields.indexOf(field), 1)
2024-12-07 01:35:59 +02:00
this.template.fields.forEach((f) => {
(f.conditions || []).forEach((c) => {
if (c.field_uuid === field.uuid) {
f.conditions.splice(f.conditions.indexOf(c), 1)
}
})
})
this.template.schema.forEach((item) => {
(item.conditions || []).forEach((c) => {
if (c.field_uuid === field.uuid) {
item.conditions.splice(item.conditions.indexOf(c), 1)
}
})
})
2023-06-04 01:08:45 +03:00
}
2023-06-24 16:17:07 +03:00
this.save()
2023-06-04 01:08:45 +03:00
},
2024-06-27 15:14:32 +03:00
pasteField () {
const field = this.template.fields.find((f) => f.areas?.includes(this.copiedArea))
const currentArea = this.selectedAreaRef?.value || this.copiedArea
if (field && currentArea) {
const area = {
...JSON.parse(JSON.stringify(this.copiedArea)),
attachment_uuid: currentArea.attachment_uuid,
page: currentArea.page,
x: currentArea.x,
y: currentArea.y + currentArea.h * 1.3
}
if (['radio', 'multiple'].includes(field.type)) {
this.copiedArea.option_uuid ||= field.options[0].uuid
area.option_uuid = v4()
field.options.push({ uuid: area.option_uuid })
field.areas.push(area)
} else {
this.template.fields.push({
...JSON.parse(JSON.stringify(field)),
uuid: v4(),
areas: [area]
})
}
this.selectedAreaRef.value = area
this.save()
}
},
2023-09-28 01:48:40 +03:00
pushUndo () {
const stringData = JSON.stringify(this.template)
if (this.undoStack[this.undoStack.length - 1] !== stringData) {
this.undoStack.push(stringData)
if (this.lastRedoData !== stringData) {
this.redoStack = []
}
}
},
2024-03-27 13:47:17 +02:00
setDefaultAreaSize (area, type) {
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
const pageMask = documentRef.pageRefs[area.page].$refs.mask
if (type === 'checkbox') {
area.w = pageMask.clientWidth / 30 / pageMask.clientWidth
area.h = (pageMask.clientWidth / 30 / pageMask.clientWidth) * (pageMask.clientWidth / pageMask.clientHeight)
} else if (type === 'image') {
area.w = pageMask.clientWidth / 5 / pageMask.clientWidth
area.h = (pageMask.clientWidth / 5 / pageMask.clientWidth) * (pageMask.clientWidth / pageMask.clientHeight)
2024-12-01 00:10:07 +02:00
} else if (type === 'signature' || type === 'stamp' || type === 'verification') {
2024-03-27 13:47:17 +02:00
area.w = pageMask.clientWidth / 5 / pageMask.clientWidth
area.h = (pageMask.clientWidth / 5 / pageMask.clientWidth) * (pageMask.clientWidth / pageMask.clientHeight) / 2
} else if (type === 'initials') {
area.w = pageMask.clientWidth / 10 / pageMask.clientWidth
area.h = (pageMask.clientWidth / 35 / pageMask.clientWidth)
} else {
area.w = pageMask.clientWidth / 5 / pageMask.clientWidth
area.h = (pageMask.clientWidth / 35 / pageMask.clientWidth)
}
},
2025-05-21 22:05:33 +03:00
onDraw ({ area, isTooSmall }) {
2023-06-03 00:53:46 +03:00
if (this.drawField) {
2023-11-14 01:30:00 +02:00
if (this.drawOption) {
const areaWithoutOption = this.drawField.areas?.find((a) => !a.option_uuid)
if (areaWithoutOption && !this.drawField.areas.find((a) => a.option_uuid === this.drawField.options[0].uuid)) {
areaWithoutOption.option_uuid = this.drawField.options[0].uuid
}
area.option_uuid = this.drawOption.uuid
}
2023-12-23 11:52:46 +02:00
if (area.w === 0 || area.h === 0) {
2023-12-27 16:44:20 +02:00
const previousArea = this.drawField.areas?.[this.drawField.areas.length - 1]
2023-12-23 11:52:46 +02:00
if (this.selectedField?.type === this.drawField.type) {
area.w = this.selectedAreaRef.value.w
area.h = this.selectedAreaRef.value.h
2023-12-27 16:44:20 +02:00
} else if (previousArea) {
area.w = previousArea.w
area.h = previousArea.h
2023-12-23 11:52:46 +02:00
} else {
2024-03-27 13:47:17 +02:00
this.setDefaultAreaSize(area, this.drawOption ? 'checkbox' : this.drawField?.type)
2023-12-23 11:52:46 +02:00
}
area.x -= area.w / 2
area.y -= area.h / 2
}
2023-06-03 00:53:46 +03:00
this.drawField.areas ||= []
2024-01-06 16:19:40 +02:00
const insertBeforeAreaIndex = this.drawField.areas.findIndex((a) => {
return a.attachment_uuid === area.attachment_uuid && a.page > area.page
})
if (insertBeforeAreaIndex !== -1) {
this.drawField.areas.splice(insertBeforeAreaIndex, 0, area)
} else {
this.drawField.areas.push(area)
}
2023-05-21 17:59:36 +03:00
2023-10-06 03:21:14 +03:00
if (this.template.fields.indexOf(this.drawField) === -1) {
this.template.fields.push(this.drawField)
}
2023-06-03 00:53:46 +03:00
this.drawField = null
2023-11-14 01:30:00 +02:00
this.drawOption = null
2023-09-20 19:19:21 +03:00
this.selectedAreaRef.value = area
this.save()
2023-06-03 00:53:46 +03:00
} else {
2023-06-04 01:08:45 +03:00
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
const pageMask = documentRef.pageRefs[area.page].$refs.mask
2024-01-10 23:18:30 +02:00
let type = (pageMask.clientWidth * area.w) < 35 ? 'checkbox' : 'text'
2024-03-27 13:47:17 +02:00
if (this.drawFieldType) {
type = this.drawFieldType
} else if (this.defaultDrawFieldType && this.defaultDrawFieldType !== 'text') {
2024-02-20 22:15:21 +02:00
type = this.defaultDrawFieldType
} else if (this.fieldTypes.length !== 0 && !this.fieldTypes.includes(type)) {
2024-01-10 23:18:30 +02:00
type = this.fieldTypes[0]
}
2023-06-04 01:08:45 +03:00
2024-05-05 18:18:07 +03:00
if (type === 'checkbox' && !this.drawFieldType && (this.template.fields[this.template.fields.length - 1]?.type === 'checkbox' || area.w)) {
2023-06-08 00:20:42 +03:00
const previousField = [...this.template.fields].reverse().find((f) => f.type === type)
const previousArea = previousField?.areas?.[previousField.areas.length - 1]
2023-09-20 19:19:21 +03:00
if (previousArea || area.w) {
const areaW = previousArea?.w || (30 / pageMask.clientWidth)
const areaH = previousArea?.h || (30 / pageMask.clientHeight)
2023-06-08 00:20:42 +03:00
2023-09-20 19:19:21 +03:00
if ((pageMask.clientWidth * area.w) < 5) {
area.x = area.x - (areaW / 2)
area.y = area.y - (areaH / 2)
}
2023-06-08 00:20:42 +03:00
2023-09-20 19:19:21 +03:00
area.w = areaW
area.h = areaH
}
2023-06-04 01:08:45 +03:00
}
2024-03-27 13:47:17 +02:00
if (this.drawFieldType && (area.w === 0 || area.h === 0)) {
if (this.selectedField?.type === this.drawFieldType) {
area.w = this.selectedAreaRef.value.w
area.h = this.selectedAreaRef.value.h
} else {
this.setDefaultAreaSize(area, this.drawFieldType)
2023-09-20 19:19:21 +03:00
}
2023-06-03 00:53:46 +03:00
2024-03-27 13:47:17 +02:00
area.x -= area.w / 2
area.y -= area.h / 2
}
2023-06-07 01:16:43 +03:00
2025-05-21 22:05:33 +03:00
if (area.w && (type !== 'checkbox' || this.drawFieldType || !isTooSmall)) {
2024-03-27 13:47:17 +02:00
this.addField(type, area)
2023-06-24 16:17:07 +03:00
2024-03-27 13:47:17 +02:00
this.selectedAreaRef.value = area
2023-09-20 19:19:21 +03:00
}
}
2023-05-21 17:59:36 +03:00
},
2023-05-23 00:03:26 +03:00
onDropfield (area) {
2025-02-17 19:47:46 +02:00
if (this.$refs.dragPlaceholder) {
this.$refs.dragPlaceholder.dragPlaceholder = null
}
2025-03-04 19:46:55 +02:00
if (!this.editable) {
return
}
2024-02-27 01:41:57 +02:00
const field = this.fieldsDragFieldRef.value || {
2023-06-04 01:08:45 +03:00
name: '',
uuid: v4(),
2023-06-07 01:16:43 +03:00
submitter_uuid: this.selectedSubmitter.uuid,
2023-11-11 23:36:03 +02:00
required: this.dragField.type !== 'checkbox',
...this.dragField
2023-06-04 01:08:45 +03:00
}
2025-02-17 19:47:46 +02:00
if (!field.type) {
field.type = 'text'
}
2024-02-27 01:41:57 +02:00
if (!this.fieldsDragFieldRef.value) {
if (['select', 'multiple', 'radio'].includes(field.type)) {
2025-01-16 00:40:22 +02:00
if (this.dragField?.options?.length) {
field.options = this.dragField.options.map(option => ({ value: option, uuid: v4() }))
} else {
2025-08-23 21:33:46 +03:00
field.options = [{ value: '', uuid: v4() }, { value: '', uuid: v4() }]
2025-01-16 00:40:22 +02:00
}
2024-02-27 01:41:57 +02:00
}
2023-06-15 01:05:04 +03:00
2025-06-19 19:35:04 +03:00
if (field.type === 'datenow') {
field.type = 'date'
field.readonly = true
field.default_value = '{{date}}'
}
2024-07-11 21:40:15 +03:00
if (['stamp', 'heading'].includes(field.type)) {
2024-02-27 01:41:57 +02:00
field.readonly = true
}
2023-12-22 13:33:53 +02:00
2024-02-27 01:41:57 +02:00
if (field.type === 'date') {
2025-03-03 11:24:38 +02:00
field.preferences ||= {}
field.preferences.format ||= this.defaultDateFormat
2023-12-17 23:32:05 +02:00
}
2025-07-17 10:54:44 +03:00
if (field.type === 'signature' && [true, false].includes(this.withSignatureId)) {
field.preferences ||= {}
field.preferences.with_signature_id = this.withSignatureId
}
2023-12-17 23:32:05 +02:00
}
2023-06-04 01:08:45 +03:00
const fieldArea = {
x: (area.x - 6) / area.maskW,
y: area.y / area.maskH,
page: area.page,
attachment_uuid: area.attachment_uuid
}
2025-02-17 19:47:46 +02:00
this.assignDropAreaSize(fieldArea, field, area)
if (field.width) {
delete field.width
}
if (field.height) {
delete field.height
}
field.areas ||= []
field.areas.push(fieldArea)
this.selectedAreaRef.value = fieldArea
if (this.template.fields.indexOf(field) === -1) {
this.template.fields.push(field)
}
this.save()
document.activeElement?.blur()
if (field.type === 'heading') {
this.$nextTick(() => {
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
const areaRef = documentRef.pageRefs[area.page].areaRefs.find((ref) => ref.area === this.selectedAreaRef.value)
areaRef.isHeadingSelected = true
areaRef.focusValueInput()
})
}
},
assignDropAreaSize (fieldArea, field, area) {
const fieldType = field.type || 'text'
const previousField = [...this.template.fields].reverse().find((f) => f.type === fieldType)
2023-06-04 01:08:45 +03:00
let baseArea
2025-02-17 19:47:46 +02:00
if (this.selectedField?.type === fieldType) {
2023-06-09 01:43:18 +03:00
baseArea = this.selectedAreaRef.value
2023-07-26 23:17:24 +03:00
} else if (previousField?.areas?.length) {
2023-06-04 01:08:45 +03:00
baseArea = previousField.areas[previousField.areas.length - 1]
} else {
2025-02-17 19:47:46 +02:00
if (['checkbox'].includes(fieldType)) {
2023-06-04 01:08:45 +03:00
baseArea = {
w: area.maskW / 30 / area.maskW,
h: area.maskW / 30 / area.maskW * (area.maskW / area.maskH)
}
2025-02-17 19:47:46 +02:00
} else if (fieldType === 'image') {
2023-06-04 01:08:45 +03:00
baseArea = {
w: area.maskW / 5 / area.maskW,
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH)
}
2025-02-17 19:47:46 +02:00
} else if (fieldType === 'signature' || fieldType === 'stamp' || fieldType === 'verification') {
2023-06-20 00:24:39 +03:00
baseArea = {
w: area.maskW / 5 / area.maskW,
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH) / 2
}
2025-02-17 19:47:46 +02:00
} else if (fieldType === 'initials') {
2023-09-20 00:13:14 +03:00
baseArea = {
w: area.maskW / 10 / area.maskW,
h: area.maskW / 35 / area.maskW
}
2023-06-04 01:08:45 +03:00
} else {
baseArea = {
w: area.maskW / 5 / area.maskW,
h: area.maskW / 35 / area.maskW
}
}
}
fieldArea.w = baseArea.w
fieldArea.h = baseArea.h
2025-02-17 19:47:46 +02:00
if (fieldType === 'cells') {
2023-06-27 00:57:29 +03:00
fieldArea.cell_w = baseArea.cell_w || (baseArea.w / 5)
}
2025-02-17 19:47:46 +02:00
if (field.areas?.length) {
const lastArea = field.areas[field.areas.length - 1]
2024-02-27 01:41:57 +02:00
2025-02-17 19:47:46 +02:00
if (lastArea) {
fieldArea.w = lastArea.w
fieldArea.h = lastArea.h
}
2024-02-27 01:41:57 +02:00
}
2024-12-11 19:32:29 +02:00
if (field.width) {
fieldArea.w = field.width / area.maskW
}
if (field.height) {
fieldArea.h = field.height / area.maskH
2024-07-11 21:40:15 +03:00
}
2025-04-05 12:06:29 +03:00
fieldArea.y = fieldArea.y - fieldArea.h / 2
2023-05-23 00:03:26 +03:00
},
2024-07-09 23:47:46 +03:00
addBlankPage () {
this.isLoadingBlankPage = true
const canvas = document.createElement('canvas')
canvas.width = 816
canvas.height = 1056
const ctx = canvas.getContext('2d')
ctx.fillStyle = 'white'
ctx.fillRect(0, 0, canvas.width, canvas.height)
canvas.toBlob((blob) => {
const file = new File([blob], `Page ${this.template.schema.length + 1}.png`, { type: blob.type, lastModified: Date.now() })
const formData = new FormData()
formData.append('files[]', file)
this.baseFetch(`/templates/${this.template.id}/documents`, {
method: 'POST',
body: formData
}).then(async (resp) => {
this.updateFromUpload(await resp.json())
}).finally(() => {
this.isLoadingBlankPage = false
})
}, 'image/png')
},
2025-04-25 21:19:28 +03:00
onUploadFailed (error) {
if (error) alert(error)
},
2024-04-06 21:44:44 +03:00
updateFromUpload (data) {
this.template.schema.push(...data.schema)
this.template.documents.push(...data.documents)
if (data.fields) {
this.template.fields = data.fields
}
if (data.submitters) {
this.template.submitters = data.submitters
if (!this.template.submitters.find((s) => s.uuid === this.selectedSubmitter?.uuid)) {
this.selectedSubmitter = this.template.submitters[0]
}
}
2023-05-21 17:59:36 +03:00
2023-05-31 00:50:43 +03:00
this.$nextTick(() => {
2024-11-12 17:11:09 +02:00
if (this.$refs.previews) {
this.$refs.previews.scrollTop = this.$refs.previews.scrollHeight
}
2023-05-31 00:50:43 +03:00
2024-04-06 21:44:44 +03:00
this.scrollIntoDocument(data.schema[0])
2023-05-31 00:50:43 +03:00
})
2023-12-23 00:52:06 +02:00
if (this.template.name === 'New Document') {
this.template.name = this.template.schema[0].name
}
2023-12-19 19:27:07 +02:00
if (this.onUpload) {
this.onUpload(this.template)
}
2023-05-31 00:50:43 +03:00
this.save()
2024-06-22 13:16:22 +03:00
data.documents.forEach((attachment) => {
if (attachment.metadata?.pdf?.fields?.length) {
this.pendingFieldAttachmentUuids.push(attachment.uuid)
attachment.metadata.pdf.fields.forEach((field) => {
field.submitter_uuid = this.selectedSubmitter.uuid
this.template.fields.push(field)
})
}
})
2023-05-31 00:50:43 +03:00
},
updateName (value) {
2023-05-31 23:19:20 +03:00
this.template.name = value
2023-05-31 00:50:43 +03:00
this.save()
},
onDocumentRemove (item) {
2024-10-07 14:58:28 +03:00
if (window.confirm(this.t('are_you_sure_'))) {
2023-05-31 23:19:20 +03:00
this.template.schema.splice(this.template.schema.indexOf(item), 1)
2023-05-31 00:50:43 +03:00
2025-01-17 17:06:37 +02:00
const removedFieldUuids = []
2024-03-27 00:11:10 +02:00
2025-01-17 17:06:37 +02:00
this.template.fields.forEach((field) => {
[...(field.areas || [])].forEach((area) => {
if (area.attachment_uuid === item.attachment_uuid) {
field.areas.splice(field.areas.indexOf(area), 1)
2024-03-27 00:11:10 +02:00
2025-01-17 17:06:37 +02:00
removedFieldUuids.push(field.uuid)
}
})
2023-07-26 23:17:24 +03:00
})
2025-01-17 17:06:37 +02:00
this.template.fields =
this.template.fields.filter((f) => !removedFieldUuids.includes(f.uuid) || f.areas?.length)
2024-03-27 00:11:10 +02:00
2025-01-17 17:06:37 +02:00
this.save()
}
2023-05-21 17:59:36 +03:00
},
2024-04-06 21:44:44 +03:00
onDocumentReplace (data) {
const { replaceSchemaItem, schema, documents } = data
2025-01-17 17:11:10 +02:00
this.template.schema.splice(this.template.schema.indexOf(replaceSchemaItem), 1, { ...replaceSchemaItem, ...schema[0] })
2023-07-26 20:35:53 +03:00
this.template.documents.push(...documents)
2024-04-06 21:44:44 +03:00
if (data.fields) {
this.template.fields = data.fields
const removedFieldUuids = []
this.template.fields.forEach((field) => {
[...(field.areas || [])].forEach((area) => {
if (area.attachment_uuid === replaceSchemaItem.attachment_uuid) {
field.areas.splice(field.areas.indexOf(area), 1)
removedFieldUuids.push(field.uuid)
}
})
})
this.template.fields =
this.template.fields.filter((f) => !removedFieldUuids.includes(f.uuid) || f.areas?.length)
}
if (data.submitters) {
this.template.submitters = data.submitters
if (!this.template.submitters.find((s) => s.uuid === this.selectedSubmitter?.uuid)) {
this.selectedSubmitter = this.template.submitters[0]
}
}
2023-07-26 20:35:53 +03:00
this.template.fields.forEach((field) => {
(field.areas || []).forEach((area) => {
2023-07-26 20:35:53 +03:00
if (area.attachment_uuid === replaceSchemaItem.attachment_uuid) {
area.attachment_uuid = schema[0].attachment_uuid
}
})
})
2023-12-19 19:27:07 +02:00
if (this.onUpload) {
this.onUpload(this.template)
}
2023-07-26 20:35:53 +03:00
this.save()
},
2025-04-25 21:19:28 +03:00
onDocumentsReplace (data) {
2025-05-09 00:01:50 +03:00
data.schema.forEach((schemaItem, index) => {
2025-04-25 21:19:28 +03:00
const existingSchemaItem = this.template.schema[index]
if (this.template.schema[index]) {
this.onDocumentReplace({
replaceSchemaItem: existingSchemaItem,
schema: [schemaItem],
documents: [data.documents.find((doc) => doc.uuid === schemaItem.attachment_uuid)]
})
} else {
this.updateFromUpload({
schema: [schemaItem],
documents: [data.documents.find((doc) => doc.uuid === schemaItem.attachment_uuid)],
fields: data.fields,
submitters: data.submitters
})
}
})
},
onDocumentsReplaceAndTemplateClone (template) {
window.Turbo.visit(`/templates/${template.id}/edit`)
},
2023-05-31 00:50:43 +03:00
moveDocument (item, direction) {
2023-05-31 23:19:20 +03:00
const currentIndex = this.template.schema.indexOf(item)
2023-05-31 00:50:43 +03:00
2023-05-31 23:19:20 +03:00
this.template.schema.splice(currentIndex, 1)
2023-05-31 00:50:43 +03:00
2023-05-31 23:19:20 +03:00
if (currentIndex + direction > this.template.schema.length) {
this.template.schema.unshift(item)
2023-05-31 00:50:43 +03:00
} else if (currentIndex + direction < 0) {
2023-05-31 23:19:20 +03:00
this.template.schema.push(item)
2023-05-31 00:50:43 +03:00
} else {
2023-05-31 23:19:20 +03:00
this.template.schema.splice(currentIndex + direction, 0, item)
2023-05-31 00:50:43 +03:00
}
this.save()
},
2024-04-15 16:34:44 +03:00
maybeShowErrorTemplateAlert (e) {
if (!this.isAllRequiredFieldsAdded) {
e.preventDefault()
2024-05-15 11:42:51 +03:00
const fields = this.defaultRequiredFields?.filter((f) => {
return !this.template.fields?.some((field) => field.name === f.name)
})
if (fields?.length) {
return alert(this.t('add_all_required_fields_to_continue') + ': ' + fields.map((f) => f.name).join(', '))
}
2024-04-15 16:34:44 +03:00
}
2023-10-28 23:07:18 +03:00
if (!this.template.fields.length) {
e.preventDefault()
2024-09-23 15:22:07 +03:00
alert(this.t('please_draw_fields_to_prepare_the_document'))
2024-06-08 10:01:14 +03:00
} else {
const submitterWithoutFields =
this.template.submitters.find((submitter) => !this.template.fields.some((f) => f.submitter_uuid === submitter.uuid))
if (submitterWithoutFields) {
e.preventDefault()
2024-09-23 15:22:07 +03:00
alert(this.t('please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed').replaceAll('{submitter_name}', submitterWithoutFields.name))
2024-06-08 10:01:14 +03:00
}
2023-10-28 23:07:18 +03:00
}
},
2023-05-31 00:50:43 +03:00
onSaveClick () {
2024-04-15 16:34:44 +03:00
if (!this.isAllRequiredFieldsAdded) {
2024-05-15 11:42:51 +03:00
const fields = this.defaultRequiredFields?.filter((f) => {
return !this.template.fields?.some((field) => field.name === f.name)
})
if (fields?.length) {
return alert(this.t('add_all_required_fields_to_continue') + ': ' + fields.map((f) => f.name).join(', '))
}
2024-04-15 16:34:44 +03:00
}
2024-06-08 10:01:14 +03:00
if (!this.template.fields.length) {
2024-09-23 15:22:07 +03:00
alert(this.t('please_draw_fields_to_prepare_the_document'))
2024-06-08 10:01:14 +03:00
} else {
const submitterWithoutFields =
this.template.submitters.find((submitter) => !this.template.fields.some((f) => f.submitter_uuid === submitter.uuid))
if (submitterWithoutFields) {
2024-09-23 15:22:07 +03:00
alert(this.t('please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed').replaceAll('{submitter_name}', submitterWithoutFields.name))
2024-06-08 10:01:14 +03:00
} else {
this.isSaving = true
this.save().then(() => {
window.Turbo.visit(`/templates/${this.template.id}`)
}).finally(() => {
this.isSaving = false
})
}
2023-10-28 23:07:18 +03:00
}
2023-05-31 00:50:43 +03:00
},
2023-06-03 00:53:46 +03:00
scrollToArea (area) {
const documentRef = this.documentRefs.find((a) => a.document.uuid === area.attachment_uuid)
documentRef.scrollToArea(area)
2023-06-07 01:16:43 +03:00
2023-06-09 01:43:18 +03:00
this.selectedAreaRef.value = area
2023-06-03 00:53:46 +03:00
},
2023-08-19 21:05:54 +03:00
baseFetch (path, options = {}) {
return fetch(this.baseUrl + path, {
...options,
2024-03-23 23:16:40 +02:00
headers: {
'X-CSRF-Token': this.authenticityToken,
...this.fetchOptions.headers,
...options.headers
}
2023-08-19 21:05:54 +03:00
})
},
2024-01-03 21:44:04 +02:00
save ({ force } = { force: false }) {
2024-06-22 13:16:22 +03:00
this.pendingFieldAttachmentUuids = []
2024-04-15 16:34:44 +03:00
if (this.onChange) {
this.onChange(this.template)
}
2024-01-03 21:44:04 +02:00
if (!this.autosave && !force) {
return Promise.resolve({})
}
2023-12-31 17:23:27 +02:00
this.$nextTick(() => {
if (this.$el.closest('template-builder')) {
this.$el.closest('template-builder').dataset.template = JSON.stringify(this.template)
}
})
2023-06-04 01:08:45 +03:00
2023-09-28 01:48:40 +03:00
this.pushUndo()
2024-03-23 23:16:40 +02:00
return this.baseFetch(`/templates/${this.template.id}`, {
2023-05-21 17:59:36 +03:00
method: 'PUT',
2023-06-24 16:17:07 +03:00
body: JSON.stringify({
template: {
name: this.template.name,
schema: this.template.schema,
submitters: this.template.submitters,
fields: this.template.fields
}
}),
2023-05-21 17:59:36 +03:00
headers: { 'Content-Type': 'application/json' }
2024-01-10 17:54:35 +02:00
}).then(() => {
if (this.onSave) {
this.onSave(this.template)
}
2023-05-21 17:59:36 +03:00
})
}
}
}
</script>