X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/a6633642232efd164d4708967ab59e498fbff896..36116a45d4047b68c5a6ecf2ab3bb19e3e40782a:/resources/js/components/dropzone.js diff --git a/resources/js/components/dropzone.js b/resources/js/components/dropzone.js index e7273df62..87c6b4d4f 100644 --- a/resources/js/components/dropzone.js +++ b/resources/js/components/dropzone.js @@ -1,77 +1,154 @@ -import DropZoneLib from "dropzone"; -import {fadeOut} from "../services/animations"; +import {Component} from './component'; +import {Clipboard} from '../services/clipboard'; + +export class Dropzone extends Component { -/** - * Dropzone - * @extends {Component} - */ -class Dropzone { setup() { this.container = this.$el; this.url = this.$opts.url; this.successMessage = this.$opts.successMessage; this.removeMessage = this.$opts.removeMessage; - this.uploadLimitMessage = this.$opts.uploadLimitMessage; - this.timeoutMessage = this.$opts.timeoutMessage; - - const _this = this; - this.dz = new DropZoneLib(this.container, { - addRemoveLinks: true, - dictRemoveFile: this.removeMessage, - timeout: Number(window.uploadTimeout) || 60000, - maxFilesize: Number(window.uploadLimit) || 256, - url: this.url, - withCredentials: true, - init() { - this.dz = this; - this.dz.on('sending', _this.onSending.bind(_this)); - this.dz.on('success', _this.onSuccess.bind(_this)); - this.dz.on('error', _this.onError.bind(_this)); + this.uploadLimit = Number(this.$opts.uploadLimit); // TODO - Use + this.uploadLimitMessage = this.$opts.uploadLimitMessage; // TODO - Use + this.timeoutMessage = this.$opts.timeoutMessage; // TODO - Use + // window.uploadTimeout // TODO - Use + // TODO - Click-to-upload buttons/areas + // TODO - Drop zone highlighting of existing element + // (Add overlay via additional temp element). + + this.setupListeners(); + } + + setupListeners() { + this.container.addEventListener('dragenter', event => { + this.container.style.border = '1px dotted tomato'; + event.preventDefault(); + }); + + this.container.addEventListener('dragover', event => { + event.preventDefault(); + }); + + const reset = () => { + this.container.style.border = null; + }; + + this.container.addEventListener('dragend', event => { + reset(); + }); + + this.container.addEventListener('dragleave', event => { + reset(); + }); + + this.container.addEventListener('drop', event => { + event.preventDefault(); + const clipboard = new Clipboard(event.dataTransfer); + const files = clipboard.getFiles(); + for (const file of files) { + this.createUploadFromFile(file); } }); } - onSending(file, xhr, data) { + /** + * @param {File} file + * @return {Upload} + */ + createUploadFromFile(file) { + const {dom, status, progress} = this.createDomForFile(file); + this.container.append(dom); - const token = window.document.querySelector('meta[name=token]').getAttribute('content'); - data.append('_token', token); + const upload = { + file, + dom, + updateProgress(percentComplete) { + console.log(`progress: ${percentComplete}%`); + progress.textContent = `${percentComplete}%`; + progress.style.width = `${percentComplete}%`; + }, + markError(message) { + status.setAttribute('data-status', 'error'); + status.textContent = message; + }, + markSuccess(message) { + status.setAttribute('data-status', 'success'); + status.textContent = message; + }, + }; - xhr.ontimeout = (e) => { - this.dz.emit('complete', file); - this.dz.emit('error', file, this.timeoutMessage); - } + this.startXhrForUpload(upload); + + return upload; } - onSuccess(file, data) { - this.$emit('success', {file, data}); + /** + * @param {Upload} upload + */ + startXhrForUpload(upload) { + const formData = new FormData(); + formData.append('file', upload.file, upload.file.name); - if (this.successMessage) { - window.$events.emit('success', this.successMessage); - } + const req = window.$http.createXMLHttpRequest('POST', this.url, { + error() { + upload.markError('Upload failed'); // TODO - Update text + }, + readystatechange() { + if (this.readyState === XMLHttpRequest.DONE && this.status === 200) { + upload.markSuccess('Finished upload!'); + } else if (this.readyState === XMLHttpRequest.DONE && this.status >= 400) { + const content = this.responseText; + const data = content.startsWith('{') ? JSON.parse(content) : {message: content}; + const message = data?.message || content; + upload.markError(message); + } + }, + }); - fadeOut(file.previewElement, 800, () => { - this.dz.removeFile(file); + req.upload.addEventListener('progress', evt => { + const percent = Math.min(Math.ceil((evt.loaded / evt.total) * 100), 100); + upload.updateProgress(percent); }); + + req.setRequestHeader('Accept', 'application/json'); + req.send(formData); } - onError(file, errorMessage, xhr) { - this.$emit('error', {file, errorMessage, xhr}); + /** + * @param {File} file + * @return {{image: Element, dom: Element, progress: Element, label: Element, status: Element}} + */ + createDomForFile(file) { + const dom = document.createElement('div'); + const label = document.createElement('div'); + const status = document.createElement('div'); + const progress = document.createElement('div'); + const image = document.createElement('img'); - const setMessage = (message) => { - const messsageEl = file.previewElement.querySelector('[data-dz-errormessage]'); - messsageEl.textContent = message; - } + dom.classList.add('dropzone-file-item'); + status.classList.add('dropzone-file-item-status'); + progress.classList.add('dropzone-file-item-progress'); + + image.src = ''; // TODO - file icon + label.innerText = file.name; - if (xhr && xhr.status === 413) { - setMessage(this.uploadLimitMessage); - } else if (errorMessage.file) { - setMessage(errorMessage.file); + if (file.type.startsWith('image/')) { + image.src = URL.createObjectURL(file); } - } - removeAll() { - this.dz.removeAllFiles(true); + dom.append(image, label, progress, status); + return { + dom, label, image, progress, status, + }; } + } -export default Dropzone; \ No newline at end of file +/** + * @typedef Upload + * @property {File} file + * @property {Element} dom + * @property {function(Number)} updateProgress + * @property {function(String)} markError + * @property {function(String)} markSuccess + */