I try to list files selected using multiple file input using the code below (also available at jsbin). When trying to move li
items to sort and nest them, they start flickering and sometimes are dropped from the list and completely removed from DOM. Can it be solved while keep using dcraw
and SortableJS
? It seems to work
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Bootstrap-Flask Demo Application</title>
<style>
.handle {
cursor: -webkit-grabbing;
cursor: move;
}
</style>
</head>
<body>
<input class="form-control" id="files" multiple="" name="files" required="" type="file">
<div id="files-selected" class="mb-3">
<ul class="list-group very-first-parent nested-sortable h-200">
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/dcraw"></script>
<script src="https://sortablejs.github.io/Sortable/Sortable.js"></script>
<script>
var filesField;
var filesSelected;
document.addEventListener('DOMContentLoaded', function () {
filesField = document.getElementById("files");
filesSelected = document.getElementById("files-selected");
filesField.addEventListener('change', function (e) {
var files = e.target.files;
var reader = [];
var filenames = Array.from(files).map(file => file.name);
filesSelected.querySelector('.very-first-parent').innerHTML = '';
filenames.forEach((filename, index) => {
// alert('file selected');
var file = files[index];
var li = document.createElement('li');
li.className = 'list-group-item';
li.innerHTML = `
<div class="d-flex align-items-center">
<i class="handle bi-arrows-move"></i>
<button type="button" class="btn btn-danger me-3 ms-3" onclick="dismiss(this)">
<span aria-hidden="true">×</span>
</button>
<span class="file-name">${filename}</span>
<!--spinner--><div class="spinner spinner-border ms-auto" role="status"><span class="visually-hidden">Loading...</span></div><!--/spinner-->
</div>
<ul class="list-group nested-sortable"></ul>
`;
showPreview(file, li);
filesSelected.querySelector('.very-first-parent').appendChild(li);
});
makeNestedSortable();
});
});
function isImage(file) {
const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp'];
return imageTypes.includes(file.type);
}
function isRawImage(file) {
const rawImageExtensions = ['.nef', '.cr2', '.tiff'];
return rawImageExtensions.some(ext => file.name.toLowerCase().endsWith(ext));
}
function isVideo(file) {
const videoTypes = ['video/mp4', 'video/quicktime'];
return videoTypes.includes(file.type);
}
function replaceSpinner(string_old, string_new) {
return string_old.replace(/<!--spinner-->.*<!--/spinner-->/gi, string_new)
}
function showPreview(file, li) {
if (isImage(file)) {
const reader = new FileReader();
reader.onload = function (e) {
li.innerHTML = replaceSpinner(li.innerHTML, `<img class="preview ms-auto" src="${e.target.result}" height="70" style="max-height: 70px;">`);
};
reader.readAsDataURL(file);
} else if (isRawImage(file)) {
// You can use a library like raw.js to decode raw images and show a preview
const reader = new FileReader();
reader.onload = (function (o) {
return function (e) {
// Get the image file as a buffer
var buf = new Uint8Array(e.currentTarget.result);
// Get the RAW metadata
var metadata = dcraw(buf, { verbose: true, identify: true }).split('n').filter(String);
// Extract the thumbnail
var thumbnail = dcraw(buf, { extractThumbnail: true });
// Create thumbnail
var blob = new Blob([thumbnail], { type: "image/jpeg" });
var urlCreator = window.URL || window.webkitURL;
var imageUrl = urlCreator.createObjectURL(blob);
li.innerHTML = replaceSpinner(li.innerHTML, `<img class="preview ms-auto" src="${imageUrl}" height="70" style="max-height: 70px;">`);
};
})(file);
reader.readAsArrayBuffer(file);
} else if (isVideo(file)) {
li.innerHTML = replaceSpinner(li.innerHTML, `<video src="${URL.createObjectURL(file)}" class="ms-auto" controls height="70" style="max-height: 70px;"></video>`);
} else {
li.innerHTML = replaceSpinner(li.innerHTML, `<span class="preview ms-auto">Preview not available</span>`);
}
}
function dismiss(target) {
var li = target.closest('li');
var fileToDelete = li.querySelector('span.file-name').textContent;
var dt = new DataTransfer();
Array.from(filesField.files).forEach((file, i) => {
if (file.name !== fileToDelete)
dt.items.add(file)
filesField.files = dt.files // this will trigger a change event
});
li.remove();
}
function makeNestedSortable() {
nestedSortables = [].slice.call(document.querySelectorAll('.nested-sortable'));
console.log(nestedSortables.length);
for (var i = 0; i < nestedSortables.length; i++) {
new Sortable(nestedSortables[i], {
group: 'nested',
animation: 150,
fallbackOnBody: true,
// invertSwap: true,
swapThreshold: 0.5
});
}
}
</script>
</body>
</html>