Skip to content

Instantly share code, notes, and snippets.

@addam
Last active March 30, 2022 13:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save addam/abceced29dd9d13e2a9e8e82f5752c01 to your computer and use it in GitHub Desktop.
Save addam/abceced29dd9d13e2a9e8e82f5752c01 to your computer and use it in GitHub Desktop.
html tool for manual sorting photos on local disk
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="list.js"></script>
<script>
// generate list.js from images in current directory:
// (echo 'imageList = ['; ls *.{jpg,jpeg,JPG,JPEG} | sed -E 's/(.*)/"\1",/p'; echo ']') > list.js
// generate list.js from images in subdirectories of the current one:
// (echo 'imageList = ['; ( for d in *; do ls "$d"/*.{jpg,jpeg,JPG,JPEG}; done) | sed -E 's/(.*)/"\1",/p'; echo ']') > list.js
var lastClicked;
function qsa(selector) {
return document.querySelectorAll(selector)
}
function cel(name, parameters={}) {
const element = document.createElement(name);
for (const param in parameters) {
element[param] = parameters[param];
}
return element;
}
function clearClass(cls) {
for (it of qsa(`.${cls}`)) {
it.classList.remove(cls)
}
}
function imgOnclick(event) {
const img = event.target;
if (event.shiftKey) {
let it = lastClicked;
while (it != img) {
it = it.nextElementSibling;
it.classList.add("selected");
}
} else if (event.ctrlKey) {
img.classList.toggle("selected");
} else {
clearClass("selected")
img.classList.add("selected")
}
lastClicked = img;
}
function imgOnload(event) {
const img = event.target;
img.title = `${img.naturalWidth}×${img.naturalHeight}`;
}
function imgOndragover(ev) {
ev.preventDefault();
clearClass("drop")
ev.target.classList.add("drop");
}
function imgOndragstart(ev) {
if (!ev.target.classList.contains("selected")) {
clearClass("selected")
ev.target.classList.add("selected");
}
ev.dataTransfer.setData("text", "imagesorter");
for (const it of qsa(".selected")) {
it.classList.add("drag");
}
}
function imgOndrop(ev) {
ev.preventDefault();
if (ev.dataTransfer.getData("text") === "imagesorter") {
const img = ev.target;
for (const it of qsa(".drag")) {
img.parentNode.insertBefore(it, img);
}
}
clearClass("drag")
clearClass("drop")
}
function windowOnload() {
const body = document.body;
for (const src of imageList) {
body.appendChild(cel("img", {src, alt:src, onclick: imgOnclick, onload: imgOnload, draggable: true, ondragstart: imgOndragstart, ondrop: imgOndrop, ondragover: imgOndragover}))
}
}
function keyDown(event) {
if (!(event.ctrlKey || event.shiftKey || event.altKey || event.metaKey)) {
if (event.key == "Delete") {
if (Array.from(qsa(".selected")).every(it => it.classList.contains("delete"))) {
for (const it of qsa(".selected")) {
it.classList.remove("delete");
}
} else {
for (const it of qsa(".selected")) {
it.classList.add("delete");
}
}
} else {
return;
}
event.preventDefault()
}
}
function downloadOnclick(event) {
const a = event.target;
const data = Array.from(qsa("img:not(.delete)")).map(it => it.alt);
const code = `imageList = ${JSON.stringify(data, null, 2)}`;
a.href = `data:application/javascript,${encodeURI(code)}`;
}
window.onload = windowOnload;
document.onkeydown = keyDown;
</script>
<title>Image sorter</title>
<style>
body {
display: flex;
flex-wrap: wrap;
}
img {
max-height: 300px;
box-sizing: border-box;
}
.drag {
opacity: 20%;
}
.delete {
border: 3px solid red;
opacity: 70%;
}
.selected {
border: 5px solid paleturquoise;
}
.drop {
border-left: 5em solid paleturquoise;
}
</style>
</head>
<body>
<a download="list.js" href="#" onclick="return downloadOnclick(event)">download list.js</a><br>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment