From 5d6b5ab6d71ec5f0c060ceeebda05fd9ab6e0413 Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Sat, 3 Jun 2023 21:22:03 +0300 Subject: [PATCH] update submission templates --- app/javascript/application.js | 7 +- app/javascript/application.scss | 4 + app/javascript/elements/clipboard_copy.js | 144 ++++++++++++++++++ app/javascript/template_builder/upload.vue | 1 + app/models/submission.rb | 12 ++ app/views/shared/_no_data_banner.html.erb | 8 + .../storage_settings/_google_cloud.html.erb | 4 +- app/views/submissions/index.html.erb | 114 +++++++++----- app/views/submissions/new.html.erb | 45 ++++-- app/views/submissions/show.html.erb | 100 +++++++++--- 10 files changed, 363 insertions(+), 76 deletions(-) create mode 100644 app/javascript/elements/clipboard_copy.js create mode 100644 app/views/shared/_no_data_banner.html.erb diff --git a/app/javascript/application.js b/app/javascript/application.js index 1a85429c..1674bdc4 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -7,7 +7,7 @@ import DisableHidden from './elements/disable_hidden' import TurboModal from './elements/turbo_modal' import FileDropzone from './elements/file_dropzone' import MenuActive from './elements/menu_active' - +import ClipboardCopy from './elements/clipboard_copy' import TemplateBuilder from './template_builder/builder' document.addEventListener('turbo:before-cache', () => { @@ -19,6 +19,7 @@ window.customElements.define('disable-hidden', DisableHidden) window.customElements.define('turbo-modal', TurboModal) window.customElements.define('file-dropzone', FileDropzone) window.customElements.define('menu-active', MenuActive) +window.customElements.define('clipboard-copy', ClipboardCopy) window.customElements.define('template-builder', class extends HTMLElement { connectedCallback () { @@ -38,3 +39,7 @@ window.customElements.define('template-builder', class extends HTMLElement { this.appElem?.remove() } }) + +document.addEventListener('clipboard-copy', function(event) { + console.log('Copied to clipboard') // TODO: Add a toast message +}) diff --git a/app/javascript/application.scss b/app/javascript/application.scss index 63ea5369..b84ff8b6 100644 --- a/app/javascript/application.scss +++ b/app/javascript/application.scss @@ -39,6 +39,10 @@ button[disabled] .enabled { @apply input input-bordered bg-white; } +.base-textarea { + @apply textarea textarea-bordered bg-white; +} + .base-button { @apply btn text-white text-base; } diff --git a/app/javascript/elements/clipboard_copy.js b/app/javascript/elements/clipboard_copy.js new file mode 100644 index 00000000..c059bff5 --- /dev/null +++ b/app/javascript/elements/clipboard_copy.js @@ -0,0 +1,144 @@ +// Source: https://github.com/github/clipboard-copy-element +// License: MIT +export default class extends HTMLElement { + constructor () { + super() + this.addEventListener('click', clicked) + this.addEventListener('focus', focused) + this.addEventListener('blur', blurred) + } + + connectedCallback () { + if (!this.hasAttribute('tabindex')) { + this.setAttribute('tabindex', '0') + } + + if (!this.hasAttribute('role')) { + this.setAttribute('role', 'button') + } + } + + get value () { + return this.getAttribute('value') || '' + } + + set value (text) { + this.setAttribute('value', text) + } +} + +function createNode (text) { + const node = document.createElement('pre') + node.style.width = '1px' + node.style.height = '1px' + node.style.position = 'fixed' + node.style.top = '5px' + node.textContent = text + return node +} + +function copyNode (node) { + if ('clipboard' in navigator) { + return navigator.clipboard.writeText(node.textContent || '') + } + + const selection = getSelection() + if (selection == null) { + return Promise.reject(new Error()) + } + + selection.removeAllRanges() + + const range = document.createRange() + range.selectNodeContents(node) + selection.addRange(range) + + document.execCommand('copy') + selection.removeAllRanges() + return Promise.resolve() +} + +function copyText (text) { + if ('clipboard' in navigator) { + return navigator.clipboard.writeText(text) + } + + const body = document.body + if (!body) { + return Promise.reject(new Error()) + } + + const node = createNode(text) + body.appendChild(node) + copyNode(node) + body.removeChild(node) + return Promise.resolve() +} + +function copyTarget (content) { + if ( + content instanceof HTMLInputElement || + content instanceof HTMLTextAreaElement + ) { + return copyText(content.value) + } else if ( + content instanceof HTMLAnchorElement && + content.hasAttribute('href') + ) { + return copyText(content.href) + } else { + return copyNode(content) + } +} + +async function copy (button) { + const id = button.getAttribute('for') + const text = button.getAttribute('value') + + function trigger () { + button.dispatchEvent(new CustomEvent('clipboard-copy', { bubbles: true })) + } + + if (text) { + await copyText(text) + trigger() + } else if (id) { + const root = 'getRootNode' in Element.prototype ? button.getRootNode() : button.ownerDocument + + if (!(root instanceof Document || ('ShadowRoot' in window && root instanceof ShadowRoot))) return + + const node = root.getElementById(id) + + if (node) { + await copyTarget(node) + trigger() + } + } +} + +function clicked (event) { + const button = event.currentTarget + + if (button instanceof HTMLElement) { + copy(button) + } +} + +function keydown (event) { + if (event.key === ' ' || event.key === 'Enter') { + const button = event.currentTarget + + if (button instanceof HTMLElement) { + event.preventDefault() + copy(button) + } + } +} + +function focused (event) { + event.currentTarget.addEventListener('keydown', keydown) +} + +function blurred (event) { + event.currentTarget.removeEventListener('keydown', keydown) +} diff --git a/app/javascript/template_builder/upload.vue b/app/javascript/template_builder/upload.vue index e469cac3..5b68d3c8 100644 --- a/app/javascript/template_builder/upload.vue +++ b/app/javascript/template_builder/upload.vue @@ -16,6 +16,7 @@ ref="input" type="file" class="hidden" + accept=".pdf" multiple @change="upload" > diff --git a/app/models/submission.rb b/app/models/submission.rb index ddea1add..17d30ce1 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -42,4 +42,16 @@ class Submission < ApplicationRecord has_many_attached :attachments scope :active, -> { where(deleted_at: nil) } + + def status + if completed_at? + 'completed' + elsif opened_at? + 'opened' + elsif sent_at? + 'sent' + else + 'awaiting' + end + end end diff --git a/app/views/shared/_no_data_banner.html.erb b/app/views/shared/_no_data_banner.html.erb new file mode 100644 index 00000000..39b5e3cd --- /dev/null +++ b/app/views/shared/_no_data_banner.html.erb @@ -0,0 +1,8 @@ +
+
+
+

Nothing to display

+

We apologize, but currently there is no data available to be displayed. You can try adding new entries or refreshing the page to see if any data becomes available.

+
+
+
diff --git a/app/views/storage_settings/_google_cloud.html.erb b/app/views/storage_settings/_google_cloud.html.erb index 1f504bbc..d031aa2d 100644 --- a/app/views/storage_settings/_google_cloud.html.erb +++ b/app/views/storage_settings/_google_cloud.html.erb @@ -1,7 +1,7 @@ <%= f.fields_for :value do |ff| %> <%= ff.hidden_field :service, value: 'google' %> <%= ff.fields_for :configs, configs do |fff| %> -
+
<%= fff.label :project, 'Project', class: 'label' %> <%= fff.text_field :project, value: configs['project'], required: true, class: 'base-input' %> @@ -13,7 +13,7 @@
<%= fff.label :credentials, 'Credentials (JSON key content)', class: 'label' %> - <%= fff.text_area :credentials, value: configs['credentials'], required: true, class: 'base-input' %> + <%= fff.text_area :credentials, value: configs['credentials'], required: true, class: 'base-textarea' %>
<% end %> <% end %> diff --git a/app/views/submissions/index.html.erb b/app/views/submissions/index.html.erb index 6f571cd7..d24528f0 100644 --- a/app/views/submissions/index.html.erb +++ b/app/views/submissions/index.html.erb @@ -1,40 +1,78 @@ -Submissions -template <%= @template.name %> -Copy share link: - -Add Recepients - - - - - - - <% @submissions.each do |submission| %> - - -
- Email - - Status - -
- <%= submission.email %> - - <% if submission.completed_at? %> - Completed - <% elsif submission.opened_at? %> - Opened - <% elsif submission.sent_at? %> - Sent - <% else %> - Awaiting +
+
+

+ <%= @template.name %> + <%= link_to 'Edit', template_path(@template), class: 'btn btn-outline btn-sm' %> +

+
+ + +
+
+
+
+

Recepients

+ <%= link_to new_template_submission_path(@template), class: 'btn btn-primary btn-sm gap-2', data: { turbo_frame: 'modal' } do %> + + + + + + + <% end %> +
+
+ <%- if @submissions.any? %> + + + + + + + + + + <% @submissions.each do |submission| %> + + + + + <% end %> - - - + +
+ Email + + Status + +
+ <%= submission.email %> + + + <%= submission.status.humanize %> + + + <%= link_to 'View', submission_path(@template), title: 'View', class: 'btn btn-outline btn-xs' %> + <%= button_to 'Remove', submission_path(submission), class: 'btn btn-outline btn-error btn-xs', title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' } %> +
- copy link
- <%= link_to 'View', submission_path(@template) %> - <%= button_to 'Remove', submission_path(submission), method: :delete, data: { turbo_confirm: 'Are you sure?' } %> -
+ <% else %> + <%= render 'shared/no_data_banner' %> <% end %> -
+
diff --git a/app/views/submissions/new.html.erb b/app/views/submissions/new.html.erb index 5d2ad43d..57cc3bd2 100644 --- a/app/views/submissions/new.html.erb +++ b/app/views/submissions/new.html.erb @@ -1,19 +1,34 @@ -<%= render 'shared/turbo_modal' do %> - <%= form_for '', url: template_submissions_path(@template), data: { turbo_frame: :_top } do |f| %> -
- <%= f.label :emails %> - <%= f.text_area :emails, required: true %> +<%= render 'shared/turbo_modal', title: 'New Recepients' do %> + <%= form_for '', url: template_submissions_path(@template), html: { class: 'space-y-4' }, data: { turbo_frame: :_top } do |f| %> +
+ <%= f.label :emails, class: 'label' %> + <%= f.text_area :emails, required: true, class: 'base-textarea' %>
-
- <%= f.check_box :send_email, { onchange: "message_field.classList.toggle('hidden', !event.currentTarget.checked)" } %> - <%= f.label :send_email %> +
+ <%= f.label :send_email, class: 'flex items-center cursor-pointer' do %> + <%= f.check_box :send_email, class: 'base-checkbox', onchange: "message_field.classList.toggle('hidden', !event.currentTarget.checked)" %> + Send Email + <% end %>
-