Implement drag'n'drop
This commit is contained in:
parent
63f5ddd8af
commit
266fe05c09
63
public/assets/scripts/FileSelector.js
Normal file
63
public/assets/scripts/FileSelector.js
Normal file
@ -0,0 +1,63 @@
|
||||
export class FileSelector {
|
||||
dropZones = [];
|
||||
selectedFile = null;
|
||||
onFileSelected = null;
|
||||
|
||||
constructor(mimeType) {
|
||||
this.mimeType = mimeType;
|
||||
|
||||
window.ondrop = (e) => e.preventDefault();
|
||||
|
||||
window.ondragover = (e) => {
|
||||
if (![...e.dataTransfer.items].some(it => it.kind === "file")) return;
|
||||
|
||||
e.preventDefault();
|
||||
if (!this.dropZones.some(dropZone => dropZone.contains(e.target))) {
|
||||
e.dataTransfer.dropEffect = "none";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
selectFile(file) {
|
||||
this.selectedFile = file;
|
||||
|
||||
if (this.onFileSelected) this.onFileSelected(file);
|
||||
}
|
||||
|
||||
addInput(elem) {
|
||||
elem.oninput = () => {
|
||||
setTimeout(() => this.selectFile(elem.files[0]), 200);
|
||||
};
|
||||
}
|
||||
|
||||
addDropZone(elem) {
|
||||
this.dropZones.push(elem);
|
||||
|
||||
elem.ondrop = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const file = [...e.dataTransfer.files].find(file => file.type.startsWith(this.mimeType));
|
||||
|
||||
if (file) {
|
||||
this.selectFile(file);
|
||||
}
|
||||
};
|
||||
|
||||
elem.ondragover = (e) => {
|
||||
const items = [...e.dataTransfer.items];
|
||||
|
||||
if (!items.some(it => it.kind === "file")) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
if (items.some(it => it.type.startsWith(this.mimeType))) {
|
||||
e.dataTransfer.dropEffect = "copy";
|
||||
elem.classList.add("dropping");
|
||||
} else {
|
||||
e.dataTransfer.dropEffect = "copy";
|
||||
}
|
||||
};
|
||||
|
||||
elem.ondragleave = () => elem.classList.remove("dropping");
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,33 @@
|
||||
import { Notifier } from "./Notifier.js";
|
||||
import { ProgressBar } from "./ProgressBar.js";
|
||||
import { VideoCompressor } from "./VideoCompressor.js";
|
||||
import { FileSelector } from "./FileSelector.js";
|
||||
|
||||
const notifier = new Notifier();
|
||||
const progressBar = new ProgressBar();
|
||||
const videoCompressor = new VideoCompressor(progressBar);
|
||||
const fileSelector = new FileSelector("video/");
|
||||
|
||||
// Reset file input cache after reload
|
||||
document.getElementById("file-input").value = "";
|
||||
fileSelector.addInput(document.getElementById("file-input"));
|
||||
fileSelector.addDropZone(document.getElementById("file-drop-area"));
|
||||
|
||||
fileSelector.onFileSelected = file => {
|
||||
document.getElementById("uploaded-video").src = URL.createObjectURL(file);
|
||||
document.getElementById("uploaded-video").load();
|
||||
|
||||
hideElements("#file-drop-area", "#file-input-spacing");
|
||||
showElements("#uploaded-video", "#change-file");
|
||||
};
|
||||
|
||||
async function compress(filesize, filesizeUnit) {
|
||||
document.getElementById("uploaded-video").pause();
|
||||
|
||||
const file = document.getElementById("file-input").files[0];
|
||||
const sectionChangePromise = showSection("loading");
|
||||
|
||||
if (!file) {
|
||||
if (!fileSelector.selectedFile) {
|
||||
await sectionChangePromise; // Avoid changing sections at the same time
|
||||
alert("Please select a file");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -27,12 +39,11 @@ async function compress(filesize, filesizeUnit) {
|
||||
case "mb": targetFilesize = filesize * 1000000; break;
|
||||
}
|
||||
|
||||
showSection("loading");
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await videoCompressor.compress(file, targetFilesize, videoLength);
|
||||
result = await videoCompressor.compress(fileSelector.selectedFile, targetFilesize, videoLength);
|
||||
} catch (e) {
|
||||
await sectionChangePromise; // Avoid changing sections at the same time
|
||||
alert(e.message);
|
||||
|
||||
showSection("file-picker");
|
||||
@ -80,45 +91,16 @@ document.getElementById("compress").onclick = async () => {
|
||||
await compress(filesize, filesizeUnit);
|
||||
}
|
||||
|
||||
for (const preset of document.getElementsByClassName("preset")) {
|
||||
preset.onclick = () => compress(parseInt(preset.dataset.size), preset.dataset.unit);
|
||||
}
|
||||
|
||||
document.getElementById("cancel").onclick = () => {
|
||||
videoCompressor.stop();
|
||||
|
||||
showSection("file-picker");
|
||||
};
|
||||
|
||||
function openFileSelector() {
|
||||
document.getElementById("file-input").click();
|
||||
}
|
||||
|
||||
document.getElementById("file-drop-area").onclick = openFileSelector;
|
||||
document.getElementById("change-file").onclick = openFileSelector;
|
||||
document.getElementById("change-file").onclick = () => document.getElementById("file-input").click();
|
||||
|
||||
for (const tabbable of document.querySelectorAll("[tabindex='0']")) {
|
||||
tabbable.onkeydown = event => {
|
||||
if (['Enter', 'Space'].includes(event.code))
|
||||
tabbable.click();
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("file-input").oninput = () => {
|
||||
setTimeout(() => {
|
||||
const file = document.getElementById("file-input").files[0];
|
||||
|
||||
document.getElementById("uploaded-video").src = URL.createObjectURL(file);
|
||||
document.getElementById("uploaded-video").load();
|
||||
|
||||
hideElements("#file-drop-area", "#file-input-spacing");
|
||||
showElements("#uploaded-video", "#change-file");
|
||||
}, 200);
|
||||
};
|
||||
|
||||
document.getElementById("back").onclick = () => {
|
||||
showSection("file-picker");
|
||||
};
|
||||
document.getElementById("back").onclick = () => showSection("file-picker");
|
||||
|
||||
window.onbeforeunload = event => {
|
||||
if (videoCompressor.inProgress) event.preventDefault();
|
||||
@ -130,3 +112,14 @@ function bytesToSizeString(bytes) {
|
||||
if (bytes >= 1000) return (bytes / 1000).toFixed(1) + " KB";
|
||||
return bytes + " B";
|
||||
}
|
||||
|
||||
for (const tabbable of document.querySelectorAll("[tabindex='0']")) {
|
||||
tabbable.onkeydown = event => {
|
||||
if (['Enter', 'Space'].includes(event.code))
|
||||
tabbable.click();
|
||||
}
|
||||
}
|
||||
|
||||
for (const preset of document.getElementsByClassName("preset")) {
|
||||
preset.onclick = () => compress(parseInt(preset.dataset.size), preset.dataset.unit);
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ function showSection(section) {
|
||||
}
|
||||
|
||||
showElements(`#${section}-section`);
|
||||
|
||||
return new Promise(resolve => setTimeout(resolve, TRANSITION_TIME * 2));
|
||||
}
|
||||
|
||||
function hideElements() {
|
||||
|
||||
@ -29,6 +29,10 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#file-drop-area.dropping {
|
||||
border-color: black;
|
||||
}
|
||||
|
||||
#uploaded-video {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Easily compress video files to a specific file size for uploading to various communication platforms that restrict upload size">
|
||||
<title>Video Compressor</title>
|
||||
<title>Compact.Video - Compress videos online</title>
|
||||
<script defer src="/assets/scripts/ffmpeg/package/dist/umd/ffmpeg.js"></script>
|
||||
<script defer src="/assets/scripts/ui.js"></script>
|
||||
<script defer type="module" src="/assets/scripts/main.js"></script>
|
||||
@ -19,10 +19,10 @@
|
||||
<p>Compress video files to a specific file size, so you can upload them to your favorite social media and messaging apps!</p>
|
||||
|
||||
<section id="file-picker-section">
|
||||
<div id="file-drop-area" tabindex="0">
|
||||
<label id="file-drop-area" tabindex="0">
|
||||
<span>Drag and drop a file here!</span>
|
||||
</div>
|
||||
<input id="file-input" type="file" accept="video/*" tabindex="-1">
|
||||
</label>
|
||||
<div id="file-input-spacing" style="margin-top: -1rem">
|
||||
<button class="simple">.</button>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user