diff --git a/app/controllers/template_replace_documents_controller.rb b/app/controllers/templates_clone_and_replace_controller.rb similarity index 65% rename from app/controllers/template_replace_documents_controller.rb rename to app/controllers/templates_clone_and_replace_controller.rb index b9f28881..0d75416b 100644 --- a/app/controllers/template_replace_documents_controller.rb +++ b/app/controllers/templates_clone_and_replace_controller.rb @@ -1,15 +1,10 @@ # frozen_string_literal: true -class TemplateReplaceDocumentsController < ApplicationController +class TemplatesCloneAndReplaceController < ApplicationController load_and_authorize_resource :template def create - if params[:blobs].blank? && params[:files].blank? - return respond_to do |f| - f.html { redirect_back fallback_location: template_path(@template), alert: I18n.t('file_is_missing') } - f.json { render json: { error: I18n.t('file_is_missing') }, status: :unprocessable_entity } - end - end + return head :unprocessable_entity if params[:files].blank? ActiveRecord::Associations::Preloader.new( records: [@template], @@ -17,6 +12,7 @@ class TemplateReplaceDocumentsController < ApplicationController ).call cloned_template = Templates::Clone.call(@template, author: current_user) + cloned_template.name = File.basename(params[:files].first.original_filename, '.*') cloned_template.save! documents = Templates::ReplaceAttachments.call(cloned_template, params, extract_fields: true) @@ -31,6 +27,9 @@ class TemplateReplaceDocumentsController < ApplicationController f.json { render json: { id: cloned_template.id } } end rescue Templates::CreateAttachments::PdfEncrypted - render json: { error: 'PDF encrypted', status: 'pdf_encrypted' }, status: :unprocessable_entity + respond_to do |f| + f.html { render turbo_stream: turbo_stream.append(params[:form_id], html: helpers.tag.prompt_password) } + f.json { render json: { error: 'PDF encrypted', status: 'pdf_encrypted' }, status: :unprocessable_entity } + end end end diff --git a/app/javascript/application.js b/app/javascript/application.js index 7cf2371e..c2e12079 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -127,7 +127,7 @@ safeRegisterElement('template-builder', class extends HTMLElement { withSendButton: this.dataset.withSendButton !== 'false', withSignYourselfButton: this.dataset.withSignYourselfButton !== 'false', withConditions: this.dataset.withConditions === 'true', - withReplaceAndCloneUpload: this.dataset.withReplaceAndCloneUpload !== 'false', + withReplaceAndCloneUpload: true, currencies: (this.dataset.currencies || '').split(',').filter(Boolean), acceptFileTypes: this.dataset.acceptFileTypes, showTourStartForm: this.dataset.showTourStartForm === 'true' diff --git a/app/javascript/elements/dashboard_dropzone.js b/app/javascript/elements/dashboard_dropzone.js index 1dc3622f..6704f840 100644 --- a/app/javascript/elements/dashboard_dropzone.js +++ b/app/javascript/elements/dashboard_dropzone.js @@ -1,74 +1,106 @@ -import { actionable } from '@github/catalyst/lib/actionable' import { target, targets, targetable } from '@github/catalyst/lib/targetable' -export default actionable(targetable(class extends HTMLElement { - static [targets.static] = ['hiddenOnHover'] +export default targetable(class extends HTMLElement { + static [targets.static] = [ + 'hiddenOnDrag', + 'folderCards', + 'templateCards' + ] + static [target.static] = [ - 'loading', - 'icon', - 'input', - 'fileDropzone' + 'form', + 'fileDropzone', + 'fileDropzoneLoading' ] connectedCallback () { - this.showOnlyOnWindowHover = this.dataset.showOnlyOnWindowHover === 'true' - document.addEventListener('drop', this.onWindowDragdrop) document.addEventListener('dragover', this.onWindowDropover) + window.addEventListener('dragleave', this.onWindowDragleave) - this.addEventListener('dragover', this.onDragover) - this.addEventListener('dragleave', this.onDragleave) + this.fileDropzone?.addEventListener('drop', this.onDropFile) + + this.folderCards.forEach((el) => el.addEventListener('drop', this.onDropFolder)) + this.templateCards.forEach((el) => el.addEventListener('drop', this.onDropTemplate)) - this.fileDropzone.addEventListener('drop', this.onDrop) - this.fileDropzone.addEventListener('turbo:submit-start', this.showDraghover) - this.fileDropzone.addEventListener('turbo:submit-end', this.hideDraghover) + return [this.fileDropzone, ...this.folderCards, ...this.templateCards].forEach((el) => { + el?.addEventListener('dragover', this.onDragover) + el?.addEventListener('dragleave', this.onDragleave) + }) } disconnectedCallback () { document.removeEventListener('drop', this.onWindowDragdrop) document.removeEventListener('dragover', this.onWindowDropover) + window.removeEventListener('dragleave', this.onWindowDragleave) - this.removeEventListener('dragover', this.onDragover) - this.removeEventListener('dragleave', this.onDragleave) + this.fileDropzone?.removeEventListener('drop', this.onDropFile) - this.fileDropzone.removeEventListener('drop', this.onDrop) - this.fileDropzone.removeEventListener('turbo:submit-start', this.showDraghover) - this.fileDropzone.removeEventListener('turbo:submit-end', this.hideDraghover) + this.folderCards.forEach((el) => el.removeEventListener('drop', this.onDropFolder)) + this.templateCards.forEach((el) => el.removeEventListener('drop', this.onDropTemplate)) + + return [this.fileDropzone, ...this.folderCards, ...this.templateCards].forEach((el) => { + el?.removeEventListener('dragover', this.onDragover) + el?.removeEventListener('dragleave', this.onDragleave) + }) } - onDrop = (e) => { + onDropFile = (e) => { e.preventDefault() - this.input.files = e.dataTransfer.files + this.fileDropzoneLoading.classList.remove('hidden') + this.fileDropzoneLoading.previousElementSibling.classList.add('hidden') + this.fileDropzoneLoading.classList.add('opacity-50') - this.uploadFiles(e.dataTransfer.files) + this.uploadFiles(e.dataTransfer.files, '/templates_upload') } - onWindowDragdrop = () => { - if (!this.hovered) this.hideDraghover() + onDropFolder = (e) => { + e.preventDefault() + + const loading = document.createElement('div') + const svg = e.target.querySelector('svg') + + loading.innerHTML = this.loadingIconHtml + loading.children[0].classList.add(...svg.classList) + + e.target.replaceChild(loading.children[0], svg) + e.target.classList.add('opacity-50') + + const params = new URLSearchParams({ folder_name: e.target.innerText }).toString() + + this.uploadFiles(e.dataTransfer.files, `/templates_upload?${params}`) } - onSelectFiles (e) { + onDropTemplate = (e) => { e.preventDefault() - this.uploadFiles(this.input.files) - } + const loading = document.createElement('div') + loading.classList.add('bottom-5', 'left-0', 'flex', 'justify-center', 'w-full', 'absolute') + loading.innerHTML = this.loadingIconHtml - toggleLoading = (e) => { - if (e && e.target && (!e.target.contains(this) || !e.detail?.formSubmission?.formElement?.contains(this))) { - return - } + e.target.appendChild(loading) + e.target.classList.add('opacity-50') + + const id = e.target.href.split('/').pop() - this.loading?.classList?.toggle('hidden') - this.icon?.classList?.toggle('hidden') + this.uploadFiles(e.dataTransfer.files, `/templates/${id}/clone_and_replace`) } - uploadFiles () { - this.toggleLoading() + onWindowDragdrop = () => { + if (!this.isLoading) this.hideDraghover() + } + + uploadFiles (files, url) { + this.isLoading = true + + this.form.action = url + + this.form.querySelector('[type="file"]').files = files - this.fileDropzone.querySelector('button[type="submit"]').click() + this.form.querySelector('[type="submit"]').click() } onWindowDropover = (e) => { @@ -79,45 +111,50 @@ export default actionable(targetable(class extends HTMLElement { } } + onDragover () { + this.style.backgroundColor = '#F7F3F0' + } + + onDragleave () { + this.style.backgroundColor = null + } + onWindowDragleave = (e) => { if (e.clientX <= 0 || e.clientY <= 0 || e.clientX >= window.innerWidth || e.clientY >= window.innerHeight) { this.hideDraghover() } } - onDragover (e) { - e.preventDefault() + showDraghover = () => { + if (this.isDrag) return - this.hovered = true - this.style.backgroundColor = '#F7F3F0' - } + this.isDrag = true - onDragleave (e) { - e.preventDefault() + this.fileDropzone?.classList?.remove('hidden') - this.hovered = false - this.style.backgroundColor = null - } - - showDraghover = () => { - if (this.showOnlyOnWindowHover) { - this.classList.remove('hidden') - } + this.hiddenOnDrag.forEach((el) => { el.style.display = 'none' }) - this.classList.remove('bg-base-200', 'border-transparent') - this.classList.add('bg-base-100', 'border-base-300', 'border-dashed') - this.fileDropzone.classList.remove('hidden') - this.hiddenOnHover.forEach((el) => { el.style.display = 'none' }) + return [...this.folderCards, ...this.templateCards].forEach((el) => { + el.classList.remove('bg-base-200', 'before:hidden') + }) } hideDraghover = () => { - if (this.showOnlyOnWindowHover) { - this.classList.add('hidden') - } + this.isDrag = false + + this.fileDropzone?.classList?.add('hidden') + + this.hiddenOnDrag.forEach((el) => { el.style.display = null }) + + return [...this.folderCards, ...this.templateCards].forEach((el) => { + el.classList.add('bg-base-200', 'before:hidden') + }) + } - this.classList.add('bg-base-200', 'border-transparent') - this.classList.remove('bg-base-100', 'border-base-300', 'border-dashed') - this.fileDropzone.classList.add('hidden') - this.hiddenOnHover.forEach((el) => { el.style.display = null }) + get loadingIconHtml () { + return ` + + +` } -})) +}) diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index 2077f9c8..77e1bb96 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -1,24 +1,24 @@