prefill my signature

pull/150/merge^2
iozeey 2 years ago
parent 1bfb35f560
commit b2c051dc4b

@ -6,8 +6,7 @@ module Api
skip_authorization_check skip_authorization_check
def create def create
submitter = Submitter.find_by!(slug: params[:submitter_slug]) submitter = Template.find_by(slug: params[:template].to_unsafe_h[:slug]) || Submitter.find_by!(slug: params[:submitter_slug])
attachment = Submitters.create_attachment!(submitter, params) attachment = Submitters.create_attachment!(submitter, params)
render json: attachment.as_json(only: %i[uuid], methods: %i[url filename content_type]) render json: attachment.as_json(only: %i[uuid], methods: %i[url filename content_type])

@ -50,7 +50,7 @@
@click="selectedAreaRef.value = area" @click="selectedAreaRef.value = area"
/> />
<FieldType <FieldType
v-if="(field.type !== 'my_text')" v-if="!['my_text', 'my_signature'].includes(field.type)"
v-model="field.type" v-model="field.type"
:button-width="27" :button-width="27"
:editable="editable" :editable="editable"
@ -129,6 +129,20 @@
@input="makeMyText" @input="makeMyText"
/> />
</div> </div>
<div
v-else-if="field.type === 'my_signature'"
class="flex items-center justify-center h-full w-full"
style="background-color: white;"
>
<img
:id="field.uuid"
:src="field.url"
alt="please sign ..."
class="d-flex justify-center w-full h-full"
style="z-index: 50;"
@click="handleMySignatureClick"
>
</div>
<div <div
v-else v-else
class="flex items-center h-full w-full" class="flex items-center h-full w-full"
@ -170,6 +184,29 @@
@touchstart="startTouchResize" @touchstart="startTouchResize"
/> />
</div> </div>
<div
v-if="showMySignature"
>
<!--
<MySignature
:key="field.uuid"
v-model="values[field.uuid]"
:style="mySignatureStyle"
:field="field"
:previous-value="previousSignatureValue"
:is-direct-upload="isDirectUpload"
:attachments-index="attachmentsIndex"
@attached="attachments.push($event)"
@remove="showMySignature = false"
/>
-->
<MySignature
:key="field.uuid"
:style="mySignatureStyle"
:field="field"
:template="template"
/>
</div>
</template> </template>
<script> <script>
@ -178,6 +215,7 @@ import FieldType from './field_type'
import Field from './field' import Field from './field'
import { IconX, IconWriting } from '@tabler/icons-vue' import { IconX, IconWriting } from '@tabler/icons-vue'
import { v4 } from 'uuid' import { v4 } from 'uuid'
import MySignature from './my_signature'
export default { export default {
name: 'FieldArea', name: 'FieldArea',
@ -185,7 +223,8 @@ export default {
FieldType, FieldType,
FieldSubmitter, FieldSubmitter,
IconX, IconX,
IconWriting IconWriting,
MySignature
}, },
inject: ['template', 'selectedAreaRef', 'save'], inject: ['template', 'selectedAreaRef', 'save'],
props: { props: {
@ -198,6 +237,16 @@ export default {
required: false, required: false,
default: false default: false
}, },
// modelValue: {
// type: [Array, String, Number, Object, Boolean],
// required: false,
// default: ''
// },
// attachmentsIndex: {
// type: Object,
// required: false,
// default: () => ({})
// },
editable: { editable: {
type: Boolean, type: Boolean,
required: false, required: false,
@ -217,7 +266,8 @@ export default {
isNameFocus: false, isNameFocus: false,
myLocalText: '', myLocalText: '',
textOverflowChars: 0, textOverflowChars: 0,
dragFrom: { x: 0, y: 0 } dragFrom: { x: 0, y: 0 },
showMySignature: false
} }
}, },
computed: { computed: {
@ -231,6 +281,23 @@ export default {
return '' return ''
} }
}, },
mySignatureStyle () {
const { x, y, w, h } = this.area
return {
top: (y * 100) + 7 + '%',
left: (x * 100) - 10 + '%',
width: w * 100 + '%',
height: h * 100 + '%'
}
},
// my_signature () {
// if (this.field.type === 'my_signature') {
// return this.attachmentsIndex[this.modelValue]
// } else {
// return null
// }
// },
cells () { cells () {
const cells = [] const cells = []
@ -322,6 +389,11 @@ export default {
{ [this.field.uuid]: e.target.value } { [this.field.uuid]: e.target.value }
) )
}, },
makeMySignature (e) {
this.sendSaveText(
{ [this.field.uuid]: e.attachment.uuid }
)
},
sendSaveText (event) { sendSaveText (event) {
this.$emit('update:myText', event) this.$emit('update:myText', event)
}, },
@ -522,6 +594,10 @@ export default {
this.$emit('stop-resize') this.$emit('stop-resize')
this.save() this.save()
},
handleMySignatureClick () {
console.log('my-signature event triggered with field:')
this.showMySignature = !this.showMySignature
} }
} }
} }

@ -253,6 +253,7 @@ import Contenteditable from './contenteditable'
import DocumentPreview from './preview' import DocumentPreview from './preview'
import DocumentControls from './controls' import DocumentControls from './controls'
import FieldType from './field_type' import FieldType from './field_type'
import { t } from './i18n'
import { IconUsersPlus, IconDeviceFloppy, IconInnerShadowTop, IconPlus, IconX } from '@tabler/icons-vue' import { IconUsersPlus, IconDeviceFloppy, IconInnerShadowTop, IconPlus, IconX } from '@tabler/icons-vue'
import { v4 } from 'uuid' import { v4 } from 'uuid'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
@ -283,7 +284,9 @@ export default {
baseFetch: this.baseFetch, baseFetch: this.baseFetch,
backgroundColor: this.backgroundColor, backgroundColor: this.backgroundColor,
withPhone: this.withPhone, withPhone: this.withPhone,
selectedAreaRef: computed(() => this.selectedAreaRef) selectedAreaRef: computed(() => this.selectedAreaRef),
baseUrl: this.baseUrl,
t: this.t
} }
}, },
props: { props: {
@ -427,6 +430,7 @@ export default {
this.documentRefs = [] this.documentRefs = []
}, },
methods: { methods: {
t,
updateMyText (values) { updateMyText (values) {
const existingValues = this.template.values || {} const existingValues = this.template.values || {}
const updatedValues = { ...existingValues, ...values } const updatedValues = { ...existingValues, ...values }

@ -0,0 +1,49 @@
function cropCanvasAndExportToPNG (canvas) {
const ctx = canvas.getContext('2d')
const width = canvas.width
const height = canvas.height
let topmost = height
let bottommost = 0
let leftmost = width
let rightmost = 0
const imageData = ctx.getImageData(0, 0, width, height)
const pixels = imageData.data
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const pixelIndex = (y * width + x) * 4
const alpha = pixels[pixelIndex + 3]
if (alpha !== 0) {
topmost = Math.min(topmost, y)
bottommost = Math.max(bottommost, y)
leftmost = Math.min(leftmost, x)
rightmost = Math.max(rightmost, x)
}
}
}
const croppedWidth = rightmost - leftmost + 1
const croppedHeight = bottommost - topmost + 1
const croppedCanvas = document.createElement('canvas')
croppedCanvas.width = croppedWidth
croppedCanvas.height = croppedHeight
const croppedCtx = croppedCanvas.getContext('2d')
croppedCtx.drawImage(canvas, leftmost, topmost, croppedWidth, croppedHeight, 0, 0, croppedWidth, croppedHeight)
return new Promise((resolve, reject) => {
croppedCanvas.toBlob((blob) => {
if (blob) {
resolve(blob)
} else {
reject(new Error('Failed to create a PNG blob.'))
}
}, 'image/png')
})
}
export { cropCanvasAndExportToPNG }

@ -93,7 +93,8 @@ export default {
cells: 'Cells', cells: 'Cells',
phone: 'Phone', phone: 'Phone',
redact: 'Redact', redact: 'Redact',
my_text: 'My_Text' my_text: 'My_Text',
my_signature: 'My Signature'
} }
}, },
fieldIcons () { fieldIcons () {
@ -111,7 +112,8 @@ export default {
radio: IconCircleDot, radio: IconCircleDot,
phone: IconPhoneCheck, phone: IconPhoneCheck,
redact: IconBarrierBlock, redact: IconBarrierBlock,
my_text: IconTextResize my_text: IconTextResize,
my_signature: IconWritingSign
} }
} }
}, },

@ -72,7 +72,7 @@
:key="type" :key="type"
> >
<div <div
v-if="!['redact', 'my_text'].includes(type)" v-if="!['redact', 'my_text', 'my_signature'].includes(type)"
> >
<button <button
v-if="withPhone || type != 'phone'" v-if="withPhone || type != 'phone'"
@ -131,7 +131,7 @@
:key="type" :key="type"
> >
<div <div
v-if="['redact', 'my_text'].includes(type)" v-if="['redact', 'my_text', 'my_signature'].includes(type)"
> >
<button <button
draggable="true" draggable="true"

@ -0,0 +1,404 @@
const en = {
submit_form: 'Submit Form',
type_here: 'Type here',
optional: 'optional',
select_your_option: 'Select your option',
complete_hightlighted_checkboxes_and_click: 'Complete hightlighted checkboxes and click',
submit: 'submit',
next: 'next',
click_to_upload: 'Click to upload',
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',
please_check_the_box_to_continue: 'Please check the box to continue',
open_source_documents_software: 'open source documents software',
verified_phone_number: 'Verify Phone Number',
redact: 'redact',
use_international_format: 'Use internatioanl format: +1xxx',
six_digits_code: '6-digit code',
change_phone_number: 'Change phone number',
sending: 'Sending...',
resend_code: 'Re-send code',
verification_code_has_been_resent: 'Verification code has been re-sent via SMS',
please_fill_all_required_fields: 'Please fill all required fields',
set_today: 'Set Today',
toggle_multiline_text: 'Toggle Multiline Text',
draw_signature: 'Draw signature',
type_initial: 'Type initials',
draw: 'Draw',
type: 'Type',
type_text: 'Type text',
date: 'Date',
email_has_been_sent: 'Email has been sent'
}
const es = {
submit_form: 'Enviar Formulario',
type_here: 'Escribe aquí',
optional: 'opcional',
select_your_option: 'Selecciona tu opción',
complete_hightlighted_checkboxes_and_click: 'Completa las casillas resaltadas y haz clic',
submit: 'enviar',
next: 'siguiente',
click_to_upload: 'Haz clic para cargar',
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',
please_check_the_box_to_continue: 'Por favor marque la casilla para continuar',
open_source_documents_software: 'software de documentos de código abierto',
verified_phone_number: 'Verificar número de teléfono',
redact: 'redact',
use_international_format: 'Usar formato internacional: +1xxx',
six_digits_code: 'Código de 6 dígitos',
change_phone_number: 'Cambiar número de teléfono',
sending: 'Enviando...',
resend_code: 'Reenviar código',
verification_code_has_been_resent: 'El código de verificación ha sido reenviado por SMS',
please_fill_all_required_fields: 'Por favor, complete todos los campos obligatorios',
set_today: 'Establecer Hoy',
date: 'Fecha',
toggle_multiline_text: 'Alternar Texto Multilínea',
draw_signature: 'Dibujar firma',
type_initial: 'Escribir iniciales',
draw: 'Dibujar',
type: 'Escribir',
type_text: 'Escribir texto',
email_has_been_sent: 'El correo electrónico ha sido enviado'
}
const it = {
submit_form: 'Invia Modulo',
type_here: 'Digita qui',
optional: 'opzionale',
select_your_option: 'Seleziona la tua opzione',
complete_hightlighted_checkboxes_and_click: 'Completa le caselle evidenziate e fai clic',
submit: 'invia',
next: 'avanti',
click_to_upload: 'Clicca per caricare',
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',
please_check_the_box_to_continue: 'Si prega di spuntare la casella per continuare',
open_source_documents_software: 'software di documenti open source',
verified_phone_number: 'Verifica numero di telefono',
redact: 'redact',
use_international_format: 'Usa formato internazionale: +1xxx',
six_digits_code: 'Codice a 6 cifre',
change_phone_number: 'Cambia numero di telefono',
sending: 'Invio in corso...',
resend_code: 'Rinvia codice',
verification_code_has_been_resent: 'Il codice di verifica è stato rinviato tramite SMS',
please_fill_all_required_fields: 'Si prega di compilare tutti i campi obbligatori',
set_today: 'Imposta Oggi',
date: 'Data',
draw_signature: 'Disegna firma',
type_initial: 'Inserisci iniziali',
draw: 'Disegna',
type: 'Inserisci',
type_text: 'Inserisci testo',
toggle_multiline_text: 'Attiva Testo Multilinea',
email_has_been_sent: "L'email è stata inviata"
}
const de = {
submit_form: 'Formular absenden',
type_here: 'Hier eingeben',
optional: 'optional',
select_your_option: 'Wähle deine Option',
complete_hightlighted_checkboxes_and_click: 'Markierte Kontrollkästchen ausfüllen und klicken',
submit: 'absenden',
next: 'weiter',
click_to_upload: 'Klicken zum Hochladen',
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',
please_check_the_box_to_continue: 'Bitte setzen Sie das Häkchen, um fortzufahren',
open_source_documents_software: 'Open-Source-Dokumentensoftware',
verified_phone_number: 'Telefonnummer überprüfen',
redact: 'redact',
use_international_format: 'Internationales Format verwenden: +1xxx',
six_digits_code: '6-stelliger Code',
change_phone_number: 'Telefonnummer ändern',
sending: 'Senden...',
resend_code: 'Code erneut senden',
verification_code_has_been_resent: 'Die Verifizierungscode wurde erneut per SMS gesendet',
please_fill_all_required_fields: 'Bitte füllen Sie alle erforderlichen Felder aus',
set_today: 'Heute einstellen',
date: 'Datum',
draw_signature: 'Unterschrift zeichnen',
type_initial: 'Initialen eingeben',
draw: 'Zeichnen',
type: 'Eingeben',
type_text: 'Text eingeben',
toggle_multiline_text: 'Mehrzeiligen Text umschalten',
email_has_been_sent: 'Die E-Mail wurde gesendet'
}
const fr = {
submit_form: 'Envoyer le Formulaire',
type_here: 'Tapez ici',
optional: 'facultatif',
select_your_option: 'Sélectionnez votre option',
complete_hightlighted_checkboxes_and_click: 'Complétez les cases à cocher en surbrillance et cliquez',
submit: 'envoyer',
next: 'suivant',
click_to_upload: 'Cliquez pour télécharger',
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',
please_check_the_box_to_continue: 'Veuillez cocher la case pour continuer',
open_source_documents_software: 'logiciel de documents open source',
verified_phone_number: 'Vérifier le numéro de téléphone',
redact: 'redact',
use_international_format: 'Utiliser le format international : +1xxx',
six_digits_code: 'Code à 6 chiffres',
change_phone_number: 'Changer le numéro de téléphone',
sending: 'Envoi en cours...',
resend_code: 'Renvoyer le code',
verification_code_has_been_resent: 'Le code de vérification a été renvoyé par SMS',
please_fill_all_required_fields: 'Veuillez remplir tous les champs obligatoires',
set_today: "Définir Aujourd'hui",
date: 'Date',
draw_signature: 'Dessiner une signature',
type_initial: 'Saisir les initiales',
draw: 'Dessiner',
type: 'Saisir',
type_text: 'Saisir du texte',
toggle_multiline_text: 'Basculer le Texte Multiligne',
email_has_been_sent: "L'email a été envoyé"
}
const pl = {
submit_form: 'Wyślij Formularz',
type_here: 'Wpisz tutaj',
optional: 'opcjonalny',
select_your_option: 'Wybierz swoją opcję',
complete_hightlighted_checkboxes_and_click: 'Wypełnij zaznaczone pola wyboru i kliknij',
submit: 'wyślij',
next: 'dalej',
click_to_upload: 'Kliknij, aby przesłać',
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ą',
please_check_the_box_to_continue: 'Proszę zaznaczyć pole, aby kontynuować',
open_source_documents_software: 'oprogramowanie do dokumentów open source',
verified_phone_number: 'Zweryfikuj numer telefonu',
redact: 'redact',
use_international_format: 'Użyj międzynarodowego formatu: +1xxx',
six_digits_code: '6-cyfrowy kod',
change_phone_number: 'Zmień numer telefonu',
sending: 'Wysyłanie...',
resend_code: 'Ponownie wyślij kod',
verification_code_has_been_resent: 'Kod weryfikacyjny został ponownie wysłany',
please_fill_all_required_fields: 'Proszę wypełnić wszystkie wymagane pola',
set_today: 'Ustaw Dziś',
date: 'Data',
draw_signature: 'Rysuj podpis',
type_initial: 'Wprowadź inicjały',
draw: 'Rysuj',
type: 'Wprowadź',
type_text: 'Wprowadź tekst',
toggle_multiline_text: 'Przełącz Tekst Wielolinijkowy',
email_has_been_sent: 'E-mail został wysłany'
}
const uk = {
submit_form: 'Надіслати Форму',
type_here: 'Введіть тут',
optional: 'необов’язково',
select_your_option: 'Виберіть варіант',
complete_hightlighted_checkboxes_and_click: 'Заповніть позначені прапорці та натисніть',
submit: 'надіслати',
next: 'далі',
click_to_upload: 'Клацніть, щоб завантажити',
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: 'Підписано за допомогою',
please_check_the_box_to_continue: 'Будь ласка, позначте прапорець, щоб продовжити',
open_source_documents_software: 'відкритий програмний засіб для документів',
verified_phone_number: 'Підтвердіть номер телефону',
redact: 'redact',
use_international_format: 'Використовуйте міжнародний формат: +1xxx',
six_digits_code: '6-значний код',
change_phone_number: 'Змінити номер телефону',
sending: 'Надсилаю...',
resend_code: 'Повторно відправити код',
verification_code_has_been_resent: 'Код підтвердження був повторно надісланий',
please_fill_all_required_fields: "Будь ласка, заповніть всі обов'язкові поля",
set_today: 'Задати Сьогодні',
date: 'Дата',
draw_signature: 'Намалюйте підпис',
type_initial: 'Введіть ініціали',
draw: 'Підпис',
type: 'Текст',
type_text: 'Введіть текст',
toggle_multiline_text: 'Перемкнути Багаторядковий Текст',
email_has_been_sent: 'Електронний лист був відправлений'
}
const cs = {
submit_form: 'Odeslat formulář',
type_here: 'Zadejte zde',
optional: 'volitelné',
select_your_option: 'Vyberte svou volbu',
complete_hightlighted_checkboxes_and_click: 'Označte zvýrazněné zaškrtávací políčka a klikněte na',
submit: 'odeslat',
next: 'další',
click_to_upload: 'Klikněte pro nahrání',
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í',
please_check_the_box_to_continue: 'Prosím, zaškrtněte políčko pro pokračování',
open_source_documents_software: 'open source software pro dokumenty',
verified_phone_number: 'Ověřte telefonní číslo',
redact: 'redact',
use_international_format: 'Použijte mezinárodní formát: +1xxx',
six_digits_code: '6-místný kód',
change_phone_number: 'Změnit telefonní číslo',
sending: 'Odesílání...',
resend_code: 'Znovu odeslat kód',
verification_code_has_been_resent: 'Ověřovací kód byl znovu odeslán',
please_fill_all_required_fields: 'Prosím vyplňte všechny povinné položky',
set_today: 'Nastavit Dnes',
date: 'Datum',
draw_signature: 'Nakreslit podpis',
type_initial: 'Zadat iniciály',
draw: 'Kreslit',
type: 'Zadat',
type_text: 'Zadat text',
toggle_multiline_text: 'Přepnout Víceřádkový Text',
email_has_been_sent: 'E-mail byl odeslán'
}
const pt = {
submit_form: 'Enviar Formulário',
type_here: 'Digite aqui',
optional: 'opcional',
select_your_option: 'Selecione sua opção',
complete_hightlighted_checkboxes_and_click: 'Complete as caixas de seleção destacadas e clique',
submit: 'enviar',
next: 'próximo',
click_to_upload: 'Clique para fazer o upload',
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',
please_check_the_box_to_continue: 'Por favor, marque a caixa para continuar',
open_source_documents_software: 'software de documentos de código aberto',
verified_phone_number: 'Verificar Número de Telefone',
redact: 'redact',
use_international_format: 'Use formato internacional: +1xxx',
six_digits_code: 'Código de 6 dígitos',
change_phone_number: 'Alterar número de telefone',
sending: 'Enviando...',
resend_code: 'Reenviar código',
verification_code_has_been_resent: 'O código de verificação foi reenviado via SMS',
please_fill_all_required_fields: 'Por favor, preencha todos os campos obrigatórios',
set_today: 'Definir Hoje',
date: 'Data',
draw_signature: 'Desenhar assinatura',
type_initial: 'Inserir iniciais',
draw: 'Desenhar',
type: 'Inserir',
type_text: 'Inserir texto',
toggle_multiline_text: 'Alternar Texto Multilinha',
email_has_been_sent: 'Email enviado'
}
const i18n = { en, es, it, de, fr, pl, uk, cs, pt }
const browserLanguage = (navigator.language || navigator.userLanguage || 'en').split('-')[0]
const t = (key) => i18n[browserLanguage]?.[key] || i18n.en[key] || key
export default i18n
export { t }

@ -0,0 +1,363 @@
<template>
<div
class="absolute"
style="z-index: 50;"
:style="{ ...mySignatureStyle }"
>
<div class="flex justify-between items-center w-full mb-2">
<label
class="label text-2xl"
>Signature</label>
<div class="space-x-2 flex">
<span
v-if="isTextSignature"
class="tooltip"
:data-tip="t('draw_signature')"
>
<a
id="type_text_button"
href="#"
class="btn btn-outline btn-sm font-medium"
@click.prevent="toggleTextInput"
>
<IconSignature :width="16" />
<span class="hidden sm:inline">
<!-- {{ t('draw') }} -->
</span>
</a>
</span>
<span
v-else
class="tooltip"
:data-tip="t('type_text')"
>
<a
id="type_text_button"
href="#"
class="btn btn-outline btn-sm font-medium"
@click.prevent="toggleTextInput"
>
<IconTextSize :width="16" />
<span class="hidden sm:inline">
<!-- {{ t }} -->
</span>
</a>
</span>
<span
class="tooltip"
data-tip="Take photo"
>
<label
class="btn btn-outline btn-sm font-medium"
>
<IconCamera :width="16" />
<input
type="file"
hidden
accept="image/*"
@change="drawImage"
>
<span class="hidden sm:inline">
<!-- {{ t }} -->
</span>
</label>
</span>
<a
v-if="modelValue || computedPreviousValue"
href="#"
class="btn btn-outline btn-sm font-medium"
@click.prevent="remove"
>
<IconReload :width="16" />
{{ t('redraw') }}
</a>
<a
v-else
href="#"
class="btn btn-outline btn-sm font-medium"
@click.prevent="clear"
>
<IconReload :width="16" />
</a>
<a
href="#"
title="Remove"
class="py-1.5 inline md:hidden"
@click.prevent="$emit('remove')"
>
<IconTrashX
:width="20"
:height="20"
/>
</a>
</div>
</div>
<input
:value="modelValue || computedPreviousValue"
type="hidden"
>
<img
v-if="modelValue || computedPreviousValue"
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
class="mx-auto bg-white border border-base-300 rounded max-h-72"
>
<canvas
v-show="!modelValue && !computedPreviousValue"
ref="canvas"
style="padding: 1px; 0"
class="bg-white border border-base-300 rounded-2xl"
/>
<input
v-if="isTextSignature"
id="signature_text_input"
ref="textInput"
class="base-input !text-2xl w-full mt-6"
:placeholder="`${t('type_signature_here')}...`"
type="text"
@input="updateWrittenSignature"
>
<button
class="btn btn-outline w-full mt-2"
@click="submit"
>
<span> Submit </span>
</button>
</div>
</template>
<script>
import { IconReload, IconCamera, IconSignature, IconTextSize, IconTrashX } from '@tabler/icons-vue'
import { cropCanvasAndExportToPNG } from './crop_canvas'
import SignaturePad from 'signature_pad'
let isFontLoaded = false
export default {
name: 'MySignature',
components: {
IconReload,
IconCamera,
IconTextSize,
IconSignature,
IconTrashX
},
inject: ['baseUrl', 't'],
props: {
field: {
type: Object,
required: true
},
isDirectUpload: {
type: Boolean,
required: true,
default: true
},
attachmentsIndex: {
type: Object,
required: false,
default: () => ({})
},
previousValue: {
type: String,
required: false,
default: ''
},
modelValue: {
type: String,
required: false,
default: ''
},
template: {
type: String,
required: true
}
},
emits: ['attached', 'update:model-value', 'start', 'remove'],
data () {
return {
isSignatureStarted: !!this.previousValue,
isUsePreviousValue: true,
isTextSignature: false
}
},
computed: {
computedPreviousValue () {
if (this.isUsePreviousValue) {
return this.previousValue
} else {
return null
}
}
},
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) {
import('@rails/activestorage')
}
if (this.$refs.canvas) {
this.pad = new SignaturePad(this.$refs.canvas)
this.pad.addEventListener('beginStroke', () => {
this.isSignatureStarted = true
this.$emit('start')
})
}
},
methods: {
remove () {
this.$emit('update:model-value', '')
this.isUsePreviousValue = false
this.isSignatureStarted = false
},
loadFont () {
if (!isFontLoaded) {
const font = new FontFace('Dancing Script', `url(${this.baseUrl}/fonts/DancingScript.otf) format("opentype")`)
font.load().then((loadedFont) => {
document.fonts.add(loadedFont)
isFontLoaded = true
}).catch((error) => {
console.error('Font loading failed:', error)
})
}
},
clear () {
this.pad.clear()
this.isSignatureStarted = false
if (this.$refs.textInput) {
this.$refs.textInput.value = ''
}
},
updateWrittenSignature (e) {
this.isSignatureStarted = true
const canvas = this.$refs.canvas
const context = canvas.getContext('2d')
const fontFamily = 'Dancing Script'
const fontSize = '38px'
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.isTextSignature = !this.isTextSignature
if (this.isTextSignature) {
this.$nextTick(() => {
this.$refs.textInput.focus()
this.loadFont()
this.$emit('start')
})
}
},
drawImage (event) {
this.remove()
this.isSignatureStarted = true
const file = event.target.files[0]
if (file && file.type.match('image.*')) {
const reader = new FileReader()
reader.onload = (event) => {
const img = new Image()
img.src = event.target.result
img.onload = () => {
const canvas = this.$refs.canvas
const context = canvas.getContext('2d')
const aspectRatio = img.width / img.height
let targetWidth = canvas.width
let targetHeight = canvas.height
if (canvas.width / canvas.height > aspectRatio) {
targetWidth = canvas.height * aspectRatio
} else {
targetHeight = canvas.width / aspectRatio
}
if (targetHeight > targetWidth) {
const scale = targetHeight / targetWidth
targetWidth = targetWidth * scale
targetHeight = targetHeight * scale
}
const x = (canvas.width - targetWidth) / 2
const y = (canvas.height - targetHeight) / 2
context.clearRect(0, 0, canvas.width, canvas.height)
context.drawImage(img, x, y, targetWidth, targetHeight)
this.$emit('start')
}
}
reader.readAsDataURL(file)
}
},
async submit () {
if (this.modelValue || this.computedPreviousValue) {
if (this.computedPreviousValue) {
this.$emit('update:model-value', this.computedPreviousValue)
}
return Promise.resolve({})
}
return new Promise((resolve) => {
cropCanvasAndExportToPNG(this.$refs.canvas).then(async (blob) => {
const file = new File([blob], 'signature.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({
template: this.template,
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)
})
})
}
})
})
}
}
}
</script>

@ -26,7 +26,7 @@ module Submitters
end end
end end
def create_attachment!(submitter, params) def create_attachment!(record, params)
blob = blob =
if (file = params[:file]) if (file = params[:file])
ActiveStorage::Blob.create_and_upload!(io: file.open, ActiveStorage::Blob.create_and_upload!(io: file.open,
@ -39,7 +39,7 @@ module Submitters
ActiveStorage::Attachment.create!( ActiveStorage::Attachment.create!(
blob:, blob:,
name: params[:name], name: params[:name],
record: submitter record: record
) )
end end

Loading…
Cancel
Save