<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: 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>
Output
You can jump to the latest bin by adding /latest
to your URL
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |