/* sources: MDN: https://developer.mozilla.org/en-US/docs/Web/API/File_API/Using_files_from_web_applications#example_uploading_a_user-selected_file https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop web.dev: https://web.dev/patterns/clipboard/paste-files/ note that this likely means the code is mixed-licensed under: MDN: "Attributions and copyright licensing" by Mozilla Contributors, licensed under CC-BY-SA 2.5. web.dev: code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies */ addEventListener("DOMContentLoaded", function () { function uploadFiles(files) { for (const file of files) { const li = document.createElement("li"); if (file.type.startsWith("image/")) { const img = document.createElement("img"); img.src = URL.createObjectURL(file); img.alt = file.name; li.appendChild(img); } li.appendChild(document.createTextNode("Uploading " + file.name + "...")); preview.appendChild(li); new FileUpload(li, file) } } /* DRAG-N-DROP */ const preview = document.getElementById("preview"); function dropHandler(ev) { ev.preventDefault(); const files = [...ev.dataTransfer.items] .map((item) => item.getAsFile()) .filter((file) => file); uploadFiles(files); } window.addEventListener("drop", dropHandler); window.addEventListener("dragover", (e) => { const fileItems = [...e.dataTransfer.items].filter( (item) => item.kind === "file", ); if (fileItems.length > 0) { e.preventDefault(); if (fileItems.some((item) => item.type.startsWith("image/"))) { e.dataTransfer.dropEffect = "copy"; } else { e.dataTransfer.dropEffect = "none"; } } }); const fileInput = document.getElementById("file-input"); fileInput.addEventListener("change", (e) => { uploadFiles(e.target.files); }); window.addEventListener("drop", (e) => { if ([...e.dataTransfer.items].some((item) => item.kind === "file")) { e.preventDefault(); } }); /* PASTE */ document.addEventListener('paste', async (e) => { // Prevent the default behavior, so you can code your own logic. e.preventDefault(); if (!e.clipboardData.files.length) { return; } // Iterate over all pasted files. uploadFiles(e.clipboardData.files) }); }); function FileUpload(preview, file) { this.ctrl = createThrobber(preview); const xhr = new XMLHttpRequest(); this.xhr = xhr; this.xhr.upload.addEventListener("progress", (e) => { if (e.lengthComputable) { const percentage = Math.round((e.loaded * 100) / e.total); this.ctrl.update(percentage); } }); xhr.upload.addEventListener("load", (e) => { this.ctrl.update(100); }); xhr.addEventListener("load", (e) => { preview.removeChild(this.ctrl); const a = document.createElement('a'); const text = document.createTextNode(xhr.responseText); a.href = xhr.responseText; a.appendChild(text); preview.appendChild(a); }); xhr.open("POST", ascend(preview, "form").action + "/" + file.name); xhr.overrideMimeType("application/octet-stream; charset=x-user-defined-binary"); xhr.send(file); } function createThrobber(element) { const throbberWidth = 64; const throbberHeight = 6; const throbber = document.createElement("canvas"); throbber.classList.add("upload-progress"); throbber.setAttribute("width", throbberWidth); throbber.setAttribute("height", throbberHeight); element.appendChild(throbber); throbber.ctx = throbber.getContext("2d"); throbber.ctx.fillStyle = "hsl(from orange h s l)"; throbber.update = (percent) => { throbber.ctx.fillRect( 0, 0, (throbberWidth * percent) / 100, throbberHeight, ); throbber.ctx.fillStyle = "hsl(from orange calc(h + " + (90*percent/100) + ") s l)"; if (percent === 100) { throbber.ctx.fillStyle = "hsl(from green h s l)"; } }; throbber.update(0); return throbber; } function ascend(from_element, to_tag_name) { to_tag_name = to_tag_name.toLowerCase(); e = from_element; while (e && e.parentNode) { e = e.parentNode; if (e.tagName && e.tagName.toLowerCase() == to_tag_name) { return e; } } }