From 9f32484a6f9e424565016041535d1d3bbecdb3fd Mon Sep 17 00:00:00 2001 From: Vincent Barrier Date: Sun, 29 Dec 2024 11:06:01 +0100 Subject: [PATCH] Add support to use only camera on image field --- .../submit_form_take_photo_controller.rb | 21 + app/javascript/photo.js | 142 +++ app/javascript/submission_form/dropzone.vue | 29 +- app/javascript/submission_form/i18n.js | 12 +- app/javascript/submission_form/image_step.vue | 91 +- .../template_builder/field_settings.vue | 1098 +++++++++-------- app/views/icons/_camera.html.erb | 5 + .../submit_form_take_photo/show.html.erb | 67 + config/locales/i18n.yml | 1 + config/routes.rb | 2 + 10 files changed, 909 insertions(+), 559 deletions(-) create mode 100644 app/controllers/submit_form_take_photo_controller.rb create mode 100644 app/javascript/photo.js create mode 100644 app/views/icons/_camera.html.erb create mode 100644 app/views/submit_form_take_photo/show.html.erb diff --git a/app/controllers/submit_form_take_photo_controller.rb b/app/controllers/submit_form_take_photo_controller.rb new file mode 100644 index 00000000..0411f165 --- /dev/null +++ b/app/controllers/submit_form_take_photo_controller.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class SubmitFormTakePhotoController < ApplicationController + layout false + + around_action :with_browser_locale, only: %i[show] + skip_before_action :authenticate_user! + skip_authorization_check + + def show + @submitter = Submitter.find_by!(slug: params[:slug]) + + return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at? + + if @submitter.submission.template.archived_at? || @submitter.submission.archived_at? + return redirect_to submit_form_path(@submitter.slug) + end + + render :show + end +end diff --git a/app/javascript/photo.js b/app/javascript/photo.js new file mode 100644 index 00000000..cef7ab8b --- /dev/null +++ b/app/javascript/photo.js @@ -0,0 +1,142 @@ +window.customElements.define('file-photo', class extends HTMLElement { + connectedCallback () { + + this.clearButton.addEventListener('click', (e) => { + e.preventDefault() + this.valueInput.value = null + this.inputFile.click() + }) + + this.inputFile.addEventListener('change', (e) => { + e.preventDefault() + this.updateSubmitButtonVisibility() + this.uploadFiles(this.inputFile.files) + }) + + this.form.addEventListener('submit', (e) => { + e.preventDefault(); + this.submitButton.disabled = true + fetch(this.form.action, { + method: 'PUT', + body: new FormData(this.form) + }).then((response) => { + this.form.classList.add('hidden') + this.success.classList.remove('hidden') + return response + }).finally(() => { + this.submitButton.disabled = false + }) + }) + + } + + toggleLoading = (e) => { + this.updateSubmitButtonVisibility() + if (e && e.target && !e.target.contains(this)) { + return + } + this.loading.classList.toggle('hidden') + this.icon.classList.toggle('hidden') + this.classList.toggle('opacity-50') + } + + async uploadFiles (files) { + this.toggleLoading() + return await Promise.all( + Array.from(files).map(async (file) => { + const formData = new FormData() + if (file.type === 'image/bmp') { + file = await this.convertBmpToPng(file) + } + + formData.append('file', file) + formData.append('submitter_slug', this.dataset.slug) + formData.append('name', 'attachments') + + return fetch('/api/attachments', { + method: 'POST', + body: formData + }).then(resp => resp.json()).then((data) => { + return data + }) + })).then((result) => { + this.valueInput.value = result[0].uuid + return result[0] + }).finally(() => { + this.toggleLoading() + }) + } + + convertBmpToPng (bmpFile) { + return new Promise((resolve, reject) => { + const reader = new FileReader() + + reader.onload = function (event) { + const img = new Image() + + img.onload = function () { + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d') + + canvas.width = img.width + canvas.height = img.height + ctx.drawImage(img, 0, 0) + canvas.toBlob(function (blob) { + const newFile = new File([blob], bmpFile.name.replace(/\.\w+$/, '.png'), { type: 'image/png' }) + resolve(newFile) + }, 'image/png') + } + + img.src = event.target.result + } + reader.onerror = reject + reader.readAsDataURL(bmpFile) + }) + } + + updateSubmitButtonVisibility () { + if (!this.valueInput.value) { + this.submitButton.style.display = 'none' + this.placeholderButton.style.display = 'block' + } else { + this.submitButton.style.display = 'block' + this.placeholderButton.style.display = 'none' + } + } + + get submitButton () { + return this.querySelector('button[type="submit"]') + } + + get clearButton () { + return this.querySelector('button[aria-label="Clear"]') + } + + get placeholderButton () { + return this.querySelector('button[disabled]') + } + + get valueInput () { + return this.querySelector('input[name^="values"]') + } + + get inputFile () { + return this.querySelector('input[id="file"]') + } + + get icon () { + return this.querySelector('#file-photo-icon') + } + + get loading () { + return this.querySelector('#file-photo-loading') + } + + get form () { + return this.querySelector('form') + } + + get success () { + return this.querySelector('#success') + } +}) diff --git a/app/javascript/submission_form/dropzone.vue b/app/javascript/submission_form/dropzone.vue index d550d717..adc6bb48 100644 --- a/app/javascript/submission_form/dropzone.vue +++ b/app/javascript/submission_form/dropzone.vue @@ -1,7 +1,8 @@ diff --git a/app/javascript/template_builder/field_settings.vue b/app/javascript/template_builder/field_settings.vue index 1ab55f85..7b621c62 100644 --- a/app/javascript/template_builder/field_settings.vue +++ b/app/javascript/template_builder/field_settings.vue @@ -1,597 +1,607 @@ diff --git a/app/views/icons/_camera.html.erb b/app/views/icons/_camera.html.erb new file mode 100644 index 00000000..403797eb --- /dev/null +++ b/app/views/icons/_camera.html.erb @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/views/submit_form_take_photo/show.html.erb b/app/views/submit_form_take_photo/show.html.erb new file mode 100644 index 00000000..9e1caba0 --- /dev/null +++ b/app/views/submit_form_take_photo/show.html.erb @@ -0,0 +1,67 @@ + + + + <%= render 'layouts/head_tags' %> + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + <% if ENV['ROLLBAR_CLIENT_TOKEN'] %> + + <%= javascript_pack_tag 'rollbar', 'photo', defer: true %> + <% else %> + <%= javascript_pack_tag 'photo', defer: true %> + <% end %> + <%= stylesheet_pack_tag 'form', media: 'all' %> + <%= render 'shared/posthog' if ENV['POSTHOG_TOKEN'] %> + + + <% field = (@submitter.submission.template_fields || @submitter.template.fields).find { |f| f['type'] == 'image' && f['uuid'].starts_with?(params[:f]) } %> + + <%= form_for '', url: submit_form_path(params[:slug]), html: { style: 'max-width: 900px; width: 100%; margin-bottom: 120px' }, method: :put do |f| %> + + <% if field['description'] %> +
+ <%= field['description'] %> +
+ <% end %> + +
+ + <%= f.button button_title(title: t('submit')), class: 'base-button w-full', style: 'display: none' %> +
+ <% end %> + +
+ + diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 92ee1d8e..b6114300 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -391,6 +391,7 @@ en: &en draw_signature: Draw Signature clear: Clear signature_uploaded: Signature Uploaded + photo_uploaded: Photo Uploaded submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: 'Submission deletion is irreversible and will permanently remove all associated signed documents with it. Are you sure?' return_back_to_your_desktop_device_to_complete_the_form_or_continue_on_mobile_html: 'Return back to your desktop device to complete the form or continue on mobile' template_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: Template deletion is irreversible and will permanently remove all associated signed documents with it. Are you sure? diff --git a/config/routes.rb b/config/routes.rb index dd568316..eba58222 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -139,6 +139,8 @@ Rails.application.routes.draw do resources :submit_form_draw_signature, only: %i[show], path: 'p', param: 'slug' + resources :submit_form_take_photo, only: %i[show], path: 't', param: 'slug' + resources :submissions_preview, only: %i[show], path: 'e', param: 'slug' do get :completed end