add initials field
This commit is contained in:
@@ -74,3 +74,20 @@ button[disabled] .enabled {
|
|||||||
.base-select {
|
.base-select {
|
||||||
@apply select base-input w-full font-normal;
|
@apply select base-input w-full font-normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip-bottom-end:before {
|
||||||
|
transform: translateX(-95%);
|
||||||
|
top: var(--tooltip-offset);
|
||||||
|
left: 100%;
|
||||||
|
right: auto;
|
||||||
|
bottom: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-bottom-end:after {
|
||||||
|
transform: translateX(-25%);
|
||||||
|
border-color: transparent transparent var(--tooltip-color) transparent;
|
||||||
|
top: var(--tooltip-tail-offset);
|
||||||
|
left: 50%;
|
||||||
|
right: auto;
|
||||||
|
bottom: auto;
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,6 +47,11 @@
|
|||||||
class="object-contain mx-auto"
|
class="object-contain mx-auto"
|
||||||
:src="signature.url"
|
:src="signature.url"
|
||||||
>
|
>
|
||||||
|
<img
|
||||||
|
v-else-if="field.type === 'initials' && initials"
|
||||||
|
class="object-contain mx-auto"
|
||||||
|
:src="initials.url"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'file'"
|
v-else-if="field.type === 'file'"
|
||||||
class="px-0.5 flex flex-col justify-center"
|
class="px-0.5 flex flex-col justify-center"
|
||||||
@@ -113,7 +118,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconCheck, IconColumns3, IconPhoneCheck } from '@tabler/icons-vue'
|
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconCheck, IconColumns3, IconPhoneCheck, IconLetterCaseUpper } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FieldArea',
|
name: 'FieldArea',
|
||||||
@@ -175,6 +180,7 @@ export default {
|
|||||||
signature: 'Signature',
|
signature: 'Signature',
|
||||||
date: 'Date',
|
date: 'Date',
|
||||||
image: 'Image',
|
image: 'Image',
|
||||||
|
initials: 'Initials',
|
||||||
file: 'File',
|
file: 'File',
|
||||||
select: 'Select',
|
select: 'Select',
|
||||||
checkbox: 'Checkbox',
|
checkbox: 'Checkbox',
|
||||||
@@ -189,6 +195,7 @@ export default {
|
|||||||
signature: IconWritingSign,
|
signature: IconWritingSign,
|
||||||
date: IconCalendarEvent,
|
date: IconCalendarEvent,
|
||||||
image: IconPhoto,
|
image: IconPhoto,
|
||||||
|
initials: IconLetterCaseUpper,
|
||||||
file: IconPaperclip,
|
file: IconPaperclip,
|
||||||
select: IconSelect,
|
select: IconSelect,
|
||||||
checkbox: IconCheckbox,
|
checkbox: IconCheckbox,
|
||||||
@@ -212,6 +219,13 @@ export default {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
initials () {
|
||||||
|
if (this.field.type === 'initials') {
|
||||||
|
return this.attachmentsIndex[this.modelValue]
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
formattedDate () {
|
formattedDate () {
|
||||||
if (this.field.type === 'date' && this.modelValue) {
|
if (this.field.type === 'date' && this.modelValue) {
|
||||||
return new Intl.DateTimeFormat([], { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' }).format(new Date(this.modelValue))
|
return new Intl.DateTimeFormat([], { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' }).format(new Date(this.modelValue))
|
||||||
|
|||||||
@@ -256,6 +256,19 @@
|
|||||||
@start="$refs.areas.scrollIntoField(currentField)"
|
@start="$refs.areas.scrollIntoField(currentField)"
|
||||||
@minimize="isFormVisible = false"
|
@minimize="isFormVisible = false"
|
||||||
/>
|
/>
|
||||||
|
<InitialsStep
|
||||||
|
v-else-if="currentField.type === 'initials'"
|
||||||
|
ref="currentStep"
|
||||||
|
v-model="values[currentField.uuid]"
|
||||||
|
:field="currentField"
|
||||||
|
:is-direct-upload="isDirectUpload"
|
||||||
|
:attachments-index="attachmentsIndex"
|
||||||
|
:submitter-slug="submitterSlug"
|
||||||
|
@attached="attachments.push($event)"
|
||||||
|
@start="$refs.areas.scrollIntoField(currentField)"
|
||||||
|
@focus="$refs.areas.scrollIntoField(currentField)"
|
||||||
|
@minimize="isFormVisible = false"
|
||||||
|
/>
|
||||||
<AttachmentStep
|
<AttachmentStep
|
||||||
v-else-if="currentField.type === 'file'"
|
v-else-if="currentField.type === 'file'"
|
||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
@@ -328,6 +341,7 @@
|
|||||||
import FieldAreas from './areas'
|
import FieldAreas from './areas'
|
||||||
import ImageStep from './image_step'
|
import ImageStep from './image_step'
|
||||||
import SignatureStep from './signature_step'
|
import SignatureStep from './signature_step'
|
||||||
|
import InitialsStep from './initials_step'
|
||||||
import AttachmentStep from './attachment_step'
|
import AttachmentStep from './attachment_step'
|
||||||
import MultiSelectStep from './multi_select_step'
|
import MultiSelectStep from './multi_select_step'
|
||||||
import PhoneStep from './phone_step'
|
import PhoneStep from './phone_step'
|
||||||
@@ -342,6 +356,7 @@ export default {
|
|||||||
ImageStep,
|
ImageStep,
|
||||||
SignatureStep,
|
SignatureStep,
|
||||||
AttachmentStep,
|
AttachmentStep,
|
||||||
|
InitialsStep,
|
||||||
MultiSelectStep,
|
MultiSelectStep,
|
||||||
IconInnerShadowTop,
|
IconInnerShadowTop,
|
||||||
IconArrowsDiagonal,
|
IconArrowsDiagonal,
|
||||||
@@ -439,7 +454,8 @@ export default {
|
|||||||
if (this.recalculateButtonDisabledKey) {
|
if (this.recalculateButtonDisabledKey) {
|
||||||
return this.isSubmitting ||
|
return this.isSubmitting ||
|
||||||
(this.currentField.required && ['image', 'file'].includes(this.currentField.type) && !this.values[this.currentField.uuid]?.length) ||
|
(this.currentField.required && ['image', 'file'].includes(this.currentField.type) && !this.values[this.currentField.uuid]?.length) ||
|
||||||
(this.currentField.required && this.currentField.type === 'signature' && !this.values[this.currentField.uuid]?.length && this.$refs.currentStep && !this.$refs.currentStep.isSignatureStarted)
|
(this.currentField.required && this.currentField.type === 'signature' && !this.values[this.currentField.uuid]?.length && this.$refs.currentStep && !this.$refs.currentStep.isSignatureStarted) ||
|
||||||
|
(this.currentField.required && this.currentField.type === 'initials' && !this.values[this.currentField.uuid]?.length && this.$refs.currentStep && !this.$refs.currentStep.isInitialsStarted)
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -566,7 +582,7 @@ export default {
|
|||||||
async submitStep () {
|
async submitStep () {
|
||||||
this.isSubmitting = true
|
this.isSubmitting = true
|
||||||
|
|
||||||
const stepPromise = ['signature', 'phone'].includes(this.currentField.type)
|
const stepPromise = ['signature', 'phone', 'initials'].includes(this.currentField.type)
|
||||||
? this.$refs.currentStep.submit
|
? this.$refs.currentStep.submit
|
||||||
: () => Promise.resolve({})
|
: () => Promise.resolve({})
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ const en = {
|
|||||||
or_drag_and_drop_files: 'or drag and drop files',
|
or_drag_and_drop_files: 'or drag and drop files',
|
||||||
send_copy_via_email: 'Send copy via email',
|
send_copy_via_email: 'Send copy via email',
|
||||||
download: 'Download',
|
download: 'Download',
|
||||||
|
signature: 'Signature',
|
||||||
|
initials: 'Initials',
|
||||||
|
clear: 'Clear',
|
||||||
|
redraw: 'Redraw',
|
||||||
|
draw_initials: 'Draw initials',
|
||||||
|
type_signature_here: 'Type signature here',
|
||||||
|
type_initial_here: 'Type initials here',
|
||||||
form_has_been_completed: 'Form has been completed!',
|
form_has_been_completed: 'Form has been completed!',
|
||||||
create_a_free_account: 'Create a Free Account',
|
create_a_free_account: 'Create a Free Account',
|
||||||
signed_with: 'Signed with',
|
signed_with: 'Signed with',
|
||||||
@@ -36,6 +43,13 @@ const es = {
|
|||||||
or_drag_and_drop_files: 'o arrastra y suelta archivos',
|
or_drag_and_drop_files: 'o arrastra y suelta archivos',
|
||||||
send_copy_via_email: 'Enviar copia por correo electrónico',
|
send_copy_via_email: 'Enviar copia por correo electrónico',
|
||||||
download: 'Descargar',
|
download: 'Descargar',
|
||||||
|
signature: 'Firma',
|
||||||
|
initials: 'Iniciales',
|
||||||
|
clear: 'Borrar',
|
||||||
|
redraw: 'Redibujar',
|
||||||
|
draw_initials: 'Dibujar iniciales',
|
||||||
|
type_signature_here: 'Escribe la firma aquí',
|
||||||
|
type_initial_here: 'Escribe las iniciales aquí',
|
||||||
form_has_been_completed: '¡El formulario ha sido completado!',
|
form_has_been_completed: '¡El formulario ha sido completado!',
|
||||||
create_a_free_account: 'Crear una Cuenta Gratuita',
|
create_a_free_account: 'Crear una Cuenta Gratuita',
|
||||||
signed_with: 'Firmado con',
|
signed_with: 'Firmado con',
|
||||||
@@ -62,6 +76,13 @@ const it = {
|
|||||||
or_drag_and_drop_files: 'oppure trascina e rilascia i file',
|
or_drag_and_drop_files: 'oppure trascina e rilascia i file',
|
||||||
send_copy_via_email: 'Invia copia via email',
|
send_copy_via_email: 'Invia copia via email',
|
||||||
download: 'Scarica',
|
download: 'Scarica',
|
||||||
|
signature: 'Firma',
|
||||||
|
initials: 'Iniziali',
|
||||||
|
clear: 'Cancella',
|
||||||
|
redraw: 'Ridisegna',
|
||||||
|
draw_initials: 'Disegna iniziali',
|
||||||
|
type_signature_here: 'Scrivi la firma qui',
|
||||||
|
type_initial_here: 'Scrivi le iniziali qui',
|
||||||
form_has_been_completed: 'Il modulo è stato completato!',
|
form_has_been_completed: 'Il modulo è stato completato!',
|
||||||
create_a_free_account: 'Crea un Account Gratuito',
|
create_a_free_account: 'Crea un Account Gratuito',
|
||||||
signed_with: 'Firmato con',
|
signed_with: 'Firmato con',
|
||||||
@@ -88,6 +109,13 @@ const de = {
|
|||||||
or_drag_and_drop_files: 'oder Dateien hierher ziehen und ablegen',
|
or_drag_and_drop_files: 'oder Dateien hierher ziehen und ablegen',
|
||||||
send_copy_via_email: 'Kopie per E-Mail senden',
|
send_copy_via_email: 'Kopie per E-Mail senden',
|
||||||
download: 'Herunterladen',
|
download: 'Herunterladen',
|
||||||
|
signature: 'Unterschrift',
|
||||||
|
initials: 'Initialen',
|
||||||
|
clear: 'Löschen',
|
||||||
|
redraw: 'Neu zeichnen',
|
||||||
|
draw_initials: 'Initialen zeichnen',
|
||||||
|
type_signature_here: 'Unterschrift hier eingeben',
|
||||||
|
type_initial_here: 'Initialen hier eingeben',
|
||||||
form_has_been_completed: 'Formular wurde ausgefüllt!',
|
form_has_been_completed: 'Formular wurde ausgefüllt!',
|
||||||
create_a_free_account: 'Kostenloses Konto erstellen',
|
create_a_free_account: 'Kostenloses Konto erstellen',
|
||||||
signed_with: 'Unterschrieben mit',
|
signed_with: 'Unterschrieben mit',
|
||||||
@@ -114,6 +142,13 @@ const fr = {
|
|||||||
or_drag_and_drop_files: 'ou faites glisser-déposer les fichiers',
|
or_drag_and_drop_files: 'ou faites glisser-déposer les fichiers',
|
||||||
send_copy_via_email: 'Envoyer une copie par e-mail',
|
send_copy_via_email: 'Envoyer une copie par e-mail',
|
||||||
download: 'Télécharger',
|
download: 'Télécharger',
|
||||||
|
signature: 'Signature',
|
||||||
|
initials: 'Initiales',
|
||||||
|
clear: 'Effacer',
|
||||||
|
redraw: 'Redessiner',
|
||||||
|
draw_initials: 'Dessiner les initiales',
|
||||||
|
type_signature_here: 'Tapez la signature ici',
|
||||||
|
type_initial_here: 'Tapez les initiales ici',
|
||||||
form_has_been_completed: 'Le formulaire a été complété !',
|
form_has_been_completed: 'Le formulaire a été complété !',
|
||||||
create_a_free_account: 'Créer un Compte Gratuit',
|
create_a_free_account: 'Créer un Compte Gratuit',
|
||||||
signed_with: 'Signé avec',
|
signed_with: 'Signé avec',
|
||||||
@@ -140,6 +175,13 @@ const pl = {
|
|||||||
or_drag_and_drop_files: 'lub przeciągnij i upuść pliki',
|
or_drag_and_drop_files: 'lub przeciągnij i upuść pliki',
|
||||||
send_copy_via_email: 'Wyślij kopię drogą mailową',
|
send_copy_via_email: 'Wyślij kopię drogą mailową',
|
||||||
download: 'Pobierz',
|
download: 'Pobierz',
|
||||||
|
signature: 'Podpis',
|
||||||
|
initials: 'Inicjały',
|
||||||
|
clear: 'Wyczyść',
|
||||||
|
redraw: 'Przerysuj',
|
||||||
|
draw_initials: 'Narysuj inicjały',
|
||||||
|
type_signature_here: 'Wpisz podpis tutaj',
|
||||||
|
type_initial_here: 'Wpisz inicjały tutaj',
|
||||||
form_has_been_completed: 'Formularz został wypełniony!',
|
form_has_been_completed: 'Formularz został wypełniony!',
|
||||||
create_a_free_account: 'Utwórz darmowe konto',
|
create_a_free_account: 'Utwórz darmowe konto',
|
||||||
signed_with: 'Podpisane za pomocą',
|
signed_with: 'Podpisane za pomocą',
|
||||||
@@ -166,6 +208,13 @@ const uk = {
|
|||||||
or_drag_and_drop_files: 'або перетягніть файли сюди',
|
or_drag_and_drop_files: 'або перетягніть файли сюди',
|
||||||
send_copy_via_email: 'Надіслати копію електронною поштою',
|
send_copy_via_email: 'Надіслати копію електронною поштою',
|
||||||
download: 'Завантажити',
|
download: 'Завантажити',
|
||||||
|
signature: 'Підпис',
|
||||||
|
initials: 'Ініціали',
|
||||||
|
clear: 'Очистити',
|
||||||
|
redraw: 'Перемалювати',
|
||||||
|
draw_initials: 'Намалювати ініціали',
|
||||||
|
type_signature_here: 'Введіть підпис тут',
|
||||||
|
type_initial_here: 'Введіть ініціали тут',
|
||||||
form_has_been_completed: 'Форму заповнено!',
|
form_has_been_completed: 'Форму заповнено!',
|
||||||
create_a_free_account: 'Створити безкоштовний обліковий запис',
|
create_a_free_account: 'Створити безкоштовний обліковий запис',
|
||||||
signed_with: 'Підписано за допомогою',
|
signed_with: 'Підписано за допомогою',
|
||||||
@@ -192,6 +241,13 @@ const cs = {
|
|||||||
or_drag_and_drop_files: 'nebo přetáhněte soubory sem',
|
or_drag_and_drop_files: 'nebo přetáhněte soubory sem',
|
||||||
send_copy_via_email: 'Odeslat kopii e-mailem',
|
send_copy_via_email: 'Odeslat kopii e-mailem',
|
||||||
download: 'Stáhnout',
|
download: 'Stáhnout',
|
||||||
|
signature: 'Podpis',
|
||||||
|
initials: 'Iniciály',
|
||||||
|
clear: 'Smazat',
|
||||||
|
redraw: 'Překreslit',
|
||||||
|
draw_initials: 'Nakreslit iniciály',
|
||||||
|
type_signature_here: 'Sem zadejte podpis',
|
||||||
|
type_initial_here: 'Sem zadejte iniciály',
|
||||||
form_has_been_completed: 'Formulář byl dokončen!',
|
form_has_been_completed: 'Formulář byl dokončen!',
|
||||||
create_a_free_account: 'Vytvořit bezplatný účet',
|
create_a_free_account: 'Vytvořit bezplatný účet',
|
||||||
signed_with: 'Podepsáno pomocí',
|
signed_with: 'Podepsáno pomocí',
|
||||||
@@ -218,6 +274,13 @@ const pt = {
|
|||||||
or_drag_and_drop_files: 'ou arraste e solte arquivos',
|
or_drag_and_drop_files: 'ou arraste e solte arquivos',
|
||||||
send_copy_via_email: 'Enviar cópia por e-mail',
|
send_copy_via_email: 'Enviar cópia por e-mail',
|
||||||
download: 'Baixar',
|
download: 'Baixar',
|
||||||
|
signature: 'Assinatura',
|
||||||
|
initials: 'Iniciais',
|
||||||
|
clear: 'Limpar',
|
||||||
|
redraw: 'Redesenhar',
|
||||||
|
draw_initials: 'Desenhar iniciais',
|
||||||
|
type_signature_here: 'Digite a assinatura aqui',
|
||||||
|
type_initial_here: 'Digite as iniciais aqui',
|
||||||
form_has_been_completed: 'O formulário foi concluído!',
|
form_has_been_completed: 'O formulário foi concluído!',
|
||||||
create_a_free_account: 'Criar uma Conta Gratuita',
|
create_a_free_account: 'Criar uma Conta Gratuita',
|
||||||
signed_with: 'Assinado com',
|
signed_with: 'Assinado com',
|
||||||
|
|||||||
@@ -0,0 +1,245 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between items-center w-full mb-2">
|
||||||
|
<label
|
||||||
|
class="label text-2xl"
|
||||||
|
>{{ field.name || t('initials') }}</label>
|
||||||
|
<div class="space-x-2 flex">
|
||||||
|
<span
|
||||||
|
class="tooltip"
|
||||||
|
:data-tip="t('draw_initials')"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
id="type_text_button"
|
||||||
|
class="btn btn-sm btn-circle"
|
||||||
|
:class="{ 'btn-neutral': isDrawInitials, 'btn-outline': !isDrawInitials }"
|
||||||
|
@click.prevent="toggleTextInput"
|
||||||
|
>
|
||||||
|
<IconSignature :width="16" />
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
v-if="modelValue"
|
||||||
|
class="btn btn-outline btn-sm"
|
||||||
|
@click.prevent="remove"
|
||||||
|
>
|
||||||
|
<IconReload :width="16" />
|
||||||
|
{{ t('clear') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
class="btn btn-outline btn-sm"
|
||||||
|
@click.prevent="clear"
|
||||||
|
>
|
||||||
|
<IconReload :width="16" />
|
||||||
|
{{ t('clear') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
title="Minimize"
|
||||||
|
class="py-1.5 inline md:hidden"
|
||||||
|
@click.prevent="$emit('minimize')"
|
||||||
|
>
|
||||||
|
<IconArrowsDiagonalMinimize2
|
||||||
|
:width="20"
|
||||||
|
:height="20"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
:value="modelValue"
|
||||||
|
type="hidden"
|
||||||
|
:name="`values[${field.uuid}]`"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="modelValue"
|
||||||
|
:src="attachmentsIndex[modelValue].url"
|
||||||
|
class="mx-auto bg-white border border-base-300 rounded max-h-72"
|
||||||
|
>
|
||||||
|
<canvas
|
||||||
|
v-show="!modelValue"
|
||||||
|
ref="canvas"
|
||||||
|
class="bg-white border border-base-300 rounded"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
v-if="!isDrawInitials"
|
||||||
|
id="initials_text_input"
|
||||||
|
ref="textInput"
|
||||||
|
class="base-input !text-2xl w-full mt-6 text-center"
|
||||||
|
:required="field.required && !isInitialsStarted"
|
||||||
|
:placeholder="`${t('type_initial_here')}...`"
|
||||||
|
type="text"
|
||||||
|
@focus="$emit('focus')"
|
||||||
|
@input="updateWrittenInitials"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SignatureStep from './signature_step'
|
||||||
|
import { IconReload, IconSignature, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'InitialsStep',
|
||||||
|
components: {
|
||||||
|
IconReload,
|
||||||
|
IconSignature,
|
||||||
|
IconArrowsDiagonalMinimize2
|
||||||
|
},
|
||||||
|
inject: ['baseUrl', 't'],
|
||||||
|
props: {
|
||||||
|
field: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
submitterSlug: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isDirectUpload: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
attachmentsIndex: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['attached', 'update:model-value', 'start', 'minimize', 'focus'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isInitialsStarted: false,
|
||||||
|
isDrawInitials: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted () {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.$refs.canvas) {
|
||||||
|
this.$refs.canvas.width = this.$refs.canvas.parentNode.clientWidth
|
||||||
|
this.$refs.canvas.height = this.$refs.canvas.parentNode.clientWidth / 5
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$refs.textInput?.focus()
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.isDirectUpload) {
|
||||||
|
import('@rails/activestorage')
|
||||||
|
}
|
||||||
|
|
||||||
|
const { default: SignaturePad } = await import('signature_pad')
|
||||||
|
|
||||||
|
if (this.$refs.canvas) {
|
||||||
|
this.pad = new SignaturePad(this.$refs.canvas)
|
||||||
|
|
||||||
|
this.pad.addEventListener('beginStroke', () => {
|
||||||
|
this.isInitialsStarted = true
|
||||||
|
|
||||||
|
this.$emit('start')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
cropCanvasAndExportToPNG: SignatureStep.methods.cropCanvasAndExportToPNG,
|
||||||
|
remove () {
|
||||||
|
this.$emit('update:model-value', '')
|
||||||
|
},
|
||||||
|
clear () {
|
||||||
|
this.pad.clear()
|
||||||
|
|
||||||
|
this.isInitialsStarted = false
|
||||||
|
|
||||||
|
if (this.$refs.textInput) {
|
||||||
|
this.$refs.textInput.value = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateWrittenInitials (e) {
|
||||||
|
this.isInitialsStarted = true
|
||||||
|
|
||||||
|
const canvas = this.$refs.canvas
|
||||||
|
const context = canvas.getContext('2d')
|
||||||
|
|
||||||
|
const fontFamily = 'Arial'
|
||||||
|
const fontSize = '44px'
|
||||||
|
const fontStyle = 'italic'
|
||||||
|
const fontWeight = ''
|
||||||
|
|
||||||
|
context.font = fontStyle + ' ' + fontWeight + ' ' + fontSize + ' ' + fontFamily
|
||||||
|
context.textAlign = 'center'
|
||||||
|
|
||||||
|
context.clearRect(0, 0, canvas.width, canvas.height)
|
||||||
|
context.fillText(e.target.value, canvas.width / 2, canvas.height / 2 + 11)
|
||||||
|
},
|
||||||
|
toggleTextInput () {
|
||||||
|
this.remove()
|
||||||
|
this.clear()
|
||||||
|
this.isDrawInitials = !this.isDrawInitials
|
||||||
|
|
||||||
|
if (!this.isDrawInitials) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.textInput.focus()
|
||||||
|
|
||||||
|
this.$emit('start')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async submit () {
|
||||||
|
if (this.modelValue) {
|
||||||
|
return Promise.resolve({})
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.cropCanvasAndExportToPNG(this.$refs.canvas).then(async (blob) => {
|
||||||
|
const file = new File([blob], 'initials.png', { type: 'image/png' })
|
||||||
|
|
||||||
|
if (this.isDirectUpload) {
|
||||||
|
const { DirectUpload } = await import('@rails/activestorage')
|
||||||
|
|
||||||
|
new DirectUpload(
|
||||||
|
file,
|
||||||
|
'/direct_uploads'
|
||||||
|
).create((_error, data) => {
|
||||||
|
fetch(this.baseUrl + '/api/attachments', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
submitter_slug: this.submitterSlug,
|
||||||
|
blob_signed_id: data.signed_id,
|
||||||
|
name: 'attachments'
|
||||||
|
}),
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
}).then((resp) => resp.json()).then((attachment) => {
|
||||||
|
this.$emit('update:model-value', attachment.uuid)
|
||||||
|
this.$emit('attached', attachment)
|
||||||
|
|
||||||
|
return resolve(attachment)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
formData.append('file', file)
|
||||||
|
formData.append('submitter_slug', this.submitterSlug)
|
||||||
|
formData.append('name', 'attachments')
|
||||||
|
|
||||||
|
return fetch(this.baseUrl + '/api/attachments', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}).then((resp) => resp.json()).then((attachment) => {
|
||||||
|
this.$emit('attached', attachment)
|
||||||
|
this.$emit('update:model-value', attachment.uuid)
|
||||||
|
|
||||||
|
return resolve(attachment)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="flex justify-between items-center w-full mb-2">
|
<div class="flex justify-between items-center w-full mb-2">
|
||||||
<label
|
<label
|
||||||
class="label text-2xl"
|
class="label text-2xl"
|
||||||
>{{ field.name || 'Signature' }}</label>
|
>{{ field.name || t('signature') }}</label>
|
||||||
<div class="space-x-2 flex">
|
<div class="space-x-2 flex">
|
||||||
<span
|
<span
|
||||||
class="tooltip"
|
class="tooltip"
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
@click.prevent="remove"
|
@click.prevent="remove"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload :width="16" />
|
||||||
Redraw
|
{{ t('redraw') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
@click.prevent="clear"
|
@click.prevent="clear"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload :width="16" />
|
||||||
Clear
|
{{ t('clear') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
title="Minimize"
|
title="Minimize"
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
ref="textInput"
|
ref="textInput"
|
||||||
class="base-input !text-2xl w-full mt-6"
|
class="base-input !text-2xl w-full mt-6"
|
||||||
:required="field.required"
|
:required="field.required"
|
||||||
:placeholder="`Type signature here...`"
|
:placeholder="`${t('type_signature_here')}...`"
|
||||||
type="text"
|
type="text"
|
||||||
@input="updateWrittenSignature"
|
@input="updateWrittenSignature"
|
||||||
>
|
>
|
||||||
@@ -101,7 +101,7 @@ export default {
|
|||||||
IconTextSize,
|
IconTextSize,
|
||||||
IconArrowsDiagonalMinimize2
|
IconArrowsDiagonalMinimize2
|
||||||
},
|
},
|
||||||
inject: ['baseUrl'],
|
inject: ['baseUrl', 't'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -136,8 +136,10 @@ export default {
|
|||||||
},
|
},
|
||||||
async mounted () {
|
async mounted () {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.canvas.width = this.$refs.canvas.parentNode.clientWidth
|
if (this.$refs.canvas) {
|
||||||
this.$refs.canvas.height = this.$refs.canvas.parentNode.clientWidth / 3
|
this.$refs.canvas.width = this.$refs.canvas.parentNode.clientWidth
|
||||||
|
this.$refs.canvas.height = this.$refs.canvas.parentNode.clientWidth / 3
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (this.isDirectUpload) {
|
if (this.isDirectUpload) {
|
||||||
@@ -146,13 +148,15 @@ export default {
|
|||||||
|
|
||||||
const { default: SignaturePad } = await import('signature_pad')
|
const { default: SignaturePad } = await import('signature_pad')
|
||||||
|
|
||||||
this.pad = new SignaturePad(this.$refs.canvas)
|
if (this.$refs.canvas) {
|
||||||
|
this.pad = new SignaturePad(this.$refs.canvas)
|
||||||
|
|
||||||
this.pad.addEventListener('beginStroke', () => {
|
this.pad.addEventListener('beginStroke', () => {
|
||||||
this.isSignatureStarted = true
|
this.isSignatureStarted = true
|
||||||
|
|
||||||
this.$emit('start')
|
this.$emit('start')
|
||||||
})
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
remove () {
|
remove () {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<a
|
<a
|
||||||
:href="`/templates/${template.id}/submissions/new`"
|
:href="`/templates/${template.id}/submissions/new`"
|
||||||
data-turbo-frame="modal"
|
data-turbo-frame="modal"
|
||||||
class="btn btn-primary"
|
class="btn btn-primary text-base"
|
||||||
>
|
>
|
||||||
<IconUsersPlus
|
<IconUsersPlus
|
||||||
width="20"
|
width="20"
|
||||||
@@ -46,12 +46,12 @@
|
|||||||
>
|
>
|
||||||
<IconInnerShadowTop
|
<IconInnerShadowTop
|
||||||
v-if="isSaving"
|
v-if="isSaving"
|
||||||
width="20"
|
width="22"
|
||||||
class="animate-spin"
|
class="animate-spin"
|
||||||
/>
|
/>
|
||||||
<IconDeviceFloppy
|
<IconDeviceFloppy
|
||||||
v-else
|
v-else
|
||||||
width="20"
|
width="22"
|
||||||
/>
|
/>
|
||||||
<span class="hidden md:inline">
|
<span class="hidden md:inline">
|
||||||
Save
|
Save
|
||||||
@@ -473,6 +473,11 @@ export default {
|
|||||||
w: area.maskW / 5 / area.maskW,
|
w: area.maskW / 5 / area.maskW,
|
||||||
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH) / 2
|
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH) / 2
|
||||||
}
|
}
|
||||||
|
} else if (this.dragFieldType === 'initials') {
|
||||||
|
baseArea = {
|
||||||
|
w: area.maskW / 10 / area.maskW,
|
||||||
|
h: area.maskW / 35 / area.maskW
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
baseArea = {
|
baseArea = {
|
||||||
w: area.maskW / 5 / area.maskW,
|
w: area.maskW / 5 / area.maskW,
|
||||||
|
|||||||
@@ -99,6 +99,19 @@
|
|||||||
Draw New Area
|
Draw New Area
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li v-if="field.areas?.length === 1 && ['date', 'signature', 'initials', 'text', 'cells'].includes(field.type)">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-sm py-1 px-2"
|
||||||
|
@click.prevent="copyToAllPages(field)"
|
||||||
|
>
|
||||||
|
<IconCopy
|
||||||
|
:width="20"
|
||||||
|
:stroke-width="1.6"
|
||||||
|
/>
|
||||||
|
Copy to All Pages
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
@@ -183,7 +196,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import Contenteditable from './contenteditable'
|
import Contenteditable from './contenteditable'
|
||||||
import FieldType from './field_type'
|
import FieldType from './field_type'
|
||||||
import { IconShape, IconNewSection, IconTrashX } from '@tabler/icons-vue'
|
import { IconShape, IconNewSection, IconTrashX, IconCopy } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TemplateField',
|
name: 'TemplateField',
|
||||||
@@ -192,6 +205,7 @@ export default {
|
|||||||
IconShape,
|
IconShape,
|
||||||
IconNewSection,
|
IconNewSection,
|
||||||
IconTrashX,
|
IconTrashX,
|
||||||
|
IconCopy,
|
||||||
FieldType
|
FieldType
|
||||||
},
|
},
|
||||||
inject: ['template', 'save'],
|
inject: ['template', 'save'],
|
||||||
@@ -221,6 +235,23 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
copyToAllPages (field) {
|
||||||
|
const areaString = JSON.stringify(field.areas[0])
|
||||||
|
|
||||||
|
this.template.documents.forEach((attachment) => {
|
||||||
|
attachment.preview_images.forEach((page) => {
|
||||||
|
if (!field.areas.find((area) => area.attachment_uuid === attachment.uuid && area.page === parseInt(page.filename))) {
|
||||||
|
field.areas.push({ ...JSON.parse(areaString), page: parseInt(page.filename) })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$emit('scroll-to', this.field.areas[this.field.areas.length - 1])
|
||||||
|
})
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
},
|
||||||
onNameFocus (e) {
|
onNameFocus (e) {
|
||||||
this.isNameFocus = true
|
this.isNameFocus = true
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconColumns3, IconPhoneCheck } from '@tabler/icons-vue'
|
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconColumns3, IconPhoneCheck, IconLetterCaseUpper } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FiledTypeDropdown',
|
name: 'FiledTypeDropdown',
|
||||||
@@ -75,6 +75,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
text: 'Text',
|
text: 'Text',
|
||||||
signature: 'Signature',
|
signature: 'Signature',
|
||||||
|
initials: 'Initials',
|
||||||
date: 'Date',
|
date: 'Date',
|
||||||
image: 'Image',
|
image: 'Image',
|
||||||
file: 'File',
|
file: 'File',
|
||||||
@@ -90,14 +91,15 @@ export default {
|
|||||||
return {
|
return {
|
||||||
text: IconTextSize,
|
text: IconTextSize,
|
||||||
signature: IconWritingSign,
|
signature: IconWritingSign,
|
||||||
|
initials: IconLetterCaseUpper,
|
||||||
date: IconCalendarEvent,
|
date: IconCalendarEvent,
|
||||||
image: IconPhoto,
|
image: IconPhoto,
|
||||||
file: IconPaperclip,
|
file: IconPaperclip,
|
||||||
select: IconSelect,
|
select: IconSelect,
|
||||||
checkbox: IconCheckbox,
|
checkbox: IconCheckbox,
|
||||||
cells: IconColumns3,
|
|
||||||
multiple: IconChecks,
|
|
||||||
radio: IconCircleDot,
|
radio: IconCircleDot,
|
||||||
|
multiple: IconChecks,
|
||||||
|
cells: IconColumns3,
|
||||||
phone: IconPhoneCheck
|
phone: IconPhoneCheck
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="tooltip flex"
|
class="tooltip tooltip-bottom-end flex"
|
||||||
data-tip="Unlock SMS-verified phone number field with paid plan. Use text field for phone numbers without verification."
|
data-tip="Unlock SMS-verified phone number field with paid plan. Use text field for phone numbers without verification."
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="flex absolute text-[1.5vw] lg:text-base" style="width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%">
|
<div class="flex absolute text-[1.5vw] lg:text-base" style="width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%">
|
||||||
<% if field['type'].in?(['signature', 'image']) %>
|
<% if field['type'].in?(['signature', 'image', 'initials']) %>
|
||||||
<img class="object-contain mx-auto" src="<%= attachments_index[value].url %>" loading="lazy">
|
<img class="object-contain mx-auto" src="<%= attachments_index[value].url %>" loading="lazy">
|
||||||
<% elsif field['type'] == 'file' %>
|
<% elsif field['type'] == 'file' %>
|
||||||
<div class="px-0.5 flex flex-col justify-center">
|
<div class="px-0.5 flex flex-col justify-center">
|
||||||
|
|||||||
@@ -136,8 +136,8 @@
|
|||||||
<%= field['name'].presence || "#{field['type'].titleize} Field #{submitter_field_counters[field['type']]}" %>
|
<%= field['name'].presence || "#{field['type'].titleize} Field #{submitter_field_counters[field['type']]}" %>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<% if field['type'] == 'signature' %>
|
<% if field['type'].in?(%w[signature initials]) %>
|
||||||
<div class="w-full bg-base-300">
|
<div class="w-full bg-base-300 py-1">
|
||||||
<img class="object-contain mx-auto" height="<%= attachments_index[value].metadata['height'] %>" width="<%= attachments_index[value].metadata['width'] %>" src="<%= attachments_index[value].url %>" loading="lazy">
|
<img class="object-contain mx-auto" height="<%= attachments_index[value].metadata['height'] %>" width="<%= attachments_index[value].metadata['width'] %>" src="<%= attachments_index[value].url %>" loading="lazy">
|
||||||
</div>
|
</div>
|
||||||
<% elsif field['type'] == 'image' %>
|
<% elsif field['type'] == 'image' %>
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ module Submissions
|
|||||||
}
|
}
|
||||||
].compact_blank, line_spacing: 1.8, padding: [0, 0, 5, 0]
|
].compact_blank, line_spacing: 1.8, padding: [0, 0, 5, 0]
|
||||||
),
|
),
|
||||||
if field['type'].in?(%w[image signature])
|
if field['type'].in?(%w[image signature initials])
|
||||||
attachment = submitter.attachments.find { |a| a.uuid == value }
|
attachment = submitter.attachments.find { |a| a.uuid == value }
|
||||||
image = Vips::Image.new_from_buffer(attachment.download, '').autorot
|
image = Vips::Image.new_from_buffer(attachment.download, '').autorot
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ module Submissions
|
|||||||
|
|
||||||
io = StringIO.new(image.resize([scale, 1].min).write_to_buffer('.png'))
|
io = StringIO.new(image.resize([scale, 1].min).write_to_buffer('.png'))
|
||||||
|
|
||||||
column.image(io, padding: [0, 100, 10, 0])
|
column.image(io, padding: [0, field['type'] == 'initials' ? 200 : 100, 10, 0])
|
||||||
column.formatted_text_box([{ text: '' }])
|
column.formatted_text_box([{ text: '' }])
|
||||||
elsif field['type'] == 'file'
|
elsif field['type'] == 'file'
|
||||||
column.formatted_text_box(
|
column.formatted_text_box(
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ module Submissions
|
|||||||
canvas.font(FONT_NAME, size: font_size)
|
canvas.font(FONT_NAME, size: font_size)
|
||||||
|
|
||||||
case field['type']
|
case field['type']
|
||||||
when 'image', 'signature'
|
when 'image', 'signature', 'initials'
|
||||||
attachment = submitter.attachments.find { |a| a.uuid == value }
|
attachment = submitter.attachments.find { |a| a.uuid == value }
|
||||||
|
|
||||||
image = Vips::Image.new_from_buffer(attachment.download, '').autorot
|
image = Vips::Image.new_from_buffer(attachment.download, '').autorot
|
||||||
|
|||||||
Reference in New Issue
Block a user