Skip to content

Add option to only accept photo taken in real time #402

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,8 @@ yarn-debug.log*
/docuseal
/ee
dump.rdb
/custom/
/docuseal.iml
/.idea/misc.xml
/.idea/modules.xml
/.idea/vcs.xml
21 changes: 21 additions & 0 deletions app/controllers/submit_form_take_photo_controller.rb
Original file line number Diff line number Diff line change
@@ -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
142 changes: 142 additions & 0 deletions app/javascript/photo.js
Original file line number Diff line number Diff line change
@@ -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')
}
})
35 changes: 27 additions & 8 deletions app/javascript/submission_form/dropzone.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<template>
<div
id="dropzone"
class="flex h-32 w-full"
class="flex w-full"
:class="{'h-20': onlyWithCamera, 'h-32': !onlyWithCamera}"
@dragover.prevent
@drop.prevent="onDropFiles"
>
Expand All @@ -13,10 +14,15 @@
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
<div class="flex flex-col items-center">
<IconInnerShadowTop
v-if="isLoading"
class="animate-spin"
:width="30"
:height="30"
v-if="isLoading"
class="animate-spin"
:width="30"
:height="30"
/>
<IconCamera
v-else-if="onlyWithCamera"
:width="30"
:height="30"
/>
<IconCloudUpload
v-else
Expand All @@ -29,7 +35,7 @@
>
{{ message }}
</div>
<div class="text-xs">
<div class="text-xs" v-if="!onlyWithCamera">
<span class="font-medium">{{ t('click_to_upload') }}</span> {{ t('or_drag_and_drop_files') }}
</div>
</div>
Expand All @@ -39,6 +45,7 @@
ref="input"
:multiple="multiple"
:accept="accept"
:capture="onlyWithCamera === true ? `camera` : null"
type="file"
class="hidden"
@change="onSelectFiles"
Expand All @@ -48,11 +55,13 @@
</template>

<script>
import { IconCloudUpload, IconInnerShadowTop } from '@tabler/icons-vue'
import { IconCamera, IconCloudUpload, IconInnerShadowTop } from '@tabler/icons-vue'
import field from "../template_builder/field.vue";

export default {
name: 'FileDropzone',
components: {
IconCamera,
IconCloudUpload,
IconInnerShadowTop
},
Expand All @@ -71,6 +80,11 @@ export default {
required: false,
default: false
},
onlyWithCamera: {
type: Boolean,
required: false,
default: false
},
accept: {
type: String,
required: false,
Expand All @@ -89,13 +103,18 @@ export default {
}
},
computed: {
field() {
return field
},
inputId () {
return 'el' + Math.random().toString(32).split('.')[1]
}
},
methods: {
onDropFiles (e) {
this.uploadFiles(e.dataTransfer.files)
if(!this.onlyWithCamera){
this.uploadFiles(e.dataTransfer.files)
}
},
onSelectFiles (e) {
e.preventDefault()
Expand Down
50 changes: 47 additions & 3 deletions app/javascript/submission_form/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ const en = {
upload: 'Upload',
files: 'Files',
signature_is_too_small_please_redraw: 'Signature is too small. Please redraw.',
wait_countdown_seconds: 'Wait {countdown} seconds'
wait_countdown_seconds: 'Wait {countdown} seconds',
photo: 'Photo',
take: 'Take',
retake: 'Retake',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Scan the QR code with your mobile camera app to open the form and take a photo',
}

const es = {
Expand Down Expand Up @@ -192,6 +196,10 @@ const es = {
upload: 'Subir',
files: 'Archivos',
signature_is_too_small_please_redraw: 'La firma es demasiado pequeña. Por favor, dibújala de nuevo.',
foto: 'Foto',
tomar: 'Tomar',
retomar: 'Retomar',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Escanea el código QR con la aplicación de cámara de tu móvil para abrir el formulario y tomar una foto',
wait_countdown_seconds: 'Espera {countdown} segundos'
}

Expand Down Expand Up @@ -290,6 +298,10 @@ const it = {
upload: 'Carica',
files: 'File',
signature_is_too_small_please_redraw: 'La firma è troppo piccola. Ridisegnala per favore.',
foto: 'Foto',
prendere: 'Prendere',
riprendere: 'Riprendere',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: "Scansiona il codice QR con l'app della fotocamera del tuo cellulare per aprire il modulo e scattare una foto",
wait_countdown_seconds: 'Attendi {countdown} secondi'
}

Expand Down Expand Up @@ -388,6 +400,10 @@ const de = {
upload: 'Hochladen',
files: 'Dateien',
signature_is_too_small_please_redraw: 'Die Unterschrift ist zu klein. Bitte erneut zeichnen.',
Foto: 'Foto',
take: 'Nimm',
Wiederholung: 'Wiederholen',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Scannen Sie den QR-Code mit Ihrer mobilen Kamera-App, um das Formular zu öffnen und ein Foto aufzunehmen',
wait_countdown_seconds: 'Warte {countdown} Sekunden'
}

Expand Down Expand Up @@ -486,7 +502,11 @@ const fr = {
upload: 'Télécharger',
files: 'Fichiers',
signature_is_too_small_please_redraw: 'La signature est trop petite. Veuillez la redessiner.',
wait_countdown_seconds: 'Attendez {countdown} secondes'
wait_countdown_seconds: 'Attendez {countdown} secondes',
photo: 'Photo',
take: 'Prendre',
retake: 'Reprendre',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Scannez le code QR avec l\'application photo de votre mobile pour ouvrir le formulaire et prendre une photo',
}

const pl = {
Expand Down Expand Up @@ -583,7 +603,11 @@ const pl = {
reupload: 'Ponowne przesłanie',
upload: 'Przesyłanie',
files: 'Pliki',
signature_is_too_small_please_redraw: 'Podpis jest zbyt mały. Proszę narysować go ponownie.'
signature_is_too_small_please_redraw: 'Podpis jest zbyt mały. Proszę narysować go ponownie.',
photo: 'Zdjęcie',
take: 'Weź',
retake: 'Odtwórz ponownie',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Zeskanuj kod QR za pomocą aplikacji aparatu mobilnego, aby otworzyć formularz i zrobić zdjęcie',
}

const uk = {
Expand Down Expand Up @@ -681,6 +705,10 @@ const uk = {
upload: 'Завантажити',
files: 'Файли',
signature_is_too_small_please_redraw: 'Підпис занадто малий. Будь ласка, перемалюйте його.',
photo: 'Фото',
take: 'Взяти',
retake: 'Перезняти',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Відскануйте QR-код за допомогою мобільної програми камери, щоб відкрити форму та зробити фото',
wait_countdown_seconds: 'Зачекайте {countdown} секунд'
}

Expand Down Expand Up @@ -779,6 +807,10 @@ const cs = {
upload: 'Nahrát',
files: 'Soubory',
signature_is_too_small_please_redraw: 'Podpis je příliš malý. Prosím, překreslete ho.',
photo: 'Fotografie',
take: 'Vezmi',
retake: 'Znovu získat',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Naskenujte QR kód pomocí mobilní aplikace pro fotoaparát, otevřete formulář a vyfotografujte',
wait_countdown_seconds: 'Počkejte {countdown} sekund'
}

Expand Down Expand Up @@ -877,6 +909,10 @@ const pt = {
upload: 'Carregar',
files: 'Arquivos',
signature_is_too_small_please_redraw: 'A assinatura é muito pequena. Por favor, redesenhe-a.',
photo: 'Foto',
take: 'Pegar',
retake: 'Retomar',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Escaneie o código QR com seu aplicativo de câmera móvel para abrir o formulário e tirar uma foto',
wait_countdown_seconds: 'Aguarde {countdown} segundos'
}

Expand Down Expand Up @@ -1075,6 +1111,10 @@ const nl = {
upload: 'Uploaden',
files: 'Bestanden',
signature_is_too_small_please_redraw: 'De handtekening is te klein. Teken deze opnieuw, alstublieft.',
photo: 'Foto',
take: 'Neem',
retake: 'Opnieuw nemen',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Scan de QR-code met uw mobiele camera-app om het formulier te openen en een foto te maken',
wait_countdown_seconds: 'Wacht {countdown} seconden'
}

Expand Down Expand Up @@ -1270,6 +1310,10 @@ const ko = {
upload: '업로드',
files: '파일',
signature_is_too_small_please_redraw: '서명이 너무 작습니다. 다시 그려주세요.',
photo: '사진',
take: '가져가다',
retake: '재응시',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: '모바일 카메라 앱으로 QR 코드를 스캔하여 양식을 열고 사진을 찍으세요.',
wait_countdown_seconds: '{countdown}초 기다리세요'
}

Expand Down
Loading