add initials field
This commit is contained in:
@@ -74,3 +74,20 @@ button[disabled] .enabled {
|
||||
.base-select {
|
||||
@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"
|
||||
:src="signature.url"
|
||||
>
|
||||
<img
|
||||
v-else-if="field.type === 'initials' && initials"
|
||||
class="object-contain mx-auto"
|
||||
:src="initials.url"
|
||||
>
|
||||
<div
|
||||
v-else-if="field.type === 'file'"
|
||||
class="px-0.5 flex flex-col justify-center"
|
||||
@@ -113,7 +118,7 @@
|
||||
</template>
|
||||
|
||||
<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 {
|
||||
name: 'FieldArea',
|
||||
@@ -175,6 +180,7 @@ export default {
|
||||
signature: 'Signature',
|
||||
date: 'Date',
|
||||
image: 'Image',
|
||||
initials: 'Initials',
|
||||
file: 'File',
|
||||
select: 'Select',
|
||||
checkbox: 'Checkbox',
|
||||
@@ -189,6 +195,7 @@ export default {
|
||||
signature: IconWritingSign,
|
||||
date: IconCalendarEvent,
|
||||
image: IconPhoto,
|
||||
initials: IconLetterCaseUpper,
|
||||
file: IconPaperclip,
|
||||
select: IconSelect,
|
||||
checkbox: IconCheckbox,
|
||||
@@ -212,6 +219,13 @@ export default {
|
||||
return null
|
||||
}
|
||||
},
|
||||
initials () {
|
||||
if (this.field.type === 'initials') {
|
||||
return this.attachmentsIndex[this.modelValue]
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
formattedDate () {
|
||||
if (this.field.type === '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)"
|
||||
@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
|
||||
v-else-if="currentField.type === 'file'"
|
||||
v-model="values[currentField.uuid]"
|
||||
@@ -328,6 +341,7 @@
|
||||
import FieldAreas from './areas'
|
||||
import ImageStep from './image_step'
|
||||
import SignatureStep from './signature_step'
|
||||
import InitialsStep from './initials_step'
|
||||
import AttachmentStep from './attachment_step'
|
||||
import MultiSelectStep from './multi_select_step'
|
||||
import PhoneStep from './phone_step'
|
||||
@@ -342,6 +356,7 @@ export default {
|
||||
ImageStep,
|
||||
SignatureStep,
|
||||
AttachmentStep,
|
||||
InitialsStep,
|
||||
MultiSelectStep,
|
||||
IconInnerShadowTop,
|
||||
IconArrowsDiagonal,
|
||||
@@ -439,7 +454,8 @@ export default {
|
||||
if (this.recalculateButtonDisabledKey) {
|
||||
return this.isSubmitting ||
|
||||
(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 {
|
||||
return false
|
||||
}
|
||||
@@ -566,7 +582,7 @@ export default {
|
||||
async submitStep () {
|
||||
this.isSubmitting = true
|
||||
|
||||
const stepPromise = ['signature', 'phone'].includes(this.currentField.type)
|
||||
const stepPromise = ['signature', 'phone', 'initials'].includes(this.currentField.type)
|
||||
? this.$refs.currentStep.submit
|
||||
: () => Promise.resolve({})
|
||||
|
||||
|
||||
@@ -10,6 +10,13 @@ const en = {
|
||||
or_drag_and_drop_files: 'or drag and drop files',
|
||||
send_copy_via_email: 'Send copy via email',
|
||||
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!',
|
||||
create_a_free_account: 'Create a Free Account',
|
||||
signed_with: 'Signed with',
|
||||
@@ -36,6 +43,13 @@ const es = {
|
||||
or_drag_and_drop_files: 'o arrastra y suelta archivos',
|
||||
send_copy_via_email: 'Enviar copia por correo electrónico',
|
||||
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!',
|
||||
create_a_free_account: 'Crear una Cuenta Gratuita',
|
||||
signed_with: 'Firmado con',
|
||||
@@ -62,6 +76,13 @@ const it = {
|
||||
or_drag_and_drop_files: 'oppure trascina e rilascia i file',
|
||||
send_copy_via_email: 'Invia copia via email',
|
||||
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!',
|
||||
create_a_free_account: 'Crea un Account Gratuito',
|
||||
signed_with: 'Firmato con',
|
||||
@@ -88,6 +109,13 @@ const de = {
|
||||
or_drag_and_drop_files: 'oder Dateien hierher ziehen und ablegen',
|
||||
send_copy_via_email: 'Kopie per E-Mail senden',
|
||||
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!',
|
||||
create_a_free_account: 'Kostenloses Konto erstellen',
|
||||
signed_with: 'Unterschrieben mit',
|
||||
@@ -114,6 +142,13 @@ const fr = {
|
||||
or_drag_and_drop_files: 'ou faites glisser-déposer les fichiers',
|
||||
send_copy_via_email: 'Envoyer une copie par e-mail',
|
||||
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é !',
|
||||
create_a_free_account: 'Créer un Compte Gratuit',
|
||||
signed_with: 'Signé avec',
|
||||
@@ -140,6 +175,13 @@ const pl = {
|
||||
or_drag_and_drop_files: 'lub przeciągnij i upuść pliki',
|
||||
send_copy_via_email: 'Wyślij kopię drogą mailową',
|
||||
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!',
|
||||
create_a_free_account: 'Utwórz darmowe konto',
|
||||
signed_with: 'Podpisane za pomocą',
|
||||
@@ -166,6 +208,13 @@ const uk = {
|
||||
or_drag_and_drop_files: 'або перетягніть файли сюди',
|
||||
send_copy_via_email: 'Надіслати копію електронною поштою',
|
||||
download: 'Завантажити',
|
||||
signature: 'Підпис',
|
||||
initials: 'Ініціали',
|
||||
clear: 'Очистити',
|
||||
redraw: 'Перемалювати',
|
||||
draw_initials: 'Намалювати ініціали',
|
||||
type_signature_here: 'Введіть підпис тут',
|
||||
type_initial_here: 'Введіть ініціали тут',
|
||||
form_has_been_completed: 'Форму заповнено!',
|
||||
create_a_free_account: 'Створити безкоштовний обліковий запис',
|
||||
signed_with: 'Підписано за допомогою',
|
||||
@@ -192,6 +241,13 @@ const cs = {
|
||||
or_drag_and_drop_files: 'nebo přetáhněte soubory sem',
|
||||
send_copy_via_email: 'Odeslat kopii e-mailem',
|
||||
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!',
|
||||
create_a_free_account: 'Vytvořit bezplatný účet',
|
||||
signed_with: 'Podepsáno pomocí',
|
||||
@@ -218,6 +274,13 @@ const pt = {
|
||||
or_drag_and_drop_files: 'ou arraste e solte arquivos',
|
||||
send_copy_via_email: 'Enviar cópia por e-mail',
|
||||
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!',
|
||||
create_a_free_account: 'Criar uma Conta Gratuita',
|
||||
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">
|
||||
<label
|
||||
class="label text-2xl"
|
||||
>{{ field.name || 'Signature' }}</label>
|
||||
>{{ field.name || t('signature') }}</label>
|
||||
<div class="space-x-2 flex">
|
||||
<span
|
||||
class="tooltip"
|
||||
@@ -40,7 +40,7 @@
|
||||
@click.prevent="remove"
|
||||
>
|
||||
<IconReload :width="16" />
|
||||
Redraw
|
||||
{{ t('redraw') }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
@@ -48,7 +48,7 @@
|
||||
@click.prevent="clear"
|
||||
>
|
||||
<IconReload :width="16" />
|
||||
Clear
|
||||
{{ t('clear') }}
|
||||
</button>
|
||||
<button
|
||||
title="Minimize"
|
||||
@@ -83,7 +83,7 @@
|
||||
ref="textInput"
|
||||
class="base-input !text-2xl w-full mt-6"
|
||||
:required="field.required"
|
||||
:placeholder="`Type signature here...`"
|
||||
:placeholder="`${t('type_signature_here')}...`"
|
||||
type="text"
|
||||
@input="updateWrittenSignature"
|
||||
>
|
||||
@@ -101,7 +101,7 @@ export default {
|
||||
IconTextSize,
|
||||
IconArrowsDiagonalMinimize2
|
||||
},
|
||||
inject: ['baseUrl'],
|
||||
inject: ['baseUrl', 't'],
|
||||
props: {
|
||||
field: {
|
||||
type: Object,
|
||||
@@ -136,8 +136,10 @@ export default {
|
||||
},
|
||||
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 / 3
|
||||
}
|
||||
})
|
||||
|
||||
if (this.isDirectUpload) {
|
||||
@@ -146,6 +148,7 @@ export default {
|
||||
|
||||
const { default: SignaturePad } = await import('signature_pad')
|
||||
|
||||
if (this.$refs.canvas) {
|
||||
this.pad = new SignaturePad(this.$refs.canvas)
|
||||
|
||||
this.pad.addEventListener('beginStroke', () => {
|
||||
@@ -153,6 +156,7 @@ export default {
|
||||
|
||||
this.$emit('start')
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
remove () {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<a
|
||||
:href="`/templates/${template.id}/submissions/new`"
|
||||
data-turbo-frame="modal"
|
||||
class="btn btn-primary"
|
||||
class="btn btn-primary text-base"
|
||||
>
|
||||
<IconUsersPlus
|
||||
width="20"
|
||||
@@ -46,12 +46,12 @@
|
||||
>
|
||||
<IconInnerShadowTop
|
||||
v-if="isSaving"
|
||||
width="20"
|
||||
width="22"
|
||||
class="animate-spin"
|
||||
/>
|
||||
<IconDeviceFloppy
|
||||
v-else
|
||||
width="20"
|
||||
width="22"
|
||||
/>
|
||||
<span class="hidden md:inline">
|
||||
Save
|
||||
@@ -473,6 +473,11 @@ export default {
|
||||
w: area.maskW / 5 / area.maskW,
|
||||
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 {
|
||||
baseArea = {
|
||||
w: area.maskW / 5 / area.maskW,
|
||||
|
||||
@@ -99,6 +99,19 @@
|
||||
Draw New Area
|
||||
</a>
|
||||
</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>
|
||||
</span>
|
||||
<button
|
||||
@@ -183,7 +196,7 @@
|
||||
<script>
|
||||
import Contenteditable from './contenteditable'
|
||||
import FieldType from './field_type'
|
||||
import { IconShape, IconNewSection, IconTrashX } from '@tabler/icons-vue'
|
||||
import { IconShape, IconNewSection, IconTrashX, IconCopy } from '@tabler/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'TemplateField',
|
||||
@@ -192,6 +205,7 @@ export default {
|
||||
IconShape,
|
||||
IconNewSection,
|
||||
IconTrashX,
|
||||
IconCopy,
|
||||
FieldType
|
||||
},
|
||||
inject: ['template', 'save'],
|
||||
@@ -221,6 +235,23 @@ export default {
|
||||
}
|
||||
},
|
||||
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) {
|
||||
this.isNameFocus = true
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
</template>
|
||||
|
||||
<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 {
|
||||
name: 'FiledTypeDropdown',
|
||||
@@ -75,6 +75,7 @@ export default {
|
||||
return {
|
||||
text: 'Text',
|
||||
signature: 'Signature',
|
||||
initials: 'Initials',
|
||||
date: 'Date',
|
||||
image: 'Image',
|
||||
file: 'File',
|
||||
@@ -90,14 +91,15 @@ export default {
|
||||
return {
|
||||
text: IconTextSize,
|
||||
signature: IconWritingSign,
|
||||
initials: IconLetterCaseUpper,
|
||||
date: IconCalendarEvent,
|
||||
image: IconPhoto,
|
||||
file: IconPaperclip,
|
||||
select: IconSelect,
|
||||
checkbox: IconCheckbox,
|
||||
cells: IconColumns3,
|
||||
multiple: IconChecks,
|
||||
radio: IconCircleDot,
|
||||
multiple: IconChecks,
|
||||
cells: IconColumns3,
|
||||
phone: IconPhoneCheck
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
</button>
|
||||
<div
|
||||
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."
|
||||
>
|
||||
<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 %>%">
|
||||
<% 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">
|
||||
<% elsif field['type'] == 'file' %>
|
||||
<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']]}" %>
|
||||
</div>
|
||||
<div>
|
||||
<% if field['type'] == 'signature' %>
|
||||
<div class="w-full bg-base-300">
|
||||
<% if field['type'].in?(%w[signature initials]) %>
|
||||
<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">
|
||||
</div>
|
||||
<% elsif field['type'] == 'image' %>
|
||||
|
||||
@@ -192,7 +192,7 @@ module Submissions
|
||||
}
|
||||
].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 }
|
||||
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'))
|
||||
|
||||
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: '' }])
|
||||
elsif field['type'] == 'file'
|
||||
column.formatted_text_box(
|
||||
|
||||
@@ -58,7 +58,7 @@ module Submissions
|
||||
canvas.font(FONT_NAME, size: font_size)
|
||||
|
||||
case field['type']
|
||||
when 'image', 'signature'
|
||||
when 'image', 'signature', 'initials'
|
||||
attachment = submitter.attachments.find { |a| a.uuid == value }
|
||||
|
||||
image = Vips::Image.new_from_buffer(attachment.download, '').autorot
|
||||
|
||||
Reference in New Issue
Block a user