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

486 lines
15 KiB
Vue
Raw Normal View History

2023-05-21 17:59:36 +03:00
<template>
2024-07-02 20:27:25 +03:00
<div :class="withStickySubmitters ? 'sticky top-0 z-[1]' : ''">
2023-06-16 00:04:05 +03:00
<FieldSubmitter
:model-value="selectedSubmitter.uuid"
2025-03-18 17:34:11 +02:00
class="roles-dropdown w-full rounded-lg roles-dropdown"
2024-04-23 01:57:45 +03:00
:style="withStickySubmitters ? { backgroundColor } : {}"
2023-06-16 00:04:05 +03:00
:submitters="submitters"
2025-01-20 21:01:13 +02:00
:menu-style="{ overflow: 'auto', display: 'flex', flexDirection: 'row', maxHeight: 'calc(100vh - 120px)', backgroundColor: ['', null, 'transparent'].includes(backgroundColor) ? 'white' : backgroundColor }"
2023-12-17 16:10:17 +02:00
:editable="editable && !defaultSubmitters.length"
2023-06-24 16:17:07 +03:00
@new-submitter="save"
2023-06-16 00:04:05 +03:00
@remove="removeSubmitter"
2023-06-24 16:17:07 +03:00
@name-change="save"
2023-06-16 00:04:05 +03:00
@update:model-value="$emit('change-submitter', submitters.find((s) => s.uuid === $event))"
/>
</div>
2023-11-04 14:06:59 +02:00
<div
2023-12-31 22:39:11 +02:00
ref="fields"
2024-03-07 18:59:19 +02:00
class="fields mb-1 mt-2"
2023-11-04 14:06:59 +02:00
@dragover.prevent="onFieldDragover"
2023-12-31 22:39:11 +02:00
@drop="reorderFields"
2023-11-04 14:06:59 +02:00
>
2023-05-21 17:59:36 +03:00
<Field
2023-06-07 01:16:43 +03:00
v-for="field in submitterFields"
2023-05-21 17:59:36 +03:00
:key="field.uuid"
2023-11-04 14:06:59 +02:00
:data-uuid="field.uuid"
2023-05-21 17:59:36 +03:00
:field="field"
2023-06-03 00:53:46 +03:00
:type-index="fields.filter((f) => f.type === field.type).indexOf(field)"
2025-02-17 19:47:46 +02:00
:editable="editable"
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-07 17:30:21 +02:00
:default-field="defaultFieldsIndex[field.name]"
2023-11-04 14:06:59 +02:00
:draggable="editable"
2025-02-17 19:47:46 +02:00
@dragstart="[fieldsDragFieldRef.value = field, removeDragOverlay($event), setDragPlaceholder($event)]"
@dragend="[fieldsDragFieldRef.value = null, $emit('set-drag-placeholder', null)]"
2023-06-07 01:16:43 +03:00
@remove="removeField"
2023-06-03 00:53:46 +03:00
@scroll-to="$emit('scroll-to-area', $event)"
2023-05-21 17:59:36 +03:00
@set-draw="$emit('set-draw', $event)"
/>
</div>
2024-04-29 15:42:11 +03:00
<div v-if="submitterDefaultFields.length && editable">
2023-11-11 23:36:03 +02:00
<hr class="mb-2">
2024-03-19 01:21:44 +02:00
<template v-if="isShowFieldSearch">
<input
v-model="defaultFieldsSearch"
:placeholder="t('search_field')"
class="input input-ghost input-xs px-0 text-base mb-2 !outline-0 !rounded bg-transparent w-full"
>
<hr class="mb-2">
</template>
<div
class="overflow-auto relative"
:style="{
maxHeight: isShowFieldSearch ? '210px' : '',
minHeight: isShowFieldSearch ? '210px' : ''
}"
2023-11-11 23:36:03 +02:00
>
<div
2024-03-19 01:21:44 +02:00
v-if="!filteredSubmitterDefaultFields.length && defaultFieldsSearch"
class="top-0 bottom-0 text-center absolute flex items-center justify-center w-full flex-col"
2023-11-11 23:36:03 +02:00
>
2024-03-19 01:21:44 +02:00
<div>
{{ t('field_not_found') }}
2023-11-11 23:36:03 +02:00
</div>
2024-03-19 01:21:44 +02:00
<a
href="#"
class="link"
@click.prevent="defaultFieldsSearch = ''"
>
{{ t('clear') }}
</a>
2023-11-11 23:36:03 +02:00
</div>
2024-03-19 01:21:44 +02:00
<template
v-for="field in filteredSubmitterDefaultFields"
:key="field.name"
>
<div
2024-04-23 01:57:45 +03:00
:style="{ backgroundColor }"
2024-03-19 01:21:44 +02:00
draggable="true"
2025-02-17 19:47:46 +02:00
class="border border-base-300 rounded relative group mb-2 default-field fields-list-item"
@dragstart="onDragstart($event, field)"
2024-03-19 01:21:44 +02:00
@dragend="$emit('drag-end')"
>
<div class="flex items-center justify-between relative cursor-grab">
<div class="flex items-center p-1 space-x-1">
<IconDrag />
<FieldType
:model-value="field.type || 'text'"
:editable="false"
:button-width="20"
/>
<span class="block pl-0.5">
2024-04-15 16:34:44 +03:00
{{ field.title || field.name }}
2024-03-19 01:21:44 +02:00
</span>
</div>
2024-04-15 16:34:44 +03:00
<span
v-if="defaultRequiredFields.includes(field)"
:data-tip="t('required')"
class="text-red-400 text-3xl pr-1.5 tooltip tooltip-left h-8"
>
*
</span>
2024-03-19 01:21:44 +02:00
</div>
</div>
</template>
</div>
2023-11-11 23:36:03 +02:00
</div>
2023-11-04 01:19:32 +02:00
<div
2024-01-05 18:40:45 +02:00
v-if="editable && !onlyDefinedFields"
2025-02-21 23:56:18 +02:00
id="field-types-grid"
2025-03-18 17:34:11 +02:00
class="grid grid-cols-3 gap-1 pb-2 fields-grid"
2023-11-04 01:19:32 +02:00
>
2023-09-05 01:00:21 +03:00
<template
2024-02-20 11:57:06 +02:00
v-for="(icon, type) in fieldIconsSorted"
2023-06-04 01:08:45 +03:00
:key="type"
2023-05-23 00:03:26 +03:00
>
2023-09-05 01:00:21 +03:00
<button
2025-05-08 12:22:18 +03:00
v-if="fieldTypes.includes(type) || ((withPhone || type != 'phone') && (withPayment || type != 'payment') && (withVerification || type != 'verification'))"
2025-02-21 23:56:18 +02:00
:id="`${type}_type_field_button`"
2023-09-05 01:00:21 +03:00
draggable="true"
2025-03-18 17:34:11 +02:00
class="field-type-button group flex items-center justify-center border border-dashed w-full rounded relative fields-grid-item"
2024-04-23 01:57:45 +03:00
:style="{ backgroundColor }"
2024-03-27 13:47:17 +02:00
:class="drawFieldType === type ? 'border-base-content/40' : 'border-base-300 hover:border-base-content/20'"
2025-02-17 19:47:46 +02:00
@dragstart="onDragstart($event, { type: type })"
2023-09-05 01:00:21 +03:00
@dragend="$emit('drag-end')"
2024-12-01 00:10:07 +02:00
@click="['file', 'payment', 'verification'].includes(type) ? $emit('add-field', type) : $emit('set-draw-type', type)"
2023-09-05 01:00:21 +03:00
>
2024-03-27 13:47:17 +02:00
<div
class="flex items-console transition-all cursor-grab h-full absolute left-0"
:class="drawFieldType === type ? 'bg-base-200/50' : 'group-hover:bg-base-200/50'"
>
<IconDrag class="my-auto" />
2023-09-05 01:00:21 +03:00
</div>
<div class="flex items-center flex-col px-2 py-2">
<component :is="icon" />
<span class="text-xs mt-1">
{{ fieldNames[type] }}
</span>
</div>
</button>
<div
2024-01-10 23:18:30 +02:00
v-else-if="type == 'phone' && (fieldTypes.length === 0 || fieldTypes.includes(type))"
2023-12-22 16:29:24 +02:00
class="tooltip tooltip-bottom flex"
2024-02-23 15:58:49 +02:00
:class="{'tooltip-bottom-end': withPayment, 'tooltip-bottom': !withPayment }"
2024-09-23 15:22:07 +03:00
:data-tip="t('unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification')"
2023-09-05 01:00:21 +03:00
>
<a
2024-10-31 14:25:37 +02:00
href="https://www.docuseal.com/pricing"
2023-09-05 01:00:21 +03:00
target="_blank"
2025-03-18 17:34:11 +02:00
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative fields-grid-item"
2024-04-23 01:57:45 +03:00
:style="{ backgroundColor }"
2023-09-05 01:00:21 +03:00
>
<div class="w-0 absolute left-0">
<IconLock
width="18"
height="18"
stroke-width="1.5"
/>
</div>
<div class="flex items-center flex-col px-2 py-2">
<component :is="icon" />
<span class="text-xs mt-1">
{{ fieldNames[type] }}
</span>
</div>
</a>
2023-06-03 00:53:46 +03:00
</div>
2024-12-01 00:10:07 +02:00
<div
v-else-if="withVerification === false && type == 'verification' && (fieldTypes.length === 0 || fieldTypes.includes(type))"
class="tooltip tooltip-bottom flex tooltip-bottom-start"
:data-tip="t('obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more')"
>
<a
2025-05-08 12:32:10 +03:00
href="https://www.docuseal.com/qualified-electronic-signature"
2024-12-01 00:10:07 +02:00
target="_blank"
2025-03-18 17:34:11 +02:00
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative fields-grid-item"
2024-12-01 00:10:07 +02:00
:style="{ backgroundColor }"
>
<div class="w-0 absolute left-0">
<IconLock
width="18"
height="18"
stroke-width="1.5"
/>
</div>
<div class="flex items-center flex-col px-2 py-2">
<component :is="icon" />
<span class="text-xs mt-1">
{{ fieldNames[type] }}
</span>
</div>
</a>
</div>
2023-09-05 01:00:21 +03:00
</template>
2023-06-03 00:53:46 +03:00
</div>
2023-07-08 17:07:07 +03:00
<div
2025-02-21 23:56:18 +02:00
v-if="fields.length < 4 && editable && withHelp && !showTourStartForm"
2023-07-08 17:07:07 +03:00
class="text-xs p-2 border border-base-200 rounded"
>
2023-07-12 23:56:42 +03:00
<ul class="list-disc list-outside ml-3">
2023-07-08 17:07:07 +03:00
<li>
2024-09-23 15:22:07 +03:00
{{ t('draw_a_text_field_on_the_page_with_a_mouse') }}
2023-07-08 17:07:07 +03:00
</li>
<li>
2024-09-23 15:22:07 +03:00
{{ t('drag_and_drop_any_other_field_type_on_the_page') }}
2023-07-08 17:07:07 +03:00
</li>
<li>
2024-09-23 15:22:07 +03:00
{{ t('click_on_the_field_type_above_to_start_drawing_it') }}
2023-07-08 17:07:07 +03:00
</li>
2023-07-12 23:56:42 +03:00
</ul>
2023-07-08 17:07:07 +03:00
</div>
2025-02-21 23:56:18 +02:00
<div
v-show="fields.length < 4 && editable && withHelp && showTourStartForm"
class="rounded py-2 px-4 w-full border border-dashed border-base-300"
>
<div class="text-center text-sm">
{{ t('start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document') }}
</div>
<div class="flex justify-center">
<label
for="start_tour_button"
class="btn btn-sm btn-warning w-40 mt-2"
@click="startTour"
>
{{ t('start_tour') }}
</label>
</div>
</div>
2023-05-21 17:59:36 +03:00
</template>
<script>
import Field from './field'
2023-06-04 01:08:45 +03:00
import FieldType from './field_type'
2023-06-07 01:16:43 +03:00
import FieldSubmitter from './field_submitter'
2024-03-27 13:47:17 +02:00
import { IconLock, IconCirclePlus } from '@tabler/icons-vue'
2023-11-11 23:36:03 +02:00
import IconDrag from './icon_drag'
2023-05-21 17:59:36 +03:00
export default {
2023-05-31 23:19:20 +03:00
name: 'TemplateFields',
2023-05-21 17:59:36 +03:00
components: {
2023-06-07 01:16:43 +03:00
Field,
2023-11-11 23:36:03 +02:00
FieldType,
2024-03-27 13:47:17 +02:00
IconCirclePlus,
2023-09-05 01:00:21 +03:00
FieldSubmitter,
2023-11-11 23:36:03 +02:00
IconDrag,
2023-09-05 01:00:21 +03:00
IconLock
2023-05-21 17:59:36 +03:00
},
2024-12-01 00:10:07 +02:00
inject: ['save', 'backgroundColor', 'withPhone', 'withVerification', 'withPayment', 't', 'fieldsDragFieldRef'],
2023-05-21 17:59:36 +03:00
props: {
fields: {
type: Array,
required: true
2023-06-07 01:16:43 +03:00
},
2025-01-28 14:13:06 +02:00
withFieldsSearch: {
type: Boolean,
required: false,
default: null
},
2025-07-17 10:54:44 +03:00
withSignatureId: {
type: Boolean,
required: false,
default: null
},
2025-07-27 10:27:12 +03:00
withPrefillable: {
type: Boolean,
required: false,
default: false
},
2024-12-07 01:35:59 +02:00
template: {
type: Object,
required: true
},
2024-01-04 01:05:36 +02:00
withHelp: {
type: Boolean,
required: false,
default: true
},
2023-11-04 01:19:32 +02:00
editable: {
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-01-05 18:40:45 +02:00
onlyDefinedFields: {
type: Boolean,
required: false,
default: true
},
2024-03-27 13:47:17 +02:00
drawFieldType: {
type: String,
required: false,
default: ''
},
2023-12-17 16:10:17 +02:00
defaultSubmitters: {
type: Array,
required: false,
default: () => []
},
2023-08-19 21:05:54 +03:00
withStickySubmitters: {
type: Boolean,
required: false,
default: true
},
2024-01-10 23:18:30 +02:00
fieldTypes: {
type: Array,
required: false,
default: () => []
},
2023-06-07 01:16:43 +03:00
submitters: {
type: Array,
required: true
},
selectedSubmitter: {
type: Object,
required: true
2025-02-21 23:56:18 +02:00
},
showTourStartForm: {
type: Boolean,
required: false,
default: false
2023-05-21 17:59:36 +03:00
}
},
2025-02-17 19:47:46 +02:00
emits: ['add-field', 'set-draw', 'set-draw-type', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter', 'set-drag-placeholder'],
2024-03-19 01:21:44 +02:00
data () {
return {
defaultFieldsSearch: ''
}
},
2023-05-21 17:59:36 +03:00
computed: {
2023-06-27 00:57:29 +03:00
fieldNames: FieldType.computed.fieldNames,
2023-06-07 01:16:43 +03:00
fieldIcons: FieldType.computed.fieldIcons,
2024-03-19 01:21:44 +02:00
isShowFieldSearch () {
2025-01-28 14:13:06 +02:00
if (this.withFieldsSearch === false) {
return false
} else {
return this.submitterDefaultFields.length > 15
}
2024-03-19 01:21:44 +02:00
},
2025-01-07 17:30:21 +02:00
defaultFieldsIndex () {
return this.defaultFields.reduce((acc, field) => {
acc[field.name] = field
return acc
}, {})
},
2024-02-20 11:57:06 +02:00
fieldIconsSorted () {
if (this.fieldTypes.length) {
return this.fieldTypes.reduce((acc, type) => {
acc[type] = this.fieldIcons[type]
return acc
}, {})
} else {
2025-06-19 19:35:04 +03:00
return Object.fromEntries(Object.entries(this.fieldIcons).filter(([key]) => key !== 'heading' && key !== 'datenow'))
2024-02-20 11:57:06 +02:00
}
},
2023-06-07 01:16:43 +03:00
submitterFields () {
return this.fields.filter((f) => f.submitter_uuid === this.selectedSubmitter.uuid)
2023-11-11 23:36:03 +02:00
},
submitterDefaultFields () {
return this.defaultFields.filter((f) => {
2023-11-24 17:34:57 +02:00
return !this.submitterFields.find((field) => field.name === f.name) && (!f.role || f.role === this.selectedSubmitter.name)
2023-11-11 23:36:03 +02:00
})
2024-03-19 01:21:44 +02:00
},
filteredSubmitterDefaultFields () {
if (this.defaultFieldsSearch) {
2025-01-20 23:05:15 +02:00
return this.submitterDefaultFields.filter((f) => (f.title || f.name).toLowerCase().includes(this.defaultFieldsSearch.toLowerCase()))
2024-03-19 01:21:44 +02:00
} else {
return this.submitterDefaultFields
}
2023-06-07 01:16:43 +03:00
}
2023-05-21 17:59:36 +03:00
},
methods: {
2025-02-17 19:47:46 +02:00
onDragstart (event, field) {
this.removeDragOverlay(event)
this.setDragPlaceholder(event)
2023-11-11 23:36:03 +02:00
this.$emit('set-drag', field)
2023-05-23 00:03:26 +03:00
},
2025-02-17 19:47:46 +02:00
setDragPlaceholder (event) {
this.$emit('set-drag-placeholder', {
offsetX: event.offsetX,
offsetY: event.offsetY,
x: event.clientX - event.offsetX,
y: event.clientY - event.offsetY,
w: event.currentTarget.clientWidth + 2,
h: event.currentTarget.clientHeight + 2
})
},
removeDragOverlay (event) {
const root = this.$el.getRootNode()
const hiddenEl = document.createElement('div')
hiddenEl.style.width = '1px'
hiddenEl.style.height = '1px'
hiddenEl.style.opacity = '0'
hiddenEl.style.position = 'fixed'
root.querySelector('#docuseal_modal_container').appendChild(hiddenEl)
event.dataTransfer.setDragImage(hiddenEl, 0, 0)
setTimeout(() => { hiddenEl.remove() }, 1000)
},
2025-02-21 23:56:18 +02:00
startTour () {
document.querySelector('app-tour').start()
},
2023-11-04 14:06:59 +02:00
onFieldDragover (e) {
2025-02-17 19:47:46 +02:00
if (this.fieldsDragFieldRef.value) {
const targetField = e.target.closest('[data-uuid]')
const dragField = this.$refs.fields.querySelector(`[data-uuid="${this.fieldsDragFieldRef.value.uuid}"]`)
2023-11-04 14:06:59 +02:00
2025-02-17 19:47:46 +02:00
if (dragField && targetField && targetField !== dragField) {
const fields = Array.from(this.$refs.fields.children)
const currentIndex = fields.indexOf(dragField)
const targetIndex = fields.indexOf(targetField)
2023-11-04 14:06:59 +02:00
2025-02-17 19:47:46 +02:00
if (currentIndex < targetIndex) {
targetField.after(dragField)
} else {
targetField.before(dragField)
}
2023-11-04 14:06:59 +02:00
}
}
},
2023-12-31 22:39:11 +02:00
reorderFields () {
Array.from(this.$refs.fields.children).forEach((el, index) => {
if (el.dataset.uuid !== this.fields[index].uuid) {
const field = this.fields.find((f) => f.uuid === el.dataset.uuid)
this.fields.splice(this.fields.indexOf(field), 1)
this.fields.splice(index, 0, field)
}
})
this.save()
},
2023-06-07 01:16:43 +03:00
removeSubmitter (submitter) {
[...this.fields].forEach((field) => {
if (field.submitter_uuid === submitter.uuid) {
this.removeField(field)
}
})
this.submitters.splice(this.submitters.indexOf(submitter), 1)
if (this.selectedSubmitter === submitter) {
this.$emit('change-submitter', this.submitters[0])
}
2023-06-24 16:17:07 +03:00
this.save()
2023-06-07 01:16:43 +03:00
},
removeField (field) {
this.fields.splice(this.fields.indexOf(field), 1)
2023-06-24 16:17:07 +03:00
2024-03-01 23:29:37 +02:00
this.fields.forEach((f) => {
(f.conditions || []).forEach((c) => {
if (c.field_uuid === field.uuid) {
f.conditions.splice(f.conditions.indexOf(c), 1)
}
})
})
2024-12-07 01:35:59 +02:00
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-24 16:17:07 +03:00
this.save()
2023-05-21 17:59:36 +03:00
}
}
}
</script>