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

372 lines
11 KiB
Vue
Raw Normal View History

2023-05-21 17:59:36 +03:00
<template>
2023-08-19 21:05:54 +03:00
<div :class="withStickySubmitters ? 'sticky top-0 z-10' : ''">
2023-06-16 00:04:05 +03:00
<FieldSubmitter
:model-value="selectedSubmitter.uuid"
2024-03-07 18:59:19 +02:00
class="roles-dropdown w-full rounded-lg"
2023-08-19 21:05:54 +03:00
:class="{ 'bg-base-100': withStickySubmitters }"
2023-06-16 00:04:05 +03:00
:submitters="submitters"
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)"
2024-02-27 01:41:57 +02:00
:editable="editable && (!fieldsDragFieldRef.value || fieldsDragFieldRef.value !== field)"
2023-11-27 20:24:58 +02:00
:default-field="defaultFields.find((f) => f.name === field.name)"
2023-11-04 14:06:59 +02:00
:draggable="editable"
2024-02-27 01:41:57 +02:00
@dragstart="fieldsDragFieldRef.value = field"
@dragend="fieldsDragFieldRef.value = 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>
2023-11-11 23:36:03 +02:00
<div v-if="submitterDefaultFields.length">
<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
:style="{ backgroundColor: backgroundColor }"
draggable="true"
class="default-field border border-base-300 rounded rounded-tr-none relative group mb-2"
@dragstart="onDragstart({ type: 'text', ...field })"
@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">
{{ field.name }}
</span>
</div>
</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"
2023-11-04 01:19:32 +02:00
class="grid grid-cols-3 gap-1 pb-2"
>
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
2024-01-10 23:18:30 +02:00
v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment')"
2023-09-05 01:00:21 +03:00
draggable="true"
2024-03-07 18:59:19 +02:00
class="field-type-button group flex items-center justify-center border border-dashed border-base-300 hover:border-base-content/20 w-full rounded relative"
2023-09-05 01:00:21 +03:00
:style="{ backgroundColor: backgroundColor }"
2023-11-11 23:36:03 +02:00
@dragstart="onDragstart({ type: type })"
2023-09-05 01:00:21 +03:00
@dragend="$emit('drag-end')"
@click="addField(type)"
>
2023-11-21 23:08:59 +02:00
<div class="flex items-console group-hover:bg-base-200/50 transition-all cursor-grab h-full absolute left-0">
<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 }"
2023-09-05 01:00:21 +03:00
data-tip="Unlock SMS-verified phone number field with paid plan. Use text field for phone numbers without verification."
>
<a
href="https://www.docuseal.co/pricing"
target="_blank"
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
:style="{ backgroundColor: 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>
2023-06-03 00:53:46 +03:00
</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
2024-01-04 01:05:36 +02:00
v-if="fields.length < 4 && editable && withHelp"
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>
Draw a text field on the page with a mouse
</li>
<li>
Drag &amp; drop any other field type on the page
</li>
<li>
2024-01-06 21:16:27 +02:00
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>
2023-05-21 17:59:36 +03:00
</template>
<script>
import Field from './field'
import { v4 } from 'uuid'
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'
2023-09-05 01:00:21 +03:00
import { IconLock } 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,
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-02-27 01:41:57 +02:00
inject: ['save', 'backgroundColor', 'withPhone', '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
},
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-01-05 18:40:45 +02:00
onlyDefinedFields: {
type: Boolean,
required: false,
default: true
},
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
2023-05-21 17:59:36 +03:00
}
},
2023-06-07 01:16:43 +03:00
emits: ['set-draw', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter'],
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 () {
return this.submitterDefaultFields.length > 15
},
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 {
return this.fieldIcons
}
},
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) {
return this.submitterDefaultFields.filter((f) => f.name.toLowerCase().includes(this.defaultFieldsSearch.toLowerCase()))
} else {
return this.submitterDefaultFields
}
2023-06-07 01:16:43 +03:00
}
2023-05-21 17:59:36 +03:00
},
methods: {
2023-11-11 23:36:03 +02:00
onDragstart (field) {
this.$emit('set-drag', field)
2023-05-23 00:03:26 +03:00
},
2023-11-04 14:06:59 +02:00
onFieldDragover (e) {
2023-12-31 22:39:11 +02:00
const targetField = e.target.closest('[data-uuid]')
2024-02-27 01:41:57 +02:00
const dragField = this.$refs.fields.querySelector(`[data-uuid="${this.fieldsDragFieldRef.value.uuid}"]`)
2023-11-04 14:06:59 +02:00
2023-12-31 22:39:11 +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
if (currentIndex < targetIndex) {
2023-12-31 22:39:11 +02:00
targetField.after(dragField)
2023-11-04 14:06:59 +02:00
} else {
2023-12-31 22:39:11 +02:00
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)
}
})
})
2023-06-24 16:17:07 +03:00
this.save()
2023-06-07 01:16:43 +03:00
},
2023-05-23 00:03:26 +03:00
addField (type, area = null) {
2023-05-21 17:59:36 +03:00
const field = {
2023-06-04 01:08:45 +03:00
name: '',
2023-05-21 17:59:36 +03:00
uuid: v4(),
2023-07-22 20:00:28 +03:00
required: type !== 'checkbox',
areas: [],
2023-06-07 01:16:43 +03:00
submitter_uuid: this.selectedSubmitter.uuid,
2023-05-21 17:59:36 +03:00
type
}
2023-06-16 00:04:05 +03:00
if (['select', 'multiple', 'radio'].includes(type)) {
2023-11-13 01:46:28 +02:00
field.options = [{ value: '', uuid: v4() }]
2023-05-21 17:59:36 +03:00
}
2023-12-22 13:33:53 +02:00
if (type === 'stamp') {
field.readonly = true
}
2023-12-17 23:32:05 +02:00
if (type === 'date') {
field.preferences = {
format: Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY'
}
}
2023-05-21 17:59:36 +03:00
this.fields.push(field)
2023-06-24 16:17:07 +03:00
2024-01-06 21:16:27 +02:00
if (!['payment', 'file'].includes(type)) {
2023-11-21 23:08:59 +02:00
this.$emit('set-draw', { field })
}
2023-06-24 16:17:07 +03:00
this.save()
2023-05-21 17:59:36 +03:00
}
}
}
</script>