diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb index d86a5ad8..c96360d9 100644 --- a/app/controllers/templates_controller.rb +++ b/app/controllers/templates_controller.rb @@ -118,7 +118,8 @@ class TemplatesController < ApplicationController def template_params params.require(:template).permit( :name, - { schema: [[:attachment_uuid, :name, { conditions: [%i[field_uuid value action operation]] }]], + { schema: [[:attachment_uuid, :google_drive_file_id, :name, + { conditions: [%i[field_uuid value action operation]] }]], submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid optional_invite_by_uuid email order]], fields: [[:uuid, :submitter_uuid, :name, :type, :required, :readonly, :default_value, diff --git a/app/javascript/application.js b/app/javascript/application.js index a9fb02d0..31e28407 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -49,6 +49,8 @@ import ShowOnValue from './elements/show_on_value' import CustomValidation from './elements/custom_validation' import ToggleClasses from './elements/toggle_classes' import AutosizeField from './elements/autosize_field' +import GoogleDriveFilePicker from './elements/google_drive_file_picker' +import OpenModal from './elements/open_modal' import * as TurboInstantClick from './lib/turbo_instant_click' @@ -136,6 +138,8 @@ safeRegisterElement('show-on-value', ShowOnValue) safeRegisterElement('custom-validation', CustomValidation) safeRegisterElement('toggle-classes', ToggleClasses) safeRegisterElement('autosize-field', AutosizeField) +safeRegisterElement('google-drive-file-picker', GoogleDriveFilePicker) +safeRegisterElement('open-modal', OpenModal) safeRegisterElement('template-builder', class extends HTMLElement { connectedCallback () { @@ -160,6 +164,7 @@ safeRegisterElement('template-builder', class extends HTMLElement { withSendButton: this.dataset.withSendButton !== 'false', withSignYourselfButton: this.dataset.withSignYourselfButton !== 'false', withConditions: this.dataset.withConditions === 'true', + withGoogleDrive: this.dataset.withGoogleDrive === 'true', withReplaceAndCloneUpload: true, currencies: (this.dataset.currencies || '').split(',').filter(Boolean), acceptFileTypes: this.dataset.acceptFileTypes, diff --git a/app/javascript/elements/google_drive_file_picker.js b/app/javascript/elements/google_drive_file_picker.js new file mode 100644 index 00000000..9aae712d --- /dev/null +++ b/app/javascript/elements/google_drive_file_picker.js @@ -0,0 +1,55 @@ +export default class extends HTMLElement { + connectedCallback () { + const iframeTemplate = this.querySelector('template') + + this.observer = new IntersectionObserver((entries) => { + if (entries.some(e => e.isIntersecting)) { + iframeTemplate.parentElement.prepend(iframeTemplate.content) + + this.observer.disconnect() + } + }) + + this.observer.observe(this) + + window.addEventListener('message', this.messageHandler) + } + + messageHandler = (event) => { + if (event.data.type === 'google-drive-files-picked') { + this.form.querySelectorAll('input[name="google_drive_file_ids[]"]').forEach(el => el.remove()) + + const files = event.data.files || [] + + files.forEach((file) => { + const input = document.createElement('input') + input.type = 'hidden' + input.name = 'google_drive_file_ids[]' + input.value = file.id + this.form.appendChild(input) + }) + + this.form.querySelector('button[type="submit"]').click() + this.loader.classList.remove('hidden') + } else if (event.data.type === 'google-drive-picker-loaded') { + this.loader.classList.add('hidden') + this.form.classList.remove('hidden') + } else if (event.data.type === 'google-drive-picker-request-oauth') { + document.getElementById(this.dataset.oauthButtonId).classList.remove('hidden') + this.classList.add('hidden') + } + } + + disconnectedCallback () { + this.observer?.unobserve(this) + window.removeEventListener('message', this.messageHandler) + } + + get form () { + return this.querySelector('form') + } + + get loader () { + return document.getElementById('google_drive_loader') + } +} diff --git a/app/javascript/elements/open_modal.js b/app/javascript/elements/open_modal.js new file mode 100644 index 00000000..b7b30787 --- /dev/null +++ b/app/javascript/elements/open_modal.js @@ -0,0 +1,16 @@ +export default class extends HTMLElement { + connectedCallback () { + const src = this.getAttribute('src') + const link = document.createElement('a') + + link.href = src + link.setAttribute('data-turbo-frame', 'modal') + link.style.display = 'none' + + this.appendChild(link) + + link.click() + + window.history.replaceState({}, document.title, window.location.pathname) + } +} diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index c679a37e..07abcba9 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -260,8 +260,12 @@ :style="{ backgroundColor }" > @@ -297,6 +301,8 @@ v-if="withUploadButton" :template-id="template.id" :accept-file-types="acceptFileTypes" + :with-google-drive="withGoogleDrive" + @click-google-drive="$refs.upload.openGoogleDriveModal()" @success="updateFromUpload" />
import Upload from './upload' -import { IconCloudUpload, IconFilePlus, IconFileSymlink, IconFiles, IconInnerShadowTop } from '@tabler/icons-vue' +import { IconCloudUpload, IconFilePlus, IconFileSymlink, IconFiles, IconInnerShadowTop, IconBrandGoogleDrive } from '@tabler/icons-vue' export default { name: 'FileDropzone', @@ -71,7 +80,8 @@ export default { IconCloudUpload, IconInnerShadowTop, IconFileSymlink, - IconFiles + IconFiles, + IconBrandGoogleDrive }, inject: ['baseFetch', 't'], props: { @@ -99,6 +109,11 @@ export default { required: false, default: true }, + withGoogleDrive: { + type: Boolean, + required: false, + default: false + }, title: { type: String, required: false, @@ -110,7 +125,7 @@ export default { default: 'image/*, application/pdf, application/zip' } }, - emits: ['success', 'error', 'loading'], + emits: ['success', 'error', 'loading', 'click-google-drive'], data () { return { isLoading: false, diff --git a/app/javascript/template_builder/field_submitter.vue b/app/javascript/template_builder/field_submitter.vue index a3151288..9f77e878 100644 --- a/app/javascript/template_builder/field_submitter.vue +++ b/app/javascript/template_builder/field_submitter.vue @@ -125,7 +125,7 @@ @update:model-value="$emit('name-change', selectedSubmitter)" /> - + + + + + diff --git a/app/javascript/template_builder/i18n.js b/app/javascript/template_builder/i18n.js index 4928643b..c12f00ef 100644 --- a/app/javascript/template_builder/i18n.js +++ b/app/javascript/template_builder/i18n.js @@ -1,4 +1,5 @@ const en = { + view: 'View', payment_link: 'Payment link', strikeout: 'Strikeout', draw_strikethrough_the_document: 'Draw strikethrough the document', @@ -176,10 +177,14 @@ const en = { and: 'and', or: 'or', start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Start a quick tour to learn how to create an send your first document', - start_tour: 'Start Tour' + start_tour: 'Start Tour', + or_add_from: 'Or add from', + sync: 'Sync', + syncing: 'Syncing...' } const es = { + view: 'Vista', payment_link: 'Enlace de pago', strikeout: 'Tachar', draw_strikethrough_the_document: 'Dibujar una línea de tachado en el documento', @@ -357,10 +362,14 @@ const es = { and: 'y', or: 'o', start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Inicia una guía rápida para aprender a crear y enviar tu primer documento.', - start_tour: 'Iniciar guía' + start_tour: 'Iniciar guía', + or_add_from: 'O agregar desde', + sync: 'Sincronizar', + syncing: 'Sincronizando...' } const it = { + view: 'Vista', payment_link: 'Link di pagamento', strikeout: 'Barrato', draw_strikethrough_the_document: 'Disegna una linea barrata sul documento', @@ -538,10 +547,14 @@ const it = { and: 'e', or: 'o', start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Inizia un tour rapido per imparare a creare e inviare il tuo primo documento.', - start_tour: 'Inizia il tour' + start_tour: 'Inizia il tour', + or_add_from: 'O aggiungi da', + sync: 'Sincronizza', + syncing: 'Sincronizzazione...' } const pt = { + view: 'Visualizar', payment_link: 'Link de pagamento', strikeout: 'Tachado', draw_strikethrough_the_document: 'Desenhe uma linha de tachado no documento', @@ -719,10 +732,14 @@ const pt = { and: 'e', or: 'ou', start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Comece um tour rápido para aprender a criar e enviar seu primeiro documento.', - start_tour: 'Iniciar tour' + start_tour: 'Iniciar tour', + or_add_from: 'Ou adicionar de', + sync: 'Sincronizar', + syncing: 'Sincronizando...' } const fr = { + view: 'Vue', payment_link: 'Lien de paiement', strikeout: 'Barrer', draw_strikethrough_the_document: 'Tracer une ligne de suppression sur le document', @@ -900,10 +917,14 @@ const fr = { and: 'et', or: 'ou', start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Lancez une visite rapide pour apprendre à créer et envoyer votre premier document.', - start_tour: 'Démarrer' + start_tour: 'Démarrer', + or_add_from: 'Ou ajouter depuis', + sync: 'Synchroniser', + syncing: 'Synchronisation...' } const de = { + view: 'Ansicht', payment_link: 'Zahlungslink', strikeout: 'Streichung', draw_strikethrough_the_document: 'Ziehe eine Streichung auf das Dokument', @@ -1081,10 +1102,14 @@ const de = { and: 'und', or: 'oder', start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Starte eine kurze Tour, um zu lernen, wie du dein erstes Dokument erstellst und versendest.', - start_tour: 'Starten' + start_tour: 'Starten', + or_add_from: 'Oder hinzufügen von', + sync: 'Synchronisieren', + syncing: 'Synchronisierung...' } const nl = { + view: 'Bekijken', payment_link: 'Betaallink', strikeout: 'Doorhalen', draw_strikethrough_the_document: 'Teken doorhaling in het document', @@ -1262,7 +1287,10 @@ const nl = { and: 'en', or: 'of', start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Start een korte rondleiding om te leren hoe u uw eerste document maakt en verzendt', - start_tour: 'Rondleiding starten' + start_tour: 'Rondleiding starten', + or_add_from: 'Of toevoegen van', + sync: 'Synchroniseren', + syncing: 'Synchroniseren...' } export { en, es, it, pt, fr, de, nl } diff --git a/app/javascript/template_builder/preview.vue b/app/javascript/template_builder/preview.vue index c83f58f7..f61488a8 100644 --- a/app/javascript/template_builder/preview.vue +++ b/app/javascript/template_builder/preview.vue @@ -94,12 +94,19 @@ -
+
+ @@ -123,6 +130,7 @@ import Upload from './upload' import { IconRouteAltLeft, IconSortDescending2 } from '@tabler/icons-vue' import ConditionsModal from './conditions_modal' import ReplaceButton from './replace' +import GoogleDriveDocumentSettings from './google_drive_document_settings' import Field from './field' import FieldType from './field_type' @@ -133,6 +141,7 @@ export default { IconRouteAltLeft, ConditionsModal, ReplaceButton, + GoogleDriveDocumentSettings, IconSortDescending2 }, inject: ['t'], diff --git a/app/javascript/template_builder/upload.vue b/app/javascript/template_builder/upload.vue index bdb633ce..be801202 100644 --- a/app/javascript/template_builder/upload.vue +++ b/app/javascript/template_builder/upload.vue @@ -3,29 +3,155 @@ + +