ketr.photos/frontend/face-explorer.html
James Ketrenos 94776ca233 Advanced filtering
Signed-off-by: James Ketrenos <james_gitlab@ketrenos.com>
2020-01-09 21:37:00 -08:00

387 lines
9.1 KiB
HTML

<html>
<script>'<base href="BASEPATH">';</script>
<body>
<style>
body {
margin: 0;
padding: 0;
}
#bar {
position: absolute;
left: 0;
right: 0;
bottom: 0;
overflow-x: scroll;
overflow-y: hidden;
box-sizing: content-box;
white-space: nowrap;
z-index: 100;
background: rgba(0, 0, 0, 0.5);
padding: 0.5em;
}
.view {
display: inline-block;
width: 100px;
height: 100px;
cursor: pointer;
box-sizing: border-box;
margin: 0 1em;
color: white;
font-weight: bold;
vertical-align: bottom;
text-align: center;
background-position: 50% 50%;
background-repeat: no-repeat;
background-size: contain;
}
.face {
position: absolute;
display: inline-block;
border: 1px solid rgb(128,0,0);
border-radius: 0.5em;
opacity: 0.5;
cursor: pointer;
text-align: center;
color: rgba(0, 0, 0, 0);
}
.face:hover {
border-color: #ff0000;
background-color: rgba(128,0,0,0.5);
color: red;
}
#photo {
position: fixed;
display: inline-block;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 0;
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: contain;
background-color: #222;
}
#placeholder {
position: absolute;
left: -1000px;
display: none;
}
#footer {
position: absolute;
bottom: 0px;
left: 0px;
right: 0px;
opacity: 0.6;
background: linear-gradient(45deg, rgba(16, 16, 16, 1), transparent);
color: white;
font-size: 1.5em;
line-height: 1.5em;
font-family: Arial;
box-shadow: 0px -0.25em 1em black;
text-shadow: 0 1px 0 black;
z-index: 100;
display: flex;
flex-direction: row;
justify-content: space-between;
}
#info {
padding: 0.5em 1em;
}
#loading {
padding: 0.5em 1em;
}
</style>
<img id="placeholder"></img>
<div id="photo">
</div>
<div id="footer">
<div id="info">Loading photoset...</div>
<div id="loading"></div>
</div>
<script>
var base, placeholder, info, photos = [], photoIndex;
var countdown = 15;
const days = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
months = [ "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December" ];
let activeFaces = [];
function makeFaceBoxes() {
Array.prototype.forEach.call(document.querySelectorAll('.face'), (el) => {
el.parentElement.removeChild(el);
});
const el = document.getElementById("photo"),
photo = photos[photoIndex];
if (!photo.faces || !photo.faces.length) {
return;
}
let width, height, offsetLeft = 0, offsetTop = 0;
/* If photo is wider than viewport, it will be 100% width and < 100% height */
if (photo.width / photo.height > document.body.offsetWidth / document.body.offsetHeight) {
width = 100;
height = 100 * photo.height / photo.width * document.body.offsetWidth / document.body.offsetHeight;
offsetLeft = 0;
offsetTop = (100 - height) * 0.5;
} else {
width = 100 * photo.width / photo.height * document.body.offsetHeight / document.body.offsetWidth;
height = 100;
offsetLeft = (100 - width) * 0.5;
offsetTop = 0;
}
photo.faces.forEach((face) => {
const box = document.createElement("div");
box.classList.add("face");
document.body.appendChild(box);
box.style.left = offsetLeft + face.left * width + "%";
box.style.top = offsetTop + face.top * height + "%";
box.style.width = ((face.right - face.left) * width) + "%";
box.style.height = ((face.bottom - face.top) * height) + "%";
box.textContent = face.faceConfidence.toFixed(2);
box.addEventListener("click", (event) => {
setPause(true);
var bar = document.getElementById("bar");
if (!bar) {
bar = document.createElement("div");
bar.id = "bar";
document.body.appendChild(bar);
} else {
while (bar.firstChild) {
bar.removeChild(bar.firstChild);
}
}
if (face.relatedFaces.length == 0) {
document.body.removeChild(bar);
}
face.relatedFaces.forEach((related) => {
var view = document.createElement("div"),
id = related.faceId,
url = base + "face-data/" + (id % 100) + "/" + id + "-original.png"
view.classList.add("view");
view.style.backgroundImage = "url(" + url + ")";
view.textContent = related.faceConfidence.toFixed(2) + ":" +related.distance.toFixed(2);
view.addEventListener("click", (event) => {
window.location.search = related.photoId;
event.preventDefault = true;
event.stopImmediatePropagation();
event.stopPropagation();
return false;
});
bar.appendChild(view);
});
event.preventDefault = true;
event.stopImmediatePropagation();
event.stopPropagation();
return false;
});
});
}
function setPause(state) {
paused = state;
if (!paused) {
tick();
} else {
document.getElementById("loading").textContent = "||";
clearTimeout(scheduled);
scheduled = null;
}
}
function loadPhoto(index) {
const photo = photos[index],
xml = new XMLHttpRequest(),
url = base + photo.path /* + "thumbs/scaled/"*/ + photo.filename,
taken = new Date(photo.taken);
document.getElementById("loading").textContent = "0%";
const bar = document.getElementById("bar");
if (bar) {
bar.parentElement.removeChild(bar);
}
xml.onprogress = function (event) {
var alpha = 0;
if (event.total) {
alpha = event.loaded / event.total;
document.getElementById("loading").textContent = Math.ceil(100 * alpha) + "%";
} else {
document.getElementById("loading").textContent = "0%";
}
};
xml.onload = function(event) {
info.textContent =
days[taken.getDay()] + ", " +
months[taken.getMonth()] + " " +
taken.getDate() + " " +
taken.getFullYear();
document.getElementById("photo").style.backgroundImage = "url(" + encodeURI(url).replace(/\(/g, '%28').replace(/\)/g, '%29') + ")";
makeFaceBoxes();
var parts = window.location.href.match(/^.*\?(.*)$/);
if (!parts) {
countdown = 15;
tick();
}
}
xml.onerror = function(event) {
info.textContent = "Error loading photo. Trying next photo.";
nextPhoto();
}
xml.open("GET", url, true);
xml.send();
}
function nextPhoto() {
if (photoIndex < photos.length - 1) {
photoIndex++;
loadPhoto(photoIndex);
return;
}
window.fetch("api/v1/photos/random")
.then(res => res.json()).then(function(data) {
info.textContent = "Random photo: " + data.id;
photos.push(data);
photoIndex = photos.length - 1;
loadPhoto(photoIndex);
});
}
var scheduled = false;
function tick() {
if (scheduled) {
clearTimeout(scheduled);
}
document.getElementById("loading").textContent = countdown + "s";
if (countdown > 0) {
/* If there is a timer running, then decrement the counter */
if (scheduled) {
countdown--;
}
scheduled = setTimeout(tick, 1000);
} else {
scheduled = null;
countdown = 15;
nextPhoto();
}
}
function toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
}
var paused = false,
tap = 0;
document.addEventListener("DOMContentLoaded", function() {
var tmp = document.querySelector("base");
if (tmp) {
base = new URL(tmp.href).pathname.replace(/\/$/, "") + "/"; /* Make sure there is a trailing slash */
} else {
base = "/";
}
var timeout = 0;
window.addEventListener("resize", (event) => {
if (timeout) {
window.clearTimeout(timeout);
}
timeout = window.setTimeout(makeFaceBoxes, 250);
});
document.addEventListener("click", function(event) {
toggleFullscreen();
var now = new Date().getTime();
if (tap && (now - tap < 300)) {
toggleFullscreen();
tap = 0;
} else {
tap = new Date().getTime();
}
});
document.addEventListener("keydown", function(event) {
switch (event.keyCode) {
case 32: /* space */
setPause(!paused);
return;
case 37: /* left */
if (photoIndex == 0) {
photoIndex = photos.length - 1;
} else {
photoIndex--;
}
loadPhoto(photoIndex);
return;
case 39: /* right */
nextPhoto();
return;
case 13: /* enter */
toggleFullscreen();
return;
}
});
info = document.getElementById("info");
/* Trim off everything up to and including the ? (if its there) */
var parts = window.location.href.match(/^.*\?(.*)$/), id = ""
if (parts) {
id = "/" + parts[1];
}
window.fetch("api/v1/photos/random" + id)
.then((res) => {
if (res.status >= 400) {
throw res.status;
}
return res.json();
}).then(function(data) {
info.textContent = "Random photo: " + data.id;
photoIndex = 0;
photos = [data];
loadPhoto(photoIndex);
}).catch(function(error) {
console.error(error);
info.textContent = "Unable to load random photo :(";
});
});
</script>
</body>