mirror of https://github.com/docusealco/docuseal
				
				
				
			
							parent
							
								
									a21328afd1
								
							
						
					
					
						commit
						9f32484a6f
					
				| @ -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 | ||||
| @ -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') | ||||
|   } | ||||
| }) | ||||
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								| After Width: | Height: | Size: 446 B | 
| @ -0,0 +1,67 @@ | ||||
| <!DOCTYPE html> | ||||
| <html data-theme="docuseal" lang="en"> | ||||
|   <head> | ||||
|     <%= render 'layouts/head_tags' %> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <%= csrf_meta_tags %> | ||||
|     <%= csp_meta_tag %> | ||||
|     <% if ENV['ROLLBAR_CLIENT_TOKEN'] %> | ||||
|       <meta name="rollbar-token" content="<%= ENV.fetch('ROLLBAR_CLIENT_TOKEN', nil) %>"> | ||||
|       <%= 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'] %> | ||||
|   </head> | ||||
|   <body> | ||||
|     <% field = (@submitter.submission.template_fields || @submitter.template.fields).find { |f| f['type'] == 'image' && f['uuid'].starts_with?(params[:f]) } %> | ||||
|     <file-photo data-slug="<%= params[:slug] %>" class="flex items-center h-screen p-2 justify-center"> | ||||
|       <%= form_for '', url: submit_form_path(params[:slug]), html: { style: 'max-width: 900px; width: 100%; margin-bottom: 120px' }, method: :put do |f| %> | ||||
|         <input value="" type="hidden" name="values[<%= field['uuid'] %>]"> | ||||
|         <% if field['description'] %> | ||||
|           <div class="w-full mb-2"> | ||||
|             <%= field['description'] %> | ||||
|           </div> | ||||
|         <% end %> | ||||
|         <label for="file" class="w-full block h-32 relative bg-base-200 hover:bg-base-200/70 rounded-md border border-base-content border-dashed"> | ||||
|             <div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center p-2"> | ||||
|               <div class="flex flex-col items-center text-center"> | ||||
|               <span id="file-photo-icon"> | ||||
|                 <%= svg_icon('camera', class: 'w-10 h-10') %> | ||||
|               </span> | ||||
|                 <span id="file-photo-loading" class="hidden"> | ||||
|                 <%= svg_icon('loader', class: 'w-10 h-10 animate-spin') %> | ||||
|               </span> | ||||
|                 <div class="font-medium mb-1"> | ||||
|                   <%= t('take_photo') %> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <button aria-label="Clear" class="hidden btn btn-ghost btn-sm font-medium top-0 right-0 absolute mt-1 mr-1"> | ||||
|                 <%= svg_icon('reload', class: 'w-5 h-5') %> | ||||
|                 <span class="inline"><%= t('clear') %></span> | ||||
|               </button> | ||||
|               <input id="file" class="hidden" name="files[]" type="file" accept="image/*" capture="camera"> | ||||
|             </div> | ||||
|           </label> | ||||
|         <div class="mt-4"> | ||||
|           <button disabled class="base-button w-full"> | ||||
|             <%= t('submit') %> | ||||
|           </button> | ||||
|           <%= f.button button_title(title: t('submit')), class: 'base-button w-full', style: 'display: none' %> | ||||
|         </div> | ||||
|       <% end %> | ||||
|       <div id="success" class="text-center p-2 hidden" style="margin-bottom: 100px"> | ||||
|         <div class="flex items-center space-x-1 items-center justify-center text-2xl font-semibold mb-2"> | ||||
|           <%= svg_icon('circle_check', class: 'text-green-600') %> | ||||
|           <span> | ||||
|             <%= t('photo_uploaded') %> | ||||
|           </span> | ||||
|         </div> | ||||
|         <div> | ||||
|           <%= t('return_back_to_your_desktop_device_to_complete_the_form_or_continue_on_mobile_html', link: submit_form_path(params[:slug])) %> | ||||
|         </div> | ||||
|       </div> | ||||
|     </file-photo> | ||||
|   </body> | ||||
| </html> | ||||
					Loading…
					
					
				
		Reference in new issue