diff --git a/app/javascript/elements/dashboard_dropzone.js b/app/javascript/elements/dashboard_dropzone.js index 6704f840..19372c4c 100644 --- a/app/javascript/elements/dashboard_dropzone.js +++ b/app/javascript/elements/dashboard_dropzone.js @@ -1,5 +1,10 @@ import { target, targets, targetable } from '@github/catalyst/lib/targetable' +const loadingIconHtml = ` + + +` + export default targetable(class extends HTMLElement { static [targets.static] = [ 'hiddenOnDrag', @@ -21,8 +26,9 @@ export default targetable(class extends HTMLElement { this.fileDropzone?.addEventListener('drop', this.onDropFile) - this.folderCards.forEach((el) => el.addEventListener('drop', this.onDropFolder)) + this.folderCards.forEach((el) => el.addEventListener('drop', (e) => this.onDropFolder(e, el))) this.templateCards.forEach((el) => el.addEventListener('drop', this.onDropTemplate)) + this.templateCards.forEach((el) => el.addEventListener('dragstart', this.onTemplateDragStart)) return [this.fileDropzone, ...this.folderCards, ...this.templateCards].forEach((el) => { el?.addEventListener('dragover', this.onDragover) @@ -35,16 +41,32 @@ export default targetable(class extends HTMLElement { document.removeEventListener('dragover', this.onWindowDropover) window.removeEventListener('dragleave', this.onWindowDragleave) + } - this.fileDropzone?.removeEventListener('drop', this.onDropFile) + onTemplateDragStart = (e) => { + const id = e.target.href.split('/').pop() - this.folderCards.forEach((el) => el.removeEventListener('drop', this.onDropFolder)) - this.templateCards.forEach((el) => el.removeEventListener('drop', this.onDropTemplate)) + e.dataTransfer.effectAllowed = 'move' - return [this.fileDropzone, ...this.folderCards, ...this.templateCards].forEach((el) => { - el?.removeEventListener('dragover', this.onDragover) - el?.removeEventListener('dragleave', this.onDragleave) - }) + if (id) { + e.dataTransfer.setData('template_id', id) + + const dragPreview = e.target.cloneNode(true) + const rect = e.target.getBoundingClientRect() + + dragPreview.children[1].remove() + dragPreview.style.width = `${rect.width}px` + dragPreview.style.height = `${e.target.children[0].getBoundingClientRect().height + 50}px` + dragPreview.style.position = 'absolute' + dragPreview.style.pointerEvents = 'none' + dragPreview.style.opacity = '0.9' + + document.body.appendChild(dragPreview) + + e.dataTransfer.setDragImage(dragPreview, rect.width / 2, rect.height / 2) + + setTimeout(() => document.body.removeChild(dragPreview), 0) + } } onDropFile = (e) => { @@ -57,39 +79,65 @@ export default targetable(class extends HTMLElement { this.uploadFiles(e.dataTransfer.files, '/templates_upload') } - onDropFolder = (e) => { + onDropFolder = (e, el) => { e.preventDefault() - const loading = document.createElement('div') - const svg = e.target.querySelector('svg') + const templateId = e.dataTransfer.getData('template_id') - loading.innerHTML = this.loadingIconHtml - loading.children[0].classList.add(...svg.classList) + if (e.dataTransfer.files.length || templateId) { + const loading = document.createElement('div') + const svg = el.querySelector('svg') - e.target.replaceChild(loading.children[0], svg) - e.target.classList.add('opacity-50') + loading.innerHTML = loadingIconHtml + loading.children[0].classList.add(...svg.classList) - const params = new URLSearchParams({ folder_name: e.target.innerText }).toString() + el.replaceChild(loading.children[0], svg) + el.classList.add('opacity-50') - this.uploadFiles(e.dataTransfer.files, `/templates_upload?${params}`) + if (e.dataTransfer.files.length) { + const params = new URLSearchParams({ folder_name: el.innerText }).toString() + + this.uploadFiles(e.dataTransfer.files, `/templates_upload?${params}`) + } else { + const formData = new FormData() + + formData.append('name', el.innerText) + + fetch(`/templates/${templateId}/folder`, { + method: 'PUT', + redirect: 'manual', + body: formData, + headers: { + Accept: 'application/json', + 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content + } + }).finally(() => { + window.Turbo.visit(location.href) + }) + } + } } onDropTemplate = (e) => { e.preventDefault() - const loading = document.createElement('div') - loading.classList.add('bottom-5', 'left-0', 'flex', 'justify-center', 'w-full', 'absolute') - loading.innerHTML = this.loadingIconHtml + if (e.dataTransfer.files.length) { + const loading = document.createElement('div') + loading.classList.add('bottom-5', 'left-0', 'flex', 'justify-center', 'w-full', 'absolute') + loading.innerHTML = loadingIconHtml - e.target.appendChild(loading) - e.target.classList.add('opacity-50') + e.target.appendChild(loading) + e.target.classList.add('opacity-50') - const id = e.target.href.split('/').pop() + const id = e.target.href.split('/').pop() - this.uploadFiles(e.dataTransfer.files, `/templates/${id}/clone_and_replace`) + this.uploadFiles(e.dataTransfer.files, `/templates/${id}/clone_and_replace`) + } } - onWindowDragdrop = () => { + onWindowDragdrop = (e) => { + e.preventDefault() + if (!this.isLoading) this.hideDraghover() } @@ -111,8 +159,10 @@ export default targetable(class extends HTMLElement { } } - onDragover () { - this.style.backgroundColor = '#F7F3F0' + onDragover (e) { + if (e.dataTransfer?.types?.includes('Files') || this.dataset.targets !== 'dashboard-dropzone.templateCards') { + this.style.backgroundColor = '#F7F3F0' + } } onDragleave () { @@ -150,11 +200,4 @@ export default targetable(class extends HTMLElement { el.classList.add('bg-base-200', 'before:hidden') }) } - - get loadingIconHtml () { - return ` - - -` - } })