From 11b0b16ca7ec215e755f3e7354326475c260e64f Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 11 Apr 2026 11:50:41 +0300 Subject: [PATCH] prefill signature client side --- app/javascript/form.js | 1 + app/javascript/submission_form/form.vue | 12 ++++ .../submission_form/signature_step.vue | 62 ++++++++++++++++++- .../submit_form/_submission_form.html.erb | 2 +- .../maybe_assign_default_browser_signature.rb | 18 +----- 5 files changed, 74 insertions(+), 21 deletions(-) diff --git a/app/javascript/form.js b/app/javascript/form.js index 00ec8743..f8561dc0 100644 --- a/app/javascript/form.js +++ b/app/javascript/form.js @@ -31,6 +31,7 @@ safeRegisterElement('submission-form', class extends HTMLElement { isDemo: this.dataset.isDemo === 'true', attribution: this.dataset.attribution !== 'false', scrollPadding: this.dataset.scrollPadding || '-80px', + signatureText: this.dataset.signatureText, language: this.dataset.language, dryRun: this.dataset.dryRun === 'true', expand: ['true', 'false'].includes(this.dataset.expand) ? this.dataset.expand === 'true' : null, diff --git a/app/javascript/submission_form/form.vue b/app/javascript/submission_form/form.vue index e2bdcf42..0f469459 100644 --- a/app/javascript/submission_form/form.vue +++ b/app/javascript/submission_form/form.vue @@ -402,6 +402,8 @@ :remember-signature="rememberSignature" :attachments-index="attachmentsIndex" :require-signing-reason="requireSigningReason" + :signature-text="signatureText" + :signature-src="signatureSrc" :button-text="submitButtonText" :dry-run="dryRun" :with-disclosure="withDisclosure" @@ -832,6 +834,16 @@ export default { required: false, default: '' }, + signatureText: { + type: String, + required: false, + default: '' + }, + signatureSrc: { + type: String, + required: false, + default: '' + }, previousSignatureValue: { type: String, required: false, diff --git a/app/javascript/submission_form/signature_step.vue b/app/javascript/submission_form/signature_step.vue index 2651f8d5..fb59ef3b 100644 --- a/app/javascript/submission_form/signature_step.vue +++ b/app/javascript/submission_form/signature_step.vue @@ -408,6 +408,16 @@ export default { required: false, default: '' }, + signatureText: { + type: String, + required: false, + default: '' + }, + signatureSrc: { + type: String, + required: false, + default: '' + }, modelValue: { type: String, required: false, @@ -422,7 +432,7 @@ export default { isOtherReason: false, isUsePreviousValue: true, isTouchAttachment: false, - isTextSignature: this.field.preferences?.format === 'typed' || this.field.preferences?.format === 'typed_or_upload', + isTextSignature: !this.signatureSrc && (!!this.signatureText || this.field.preferences?.format === 'typed' || this.field.preferences?.format === 'typed_or_upload'), uploadImageInputKey: Math.random().toString() } }, @@ -482,7 +492,9 @@ export default { if (entry.isIntersecting) { this.setCanvasSize() - if (this.isTextSignature) { + if (this.signatureSrc) { + this.$nextTick(() => this.drawSignatureSrc()) + } else if (this.isTextSignature) { this.$nextTick(() => { if (this.$refs.textInput) { this.initTypedSignature() @@ -686,7 +698,9 @@ export default { } }, async initTypedSignature () { - if (this.submitter.name) { + if (this.signatureText) { + this.$refs.textInput.value = this.signatureText + } else if (this.submitter.name) { this.$refs.textInput.value = this.submitter.name } @@ -696,6 +710,48 @@ export default { this.updateWrittenSignature({ target: this.$refs.textInput }) } }, + drawSignatureSrc () { + const canvas = this.$refs.canvas + + if (!canvas) return + + const img = new Image() + + img.crossOrigin = 'anonymous' + + img.onload = () => { + const context = canvas.getContext('2d') + + const aspectRatio = img.width / img.height + const canvasWidth = canvas.width / scale + const canvasHeight = canvas.height / scale + + let targetWidth = canvasWidth + let targetHeight = canvasHeight + + if (canvasWidth / canvasHeight > aspectRatio) { + targetWidth = canvasHeight * aspectRatio + } else { + targetHeight = canvasWidth / aspectRatio + } + + const x = (canvasWidth - targetWidth) / 2 + const y = (canvasHeight - targetHeight) / 2 + + context.clearRect(0, 0, canvasWidth, canvasHeight) + context.drawImage(img, x, y, targetWidth, targetHeight) + + this.isSignatureStarted = true + + this.$emit('start') + } + + img.onerror = () => { + console.error(`Failed to load signature image from ${this.signatureSrc}. The remote server must send an Access-Control-Allow-Origin header to allow CORS access.`) + } + + img.src = this.signatureSrc + }, drawImage (event) { this.remove() this.clear() diff --git a/app/views/submit_form/_submission_form.html.erb b/app/views/submit_form/_submission_form.html.erb index 0c13d8c7..b56123ff 100644 --- a/app/views/submit_form/_submission_form.html.erb +++ b/app/views/submit_form/_submission_form.html.erb @@ -2,4 +2,4 @@ <% data_fields = Submissions.filtered_conditions_fields(submitter).to_json %> <% invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %> <% optional_invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['optional_invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %> - + diff --git a/lib/submitters/maybe_assign_default_browser_signature.rb b/lib/submitters/maybe_assign_default_browser_signature.rb index 50ad4a57..6a77b1e8 100644 --- a/lib/submitters/maybe_assign_default_browser_signature.rb +++ b/lib/submitters/maybe_assign_default_browser_signature.rb @@ -9,29 +9,13 @@ module Submitters def call(submitter, params, cookies = nil, attachments = []) attachments = attachments.select { |e| e.record_id == submitter.id && e.record_type == 'Submitter' } - if (value = params[:signature_src].presence || params[:signature].presence) - find_or_create_signature_from_value(submitter, value, attachments) - elsif params[:signed_signature_uuids].present? + if params[:signed_signature_uuids].present? find_storage_signature(submitter, params[:signed_signature_uuids], attachments) elsif cookies find_session_signature(submitter, cookies, attachments) end end - def find_or_create_signature_from_value(submitter, value, attachments) - _, attachment = Submitters::NormalizeValues.normalize_attachment_value(value, - { 'type' => 'signature' }, - submitter.account, - attachments, - for_submitter: submitter) - - attachment.record ||= submitter - - attachment.save! - - attachment - end - def sign_signature_uuid(uuid) ApplicationRecord.signed_id_verifier.generate(uuid, purpose: SIGNED_UUID_PURPPOSE) end