mirror of https://github.com/docusealco/docuseal
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
761 lines
25 KiB
761 lines
25 KiB
<template>
|
|
<div :class="withStickySubmitters ? 'sticky top-0 z-[1]' : ''">
|
|
<FieldSubmitter
|
|
:model-value="selectedSubmitter.uuid"
|
|
class="roles-dropdown w-full rounded-lg roles-dropdown"
|
|
:style="withStickySubmitters ? { backgroundColor } : {}"
|
|
:submitters="submitters"
|
|
:menu-style="{
|
|
overflow: 'auto',
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
maxHeight: 'calc(100vh - 120px)',
|
|
backgroundColor: ['', null, 'transparent'].includes(backgroundColor) ? 'white' : backgroundColor,
|
|
}"
|
|
:editable="editable && !defaultSubmitters.length"
|
|
@new-submitter="save"
|
|
@remove="removeSubmitter"
|
|
@name-change="save"
|
|
@update:model-value="
|
|
$emit(
|
|
'change-submitter',
|
|
submitters.find((s) => s.uuid === $event),
|
|
)
|
|
"
|
|
/>
|
|
</div>
|
|
<div ref="fields" class="fields mt-2" :class="{ 'mb-1': !withCustomFields || !customFields.length }" @dragover.prevent="onFieldDragover" @drop="fieldsDragFieldRef.value ? reorderFields() : null">
|
|
<Field
|
|
v-for="field in submitterFields"
|
|
:key="field.uuid"
|
|
:data-uuid="field.uuid"
|
|
:field="field"
|
|
:type-index="getFieldTypeIndex(field)"
|
|
:editable="editable"
|
|
:with-signature-id="withSignatureId"
|
|
:with-prefillable="withPrefillable"
|
|
:default-field="defaultFieldsIndex[field.name]"
|
|
:draggable="editable"
|
|
:with-custom-fields="withCustomFields"
|
|
class="mb-1.5"
|
|
@add-custom-field="addCustomField"
|
|
@dragstart="[(fieldsDragFieldRef.value = field), removeDragOverlay($event), setDragPlaceholder($event)]"
|
|
@save="save"
|
|
@dragend="[(fieldsDragFieldRef.value = null), $emit('set-drag-placeholder', null)]"
|
|
@remove="removeField"
|
|
@scroll-to="$emit('scroll-to-area', $event)"
|
|
@set-draw="$emit('set-draw', $event)"
|
|
/>
|
|
</div>
|
|
<div v-if="submitterDefaultFields.length && editable">
|
|
<hr class="mb-2" />
|
|
<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' : '',
|
|
}"
|
|
>
|
|
<div v-if="!filteredSubmitterDefaultFields.length && defaultFieldsSearch" class="top-0 bottom-0 text-center absolute flex items-center justify-center w-full flex-col">
|
|
<div>
|
|
{{ t("field_not_found") }}
|
|
</div>
|
|
<a href="#" class="link" @click.prevent="defaultFieldsSearch = ''">
|
|
{{ t("clear") }}
|
|
</a>
|
|
</div>
|
|
<template v-for="field in filteredSubmitterDefaultFields" :key="field.name">
|
|
<div
|
|
:style="{ backgroundColor }"
|
|
draggable="true"
|
|
class="border border-base-300 rounded relative group mb-2 default-field fields-list-item"
|
|
@dragstart="onDragstart($event, 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.title || field.name }}
|
|
</span>
|
|
</div>
|
|
<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>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<div v-if="editable && withCustomFields && (customFields.length || newCustomField)" class="tabs w-full mb-1.5">
|
|
<a class="tab tab-bordered w-1/2 border-base-300" :class="{ 'tab-active': !showCustomTab }" :style="{ '--tab-border': showCustomTab ? '0px' : '0.5px' }" @click="setFieldsTab('default')">{{
|
|
t("default")
|
|
}}</a>
|
|
<a class="tab tab-bordered w-1/2 border-base-300" :class="{ 'tab-active': showCustomTab }" :style="{ '--tab-border': showCustomTab ? '0.5px' : '0px' }" @click="setFieldsTab('custom')">{{
|
|
t("custom")
|
|
}}</a>
|
|
</div>
|
|
<div
|
|
v-if="showCustomTab && editable && (customFields.length || newCustomField)"
|
|
ref="customFields"
|
|
class="custom-fields"
|
|
@dragover.prevent="onCustomFieldDragover"
|
|
@drop="customDragFieldRef.value ? reorderCustomFields() : null"
|
|
>
|
|
<template v-if="isShowCustomFieldSearch">
|
|
<input v-model="customFieldsSearch" :placeholder="t('search_field')" class="input input-ghost input-xs px-0 text-base mb-1 !outline-0 !rounded bg-transparent w-full" />
|
|
<hr class="mb-2" />
|
|
</template>
|
|
<div
|
|
ref="customFieldsList"
|
|
class="overflow-auto relative"
|
|
:style="{
|
|
maxHeight: isShowCustomFieldSearch ? '320px' : '',
|
|
minHeight: '320px',
|
|
}"
|
|
>
|
|
<div v-if="!filteredCustomFields.length && customFieldsSearch" class="top-0 bottom-0 text-center absolute flex items-center justify-center w-full flex-col">
|
|
<div>
|
|
{{ t("field_not_found") }}
|
|
</div>
|
|
<a href="#" class="link" @click.prevent="customFieldsSearch = ''">
|
|
{{ t("clear") }}
|
|
</a>
|
|
</div>
|
|
<CustomField
|
|
v-if="newCustomField"
|
|
:key="newCustomField.uuid"
|
|
ref="newCustomField"
|
|
:data-uuid="newCustomField.uuid"
|
|
:is-new="true"
|
|
:field="newCustomField"
|
|
:draggable="false"
|
|
class="mb-1.5"
|
|
@save="onNewCustomFieldSave"
|
|
@remove="newCustomField = null"
|
|
/>
|
|
<CustomField
|
|
v-for="field in filteredCustomFields"
|
|
:key="field.uuid"
|
|
:data-uuid="field.uuid"
|
|
:field="field"
|
|
:draggable="true"
|
|
class="mb-1.5"
|
|
:with-signature-id="withSignatureId"
|
|
:with-prefillable="withPrefillable"
|
|
@save="saveCustomFields"
|
|
@click="$emit('set-draw-custom-field', field)"
|
|
@dragstart="[(customDragFieldRef.value = field), removeDragOverlay($event), setDragPlaceholder($event)]"
|
|
@dragend="[(customDragFieldRef.value = null), $emit('set-drag-placeholder', null)]"
|
|
@remove="removeCustomField"
|
|
@set-draw="$emit('set-draw', $event)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div v-if="editable && !onlyDefinedFields && (!showCustomTab || (!customFields.length && !newCustomField))" id="field-types-grid" class="grid grid-cols-3 gap-1 pb-2 fields-grid">
|
|
<template v-for="(icon, type) in fieldIconsSorted" :key="type">
|
|
<button
|
|
v-if="fieldTypes.includes(type) || ((withPhone || type != 'phone') && (withPayment || type != 'payment') && (withVerification || type != 'verification') && (withKba || type != 'kba'))"
|
|
:id="`${type}_type_field_button`"
|
|
draggable="true"
|
|
class="field-type-button group flex items-center justify-center border border-dashed w-full rounded relative fields-grid-item"
|
|
:style="{ backgroundColor }"
|
|
:class="drawFieldType === type ? 'border-base-content/40' : 'border-base-300 hover:border-base-content/20'"
|
|
@dragstart="onDragstart($event, { type: type })"
|
|
@dragend="$emit('drag-end')"
|
|
@click="['file', 'payment', 'verification', 'kba'].includes(type) ? $emit('add-field', type) : $emit('set-draw-type', type)"
|
|
>
|
|
<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" />
|
|
</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
|
|
v-else-if="type == 'phone' && (fieldTypes.length === 0 || fieldTypes.includes(type))"
|
|
class="tooltip tooltip-bottom flex"
|
|
:class="{ 'tooltip-bottom-end': withPayment, 'tooltip-bottom': !withPayment }"
|
|
:data-tip="t('unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification')"
|
|
>
|
|
<a
|
|
:href="(document.querySelector('meta[name=brand-website-url]')?.content || '') + '/pricing'"
|
|
target="_blank"
|
|
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative fields-grid-item"
|
|
: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>
|
|
<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
|
|
:href="(document.querySelector('meta[name=brand-website-url]')?.content || '') + '/qualified-electronic-signature'"
|
|
target="_blank"
|
|
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative fields-grid-item"
|
|
: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>
|
|
</template>
|
|
</div>
|
|
<div v-if="fields.length < 4 && editable && withHelp && !showTourStartForm" class="text-xs p-2 border border-base-200 rounded">
|
|
<ul class="list-disc list-outside ml-3">
|
|
<li>
|
|
{{ t("draw_a_text_field_on_the_page_with_a_mouse") }}
|
|
</li>
|
|
<li>
|
|
{{ t("drag_and_drop_any_other_field_type_on_the_page") }}
|
|
</li>
|
|
<li>
|
|
{{ t("click_on_the_field_type_above_to_start_drawing_it") }}
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div v-if="withFieldsDetection && editable && fields.length < 2" class="my-2">
|
|
<button class="btn w-full" :class="{ 'bg-base-300': fieldPagesLoaded !== null }" @click="fieldPagesLoaded !== null ? null : detectFields()">
|
|
<template v-if="fieldPagesLoaded !== null">
|
|
<IconInnerShadowTop width="22" class="animate-spin" />
|
|
<span v-if="analyzingProgress" class="hidden md:inline"> {{ Math.round(analyzingProgress * 100) }}% {{ t("analyzing_") }} </span>
|
|
<span v-else class="hidden md:inline"> {{ fieldPagesLoaded }} / {{ numberOfPages }} {{ t("processing_") }} </span>
|
|
</template>
|
|
<template v-else>
|
|
<IconSparkles width="22" />
|
|
<span class="hidden md:inline">
|
|
{{ t("autodetect_fields") }}
|
|
</span>
|
|
</template>
|
|
</button>
|
|
</div>
|
|
<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>
|
|
</template>
|
|
|
|
<script>
|
|
import Field from "./field";
|
|
import CustomField from "./custom_field";
|
|
import FieldType from "./field_type";
|
|
import FieldSubmitter from "./field_submitter";
|
|
import { IconLock, IconCirclePlus, IconInnerShadowTop, IconSparkles } from "@tabler/icons-vue";
|
|
import IconDrag from "./icon_drag";
|
|
import { v4 } from "uuid";
|
|
|
|
export default {
|
|
name: "TemplateFields",
|
|
components: {
|
|
Field,
|
|
CustomField,
|
|
FieldType,
|
|
IconCirclePlus,
|
|
IconSparkles,
|
|
IconInnerShadowTop,
|
|
FieldSubmitter,
|
|
IconDrag,
|
|
IconLock,
|
|
},
|
|
inject: ["save", "backgroundColor", "withPhone", "withVerification", "withKba", "withPayment", "t", "fieldsDragFieldRef", "customDragFieldRef", "baseFetch", "selectedAreasRef", "getFieldTypeIndex"],
|
|
props: {
|
|
fields: {
|
|
type: Array,
|
|
required: true,
|
|
},
|
|
customFields: {
|
|
type: Array,
|
|
required: false,
|
|
default: () => [],
|
|
},
|
|
withCustomFields: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false,
|
|
},
|
|
withFieldsSearch: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: null,
|
|
},
|
|
withFieldsDetection: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false,
|
|
},
|
|
withSignatureId: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: null,
|
|
},
|
|
withPrefillable: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false,
|
|
},
|
|
template: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
withHelp: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: true,
|
|
},
|
|
editable: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: true,
|
|
},
|
|
defaultFields: {
|
|
type: Array,
|
|
required: false,
|
|
default: () => [],
|
|
},
|
|
defaultRequiredFields: {
|
|
type: Array,
|
|
required: false,
|
|
default: () => [],
|
|
},
|
|
onlyDefinedFields: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: true,
|
|
},
|
|
drawFieldType: {
|
|
type: String,
|
|
required: false,
|
|
default: "",
|
|
},
|
|
defaultSubmitters: {
|
|
type: Array,
|
|
required: false,
|
|
default: () => [],
|
|
},
|
|
withStickySubmitters: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: true,
|
|
},
|
|
fieldTypes: {
|
|
type: Array,
|
|
required: false,
|
|
default: () => [],
|
|
},
|
|
submitters: {
|
|
type: Array,
|
|
required: true,
|
|
},
|
|
selectedSubmitter: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
showTourStartForm: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false,
|
|
},
|
|
},
|
|
emits: ["add-field", "set-draw", "set-draw-type", "set-draw-custom-field", "set-drag", "drag-end", "scroll-to-area", "change-submitter", "set-drag-placeholder", "select-submitter"],
|
|
data() {
|
|
return {
|
|
fieldPagesLoaded: null,
|
|
analyzingProgress: 0,
|
|
newCustomField: null,
|
|
showCustomTab: false,
|
|
defaultFieldsSearch: "",
|
|
customFieldsSearch: "",
|
|
};
|
|
},
|
|
computed: {
|
|
fieldNames: FieldType.computed.fieldNames,
|
|
fieldIcons: FieldType.computed.fieldIcons,
|
|
numberOfPages() {
|
|
return this.template.documents.reduce((acc, doc) => {
|
|
return acc + doc.metadata?.pdf?.number_of_pages || doc.preview_images.length;
|
|
}, 0);
|
|
},
|
|
isShowFieldSearch() {
|
|
if (this.withFieldsSearch === false) {
|
|
return false;
|
|
} else {
|
|
return this.submitterDefaultFields.length > 15;
|
|
}
|
|
},
|
|
defaultFieldsIndex() {
|
|
return this.defaultFields.reduce((acc, field) => {
|
|
acc[field.name] = field;
|
|
|
|
return acc;
|
|
}, {});
|
|
},
|
|
skipTypes() {
|
|
return ["heading", "datenow", "strikethrough"];
|
|
},
|
|
fieldIconsSorted() {
|
|
if (this.fieldTypes.length) {
|
|
return this.fieldTypes.reduce((acc, type) => {
|
|
acc[type] = this.fieldIcons[type];
|
|
|
|
return acc;
|
|
}, {});
|
|
} else {
|
|
return Object.fromEntries(Object.entries(this.fieldIcons).filter(([key]) => !this.skipTypes.includes(key)));
|
|
}
|
|
},
|
|
submitterFields() {
|
|
return this.fields.filter((f) => f.submitter_uuid === this.selectedSubmitter.uuid);
|
|
},
|
|
submitterDefaultFields() {
|
|
return this.defaultFields.filter((f) => {
|
|
return !this.submitterFields.find((field) => field.name === f.name) && (!f.role || f.role === this.selectedSubmitter.name);
|
|
});
|
|
},
|
|
filteredSubmitterDefaultFields() {
|
|
if (this.defaultFieldsSearch) {
|
|
return this.submitterDefaultFields.filter((f) => (f.title || f.name).toLowerCase().includes(this.defaultFieldsSearch.toLowerCase()));
|
|
} else {
|
|
return this.submitterDefaultFields;
|
|
}
|
|
},
|
|
isShowCustomFieldSearch() {
|
|
return this.customFields.length > 8;
|
|
},
|
|
filteredCustomFields() {
|
|
if (this.customFieldsSearch) {
|
|
return this.customFields.filter((f) => f.name.toLowerCase().includes(this.customFieldsSearch.toLowerCase()));
|
|
} else {
|
|
return this.customFields;
|
|
}
|
|
},
|
|
},
|
|
mounted() {
|
|
try {
|
|
this.showCustomTab = localStorage.getItem("docuseal_builder_tab") === "custom";
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
},
|
|
methods: {
|
|
onDragstart(event, field) {
|
|
this.removeDragOverlay(event);
|
|
|
|
this.setDragPlaceholder(event);
|
|
|
|
this.$emit("set-drag", field);
|
|
},
|
|
onNewCustomFieldSave() {
|
|
if (this.newCustomField.name) {
|
|
this.customFields.unshift(this.newCustomField);
|
|
this.newCustomField = null;
|
|
|
|
this.saveCustomFields();
|
|
} else {
|
|
this.newCustomField = null;
|
|
}
|
|
},
|
|
addCustomField(field) {
|
|
const customField = JSON.parse(JSON.stringify(field));
|
|
|
|
customField.uuid = v4();
|
|
|
|
delete customField.submitter_uuid;
|
|
delete customField.prefillable;
|
|
delete customField.conditions;
|
|
|
|
customField.areas?.forEach((area) => {
|
|
delete area.attachment_uuid;
|
|
delete area.page;
|
|
});
|
|
|
|
if (customField.name) {
|
|
this.customFields.unshift(customField);
|
|
this.saveCustomFields();
|
|
} else {
|
|
this.newCustomField = customField;
|
|
}
|
|
|
|
this.setFieldsTab("custom");
|
|
},
|
|
setFieldsTab(type) {
|
|
try {
|
|
localStorage.setItem("docuseal_builder_tab", type);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
|
|
this.showCustomTab = type === "custom";
|
|
},
|
|
saveCustomFields() {
|
|
return this.baseFetch("/account_custom_fields", {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
value: this.customFields,
|
|
}),
|
|
headers: { "Content-Type": "application/json" },
|
|
}).then(async (resp) => {
|
|
const fields = await resp.json();
|
|
|
|
this.customFields.splice(0, this.customFields.length, ...fields);
|
|
});
|
|
},
|
|
detectFields() {
|
|
const fields = [];
|
|
|
|
this.fieldPagesLoaded = 0;
|
|
|
|
this.baseFetch(`/templates/${this.template.id}/detect_fields`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
})
|
|
.then(async (response) => {
|
|
const reader = response.body.getReader();
|
|
const decoder = new TextDecoder("utf-8");
|
|
let buffer = "";
|
|
|
|
while (true) {
|
|
const { value, done } = await reader.read();
|
|
|
|
buffer += decoder.decode(value, { stream: true });
|
|
|
|
const lines = buffer.split("\n\n");
|
|
|
|
buffer = lines.pop();
|
|
|
|
for (const line of lines) {
|
|
if (line.startsWith("data: ")) {
|
|
const jsonStr = line.replace(/^data: /, "");
|
|
const data = JSON.parse(jsonStr);
|
|
|
|
if (data.error) {
|
|
if ((data.fields || fields).length) {
|
|
this.template.fields = data.fields || fields;
|
|
|
|
this.save();
|
|
} else {
|
|
alert(data.error);
|
|
}
|
|
|
|
break;
|
|
} else if (data.analyzing) {
|
|
this.analyzingProgress = data.progress;
|
|
} else if (data.completed) {
|
|
this.fieldPagesLoaded = null;
|
|
|
|
if (data.submitters) {
|
|
this.template.submitters = data.submitters;
|
|
this.$emit("select-submitter", this.template.submitters[0]);
|
|
}
|
|
|
|
this.template.fields = data.fields || fields;
|
|
|
|
this.save();
|
|
|
|
break;
|
|
} else if (data.fields) {
|
|
data.fields.forEach((f) => {
|
|
if (!f.submitter_uuid) {
|
|
f.submitter_uuid = this.template.submitters[0].uuid;
|
|
}
|
|
});
|
|
|
|
this.fieldPagesLoaded += 1;
|
|
|
|
fields.push(...data.fields);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (done) break;
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error("Error in streaming message: ", error);
|
|
})
|
|
.finally(() => {
|
|
this.fieldPagesLoaded = null;
|
|
this.analyzingProgress = null;
|
|
this.isFieldsLoading = false;
|
|
});
|
|
},
|
|
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);
|
|
},
|
|
startTour() {
|
|
document.querySelector("app-tour").start();
|
|
},
|
|
onFieldDragover(e) {
|
|
if (this.fieldsDragFieldRef.value) {
|
|
const targetField = e.target.closest("[data-uuid]");
|
|
const dragField = this.$refs.fields.querySelector(`[data-uuid="${this.fieldsDragFieldRef.value.uuid}"]`);
|
|
|
|
if (dragField && targetField && targetField !== dragField) {
|
|
const fields = Array.from(this.$refs.fields.children);
|
|
const currentIndex = fields.indexOf(dragField);
|
|
const targetIndex = fields.indexOf(targetField);
|
|
|
|
if (currentIndex < targetIndex) {
|
|
targetField.after(dragField);
|
|
} else {
|
|
targetField.before(dragField);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
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();
|
|
},
|
|
removeSubmitter(submitter) {
|
|
[...this.fields].forEach((field) => {
|
|
if (field.submitter_uuid === submitter.uuid) {
|
|
this.removeField(field, false);
|
|
}
|
|
});
|
|
|
|
this.submitters.splice(this.submitters.indexOf(submitter), 1);
|
|
|
|
if (this.selectedSubmitter === submitter) {
|
|
this.$emit("change-submitter", this.submitters[0]);
|
|
}
|
|
|
|
this.save();
|
|
},
|
|
removeField(field, save = true) {
|
|
this.fields.splice(this.fields.indexOf(field), 1);
|
|
|
|
this.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);
|
|
}
|
|
});
|
|
});
|
|
|
|
field.areas?.forEach((area) => {
|
|
this.selectedAreasRef.value.splice(this.selectedAreasRef.value.indexOf(area), 1);
|
|
});
|
|
|
|
if (save) {
|
|
this.save();
|
|
}
|
|
},
|
|
removeCustomField(field) {
|
|
this.customFields.splice(this.customFields.indexOf(field), 1);
|
|
|
|
if (!this.customFields.length) {
|
|
this.setFieldsTab("default");
|
|
}
|
|
|
|
this.saveCustomFields();
|
|
},
|
|
onCustomFieldDragover(e) {
|
|
if (this.customDragFieldRef.value && this.customFields.includes(this.customDragFieldRef.value)) {
|
|
const container = this.$refs.customFieldsList;
|
|
const targetField = e.target.closest("[data-uuid]");
|
|
const dragField = container.querySelector(`[data-uuid="${this.customDragFieldRef.value.uuid}"]`);
|
|
|
|
if (dragField && targetField && targetField !== dragField) {
|
|
const fields = Array.from(container.children);
|
|
const currentIndex = fields.indexOf(dragField);
|
|
const targetIndex = fields.indexOf(targetField);
|
|
|
|
if (currentIndex < targetIndex) {
|
|
targetField.after(dragField);
|
|
} else {
|
|
targetField.before(dragField);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
reorderCustomFields() {
|
|
if (!this.customFields.includes(this.customDragFieldRef.value)) {
|
|
return;
|
|
}
|
|
|
|
const reorderedFields = Array.from(this.$refs.customFieldsList.children)
|
|
.map((el) => {
|
|
return this.customFields.find((f) => f.uuid === el.dataset.uuid);
|
|
})
|
|
.filter(Boolean);
|
|
|
|
this.customFields.splice(0, this.customFields.length, ...reorderedFields);
|
|
|
|
this.saveCustomFields();
|
|
},
|
|
},
|
|
};
|
|
</script>
|