6 Commits

3 changed files with 314 additions and 270 deletions

View File

@@ -202,6 +202,7 @@ input {
.tooltip .infotext { .tooltip .infotext {
padding: 12px; padding: 12px;
width: max-content; width: max-content;
pointer-events: none;
} }
.tooltiptext.tagdropdown { .tooltiptext.tagdropdown {
@@ -224,11 +225,13 @@ input {
.tooltip:hover .infotext { .tooltip:hover .infotext {
display: block; display: block;
opacity: 1; opacity: 1;
pointer-events: auto;
} }
.tooltip:active .infotext { .tooltip:active .infotext {
display: block; display: block;
opacity: 1; opacity: 1;
pointer-events: auto;
} }
.tagentryparent { .tagentryparent {

View File

@@ -383,10 +383,8 @@ def process_image(item: str, folder: str, _args: Args, baseurl: str, metadata: M
if os.path.exists(file): if os.path.exists(file):
url = f"{_args.web_root_url}{baseurl}{urllib.parse.quote(extsplit[0])}{_raw}" url = f"{_args.web_root_url}{baseurl}{urllib.parse.quote(extsplit[0])}{_raw}"
if _raw in (".tif", ".tiff"): if _raw in (".tif", ".tiff"):
logger.info("tiff file found", extra={"file": file})
image.tiff = url image.tiff = url
else: else:
logger.info("raw file found", extra={"file": file, "extension": _raw})
image.raw = url image.raw = url
metadata.images[item] = image metadata.images[item] = image

View File

@@ -6,30 +6,68 @@ class PhotoGallery {
this.subfolders = []; this.subfolders = [];
this.tagDropdownShown = false; this.tagDropdownShown = false;
this.darkMode = this.darkMode.bind(this);
this.darkModeToggle = this.darkModeToggle.bind(this);
this.debounce = this.debounce.bind(this); this.debounce = this.debounce.bind(this);
this.openSwipe = this.openSwipe.bind(this); this.detectDarkMode = this.detectDarkMode.bind(this);
this.prefetch = this.prefetch.bind(this);
this.reset = this.reset.bind(this);
this.recursive = this.recursive.bind(this);
this.requestMetadata = this.requestMetadata.bind(this);
this.filter = this.filter.bind(this); this.filter = this.filter.bind(this);
this.updateImageList = this.updateImageList.bind(this); this.finalize = this.finalize.bind(this);
this.insertPath = this.insertPath.bind(this);
this.lightMode = this.lightMode.bind(this);
this.onLoad = this.onLoad.bind(this);
this.openSwipe = this.openSwipe.bind(this);
this.parseHierarchicalTags = this.parseHierarchicalTags.bind(this);
this.prefetch = this.prefetch.bind(this);
this.prefetchCancel = this.prefetchCancel.bind(this);
this.recursive = this.recursive.bind(this);
this.renderTree = this.renderTree.bind(this);
this.requestMetadata = this.requestMetadata.bind(this);
this.reset = this.reset.bind(this);
this.resetHoverTimer = this.resetHoverTimer.bind(this);
this.scrollFunction = this.scrollFunction.bind(this);
this.setFilter = this.setFilter.bind(this); this.setFilter = this.setFilter.bind(this);
this.toggleTag = this.toggleTag.bind(this); this.setupClickHandlers = this.setupClickHandlers.bind(this);
this.setupDropdownToggle = this.setupDropdownToggle.bind(this); this.setupDropdownToggle = this.setupDropdownToggle.bind(this);
this.setupTagHandlers = this.setupTagHandlers.bind(this); this.setupTagHandlers = this.setupTagHandlers.bind(this);
this.setupClickHandlers = this.setupClickHandlers.bind(this); this.showLoader = this.showLoader.bind(this);
this.scrollFunction = this.scrollFunction.bind(this); this.toggleTag = this.toggleTag.bind(this);
this.topFunction = this.topFunction.bind(this); this.topFunction = this.topFunction.bind(this);
this.onLoad = this.onLoad.bind(this); this.updateImageList = this.updateImageList.bind(this);
this.darkMode = this.darkMode.bind(this);
this.lightMode = this.lightMode.bind(this);
this.darkModeToggle = this.darkModeToggle.bind(this);
this.detectDarkMode = this.detectDarkMode.bind(this);
this.init(); this.init();
} }
darkMode() {
const themeLink = document.getElementById("theme");
const darkThemeLink = document.getElementById("darktheme");
localStorage.setItem("theme", "dark");
if (themeLink) themeLink.disabled = true;
if (darkThemeLink) darkThemeLink.disabled = false;
}
darkModeToggle(mode) {
const switchState = document.getElementById("dark-mode-switch-check");
if (mode == "dark") {
this.darkMode();
if (switchState) {
switchState.checked = true;
}
} else if (mode == "light") {
this.lightMode();
if (switchState) {
switchState.checked = false;
}
} else {
if (switchState.checked) {
switchState.checked = false;
this.lightMode();
} else {
switchState.checked = true;
this.darkMode();
}
}
}
debounce(fn, delay) { debounce(fn, delay) {
let timeoutId; let timeoutId;
return (...args) => { return (...args) => {
@@ -38,43 +76,168 @@ class PhotoGallery {
}; };
} }
detectDarkMode() {
if (document.getElementById("darktheme")) {
const switchState = document.getElementById("dark-mode-switch-check");
const localStorageTheme = localStorage.getItem("theme");
if (localStorageTheme === "dark") {
switchState.checked = true;
this.darkModeToggle("dark");
return;
} else if (localStorageTheme === "light") {
switchState.checked = true;
this.darkModeToggle("light");
return;
}
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
switchState.checked = true;
this.darkModeToggle("dark");
} else {
switchState.checked = false;
this.darkModeToggle("light");
}
}
}
filter() {
this.showLoader();
const searchParams = new URLSearchParams(window.location.search);
this.shown = [];
let path = decodeURIComponent(window.location.origin + window.location.pathname.replace("index.html", ""));
if (path.startsWith("null")) {
path = window.location.protocol + "//" + path.substring(4);
}
const selectedTags = [];
document.querySelectorAll("#tagdropdown input.tagcheckbox:checked").forEach((checkbox) => {
let tag = checkbox.parentElement.id.trim().substring(1);
if (checkbox.parentElement.parentElement.children.length > 1) tag += "|";
selectedTags.push(tag);
});
const urltags = selectedTags.join(",");
let isRecursiveChecked = false;
try {
isRecursiveChecked = document.getElementById("recursive")?.checked || false;
} catch {}
for (const item of this.items) {
const tags = item.tags || [];
const include = selectedTags.every((selected) => {
const isParent = selected.endsWith("|");
return isParent ? tags.some((t) => t.startsWith(selected)) : tags.includes(selected);
});
if (include || selectedTags.length === 0) {
if (!isRecursiveChecked) {
if (decodeURIComponent(item.src).replace(item.name, "") === path) {
this.shown.push(item);
}
} else {
this.shown.push(item);
}
}
}
this.updateImageList();
window.location.hash = urltags;
const pid = searchParams.get("pid") - 1;
if (pid != -1) {
this.openSwipe(pid);
}
}
finalize(obj) {
if (typeof obj === "object" && obj !== null && !Array.isArray(obj)) {
const result = {};
Object.keys(obj)
.sort()
.forEach((key) => {
if (obj[key] === null) {
result[key] = [];
} else {
result[key] = this.finalize(obj[key]);
}
});
return result;
}
return obj || [];
}
insertPath(obj, path) {
let current = obj;
for (let i = 0; i < path.length; i++) {
const part = path[i];
if (i === path.length - 1) {
if (!current[part]) {
current[part] = null;
}
} else {
if (!current[part] || typeof current[part] !== "object") {
current[part] = {};
}
current = current[part];
}
}
}
lightMode() {
const themeLink = document.getElementById("theme");
const darkThemeLink = document.getElementById("darktheme");
localStorage.setItem("theme", "light");
if (themeLink) themeLink.disabled = false;
if (darkThemeLink) darkThemeLink.disabled = true;
}
onLoad() {
document.querySelectorAll(".tagtoggle").forEach((toggle) => {
toggle.addEventListener("mouseup", (event) => {
event.stopPropagation();
const tagid = toggle.getAttribute("data-tagid");
this.toggleTag(tagid);
});
});
this.requestMetadata();
this.setupDropdownToggle();
this.setupTagHandlers();
this.setupClickHandlers();
this.detectDarkMode();
window.addEventListener("scroll", this.scrollFunction);
}
openSwipe(imgIndex) { openSwipe(imgIndex) {
const options = { index: imgIndex }; const options = { index: imgIndex };
const gallery = new PhotoSwipe(this.pswpElement, PhotoSwipeUI_Default, this.shown, options); const gallery = new PhotoSwipe(this.pswpElement, PhotoSwipeUI_Default, this.shown, options);
gallery.init(); gallery.init();
} }
parseHierarchicalTags(tags, delimiter = "|") {
const tree = {};
for (const tag of tags) {
const parts = tag.split(delimiter);
this.insertPath(tree, parts);
}
return this.finalize(tree);
}
prefetch(imgIndex) { prefetch(imgIndex) {
const prefetchDiv = document.getElementById("img-prefetch"); const prefetchDiv = document.getElementById("img-prefetch");
if (!prefetchDiv) return; if (!prefetchDiv) return;
const img = document.createElement("img"); const img = document.createElement("img");
img.src = this.shown[imgIndex]?.src || ""; img.src = this.shown[imgIndex]?.src || "";
prefetchDiv.removeChild(prefetchDiv.firstChild);
prefetchDiv.appendChild(img); prefetchDiv.appendChild(img);
} }
reset() { prefetchCancel() {
const content = document.documentElement.innerHTML; const prefetchDiv = document.getElementById("img-prefetch");
const title = document.title; if (!prefetchDiv) return;
const folders = document.querySelector(".folders"); if (prefetchDiv.firstChild) {
let path = window.location.origin + window.location.pathname; prefetchDiv.removeChild(prefetchDiv.firstChild);
if (path.startsWith("null")) {
path = window.location.protocol + "//" + path.substring(4);
} }
if (folders) folders.style.display = "";
document.getElementById("recursive").checked = false;
document.querySelectorAll("#tagdropdown input.tagcheckbox:checked").forEach((checkbox) => (checkbox.checked = false));
window.history.replaceState({ html: content, pageTitle: title }, "", path);
this.requestMetadata();
}
showLoader() {
const imagelist = document.getElementById("imagelist");
imagelist.innerHTML = '<span class="loader"></span>';
imagelist.classList.add("centerload");
imagelist.classList.remove("row");
} }
async recursive() { async recursive() {
@@ -93,6 +256,7 @@ class PhotoGallery {
return; return;
} }
this.showLoader();
if (folders) folders.style.display = "none"; if (folders) folders.style.display = "none";
loc.searchParams.delete("recursive"); loc.searchParams.delete("recursive");
loc.searchParams.append("recursive", true); loc.searchParams.append("recursive", true);
@@ -142,11 +306,28 @@ class PhotoGallery {
if (nextLevel.length > 0) await fetchFoldersRecursively(nextLevel); if (nextLevel.length > 0) await fetchFoldersRecursively(nextLevel);
}; };
this.showLoader();
await fetchFoldersRecursively(this.subfolders); await fetchFoldersRecursively(this.subfolders);
this.items = [...newItems]; this.items = [...newItems];
this.filter(); this.filter();
} }
renderTree = (obj, depth = 0) => {
let lines = [];
const indent = "&nbsp;&nbsp;".repeat(depth);
for (const key of Object.keys(obj)) {
lines.push(indent + key);
if (Array.isArray(obj[key])) {
for (const val of obj[key]) {
lines.push("&nbsp;&nbsp;".repeat(depth + 1) + val);
}
} else if (typeof obj[key] === "object" && obj[key] !== null) {
lines = lines.concat(this.renderTree(obj[key], depth + 1));
}
}
return lines.join("\n");
};
requestMetadata() { requestMetadata() {
this.showLoader(); this.showLoader();
const hash = window.location.hash; const hash = window.location.hash;
@@ -175,130 +356,41 @@ class PhotoGallery {
.catch(() => {}); .catch(() => {});
} }
filter() { reset() {
const searchParams = new URLSearchParams(window.location.search); const content = document.documentElement.innerHTML;
this.shown = []; const title = document.title;
let path = decodeURIComponent(window.location.origin + window.location.pathname.replace("index.html", "")); const folders = document.querySelector(".folders");
let path = window.location.origin + window.location.pathname;
if (path.startsWith("null")) { if (path.startsWith("null")) {
path = window.location.protocol + "//" + path.substring(4); path = window.location.protocol + "//" + path.substring(4);
} }
const selectedTags = [];
document.querySelectorAll("#tagdropdown input.tagcheckbox:checked").forEach((checkbox) => { if (folders) folders.style.display = "";
let tag = checkbox.parentElement.id.trim().substring(1); document.getElementById("recursive").checked = false;
if (checkbox.parentElement.parentElement.children.length > 1) tag += "|"; document.querySelectorAll("#tagdropdown input.tagcheckbox:checked").forEach((checkbox) => (checkbox.checked = false));
selectedTags.push(tag); window.history.replaceState({ html: content, pageTitle: title }, "", path);
}); this.requestMetadata();
const urltags = selectedTags.join(",");
let isRecursiveChecked = false;
try {
isRecursiveChecked = document.getElementById("recursive")?.checked || false;
} catch {}
for (const item of this.items) {
const tags = item.tags || [];
const include = selectedTags.every((selected) => {
const isParent = selected.endsWith("|");
return isParent ? tags.some((t) => t.startsWith(selected)) : tags.includes(selected);
});
if (include || selectedTags.length === 0) {
if (!isRecursiveChecked) {
if (decodeURIComponent(item.src).replace(item.name, "") === path) {
this.shown.push(item);
} }
resetHoverTimer(index) {
if (this.hoverTimer) {
clearTimeout(this.hoverTimer);
}
this.prefetchCancel();
this.hoverTimer = setTimeout(() => {
this.prefetch(index);
}, 500);
}
scrollFunction() {
const totopbutton = document.getElementById("totop");
if (!totopbutton) return;
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
totopbutton.style.display = "block";
} else { } else {
this.shown.push(item); totopbutton.style.display = "none";
} }
} }
}
this.updateImageList();
window.location.hash = urltags;
const pid = searchParams.get("pid") - 1;
if (pid != -1) {
this.openSwipe(pid);
}
}
insertPath(obj, path) {
let current = obj;
for (let i = 0; i < path.length; i++) {
const part = path[i];
if (i === path.length - 1) {
if (!current[part]) {
current[part] = null;
}
} else {
if (!current[part] || typeof current[part] !== "object") {
current[part] = {};
}
current = current[part];
}
}
}
finalize(obj) {
if (typeof obj === "object" && obj !== null && !Array.isArray(obj)) {
const result = {};
Object.keys(obj)
.sort()
.forEach((key) => {
if (obj[key] === null) {
result[key] = [];
} else {
result[key] = this.finalize(obj[key]);
}
});
return result;
}
return obj || [];
}
parseHierarchicalTags(tags, delimiter = "|") {
const tree = {};
for (const tag of tags) {
const parts = tag.split(delimiter);
this.insertPath(tree, parts);
}
return this.finalize(tree);
}
renderTree = (obj, depth = 0) => {
let lines = [];
const indent = "&nbsp;&nbsp;".repeat(depth);
for (const key of Object.keys(obj)) {
lines.push(indent + key);
if (Array.isArray(obj[key])) {
for (const val of obj[key]) {
lines.push("&nbsp;&nbsp;".repeat(depth + 1) + val);
}
} else if (typeof obj[key] === "object" && obj[key] !== null) {
lines = lines.concat(this.renderTree(obj[key], depth + 1));
}
}
return lines.join("\n");
};
updateImageList() {
const imagelist = document.getElementById("imagelist");
if (!imagelist) return;
let str = "";
this.shown.forEach((item, index) => {
let tags = this.parseHierarchicalTags(item.tags || []);
str += `<div class="column"><figure title="${this.renderTree(tags)}"><img src="${
item.msrc
}" data-index="${index}" /><figcaption class="caption">${item.name}`;
if (item.tiff) str += `&nbsp;<a href="${item.tiff}">TIFF</a>`;
if (item.raw) str += `&nbsp;<a href="${item.raw}">RAW</a>`;
str += "</figcaption></figure></div>";
});
imagelist.classList.add("row");
imagelist.classList.remove("centerload");
imagelist.innerHTML = str;
}
setFilter(selected) { setFilter(selected) {
document.querySelectorAll("#tagdropdown input.tagcheckbox").forEach((checkbox) => { document.querySelectorAll("#tagdropdown input.tagcheckbox").forEach((checkbox) => {
@@ -310,13 +402,49 @@ class PhotoGallery {
}); });
} }
toggleTag(tagid) { setupClickHandlers() {
const tag = document.getElementById(tagid); const resetEl = document.getElementById("reset-filter")?.querySelector("label");
const ol = tag?.closest(".tagentry")?.querySelector(".tagentryparent"); if (resetEl) resetEl.addEventListener("click", this.reset);
const svg = tag?.parentElement.querySelector(".tagtoggle svg");
if (!ol || !svg) return; const recurseEl = document.getElementById("recursive");
ol.classList.toggle("show"); if (recurseEl) recurseEl.addEventListener("change", this.debounce(this.recursive, 150));
svg.style.transform = ol.classList.contains("show") ? "rotate(180deg)" : "rotate(0deg)";
const totop = document.getElementById("totop");
if (totop) totop.addEventListener("click", this.topFunction);
const darkModeSwitch = document.getElementById("dark-mode-switch");
if (darkModeSwitch) darkModeSwitch.addEventListener("click", this.darkModeToggle);
const imagelist = document.getElementById("imagelist");
if (imagelist) {
imagelist.addEventListener("click", (event) => {
const img = event.target.closest("img");
if (!img || !img.dataset.index) return;
const index = parseInt(img.dataset.index);
if (!isNaN(index)) this.openSwipe(index);
});
imagelist.addEventListener("mouseenter", (event) => {
const img = event.target;
if (!img || !img.dataset.index) return;
const index = parseInt(img.dataset.index);
if (!isNaN(index)) this.resetHoverTimer(index);
});
imagelist.addEventListener("mousemove", (event) => {
const img = event.target;
if (!img || !img.dataset.index) return;
const index = parseInt(img.dataset.index);
if (!isNaN(index)) this.resetHoverTimer(index);
});
imagelist.addEventListener("mouseleave", () => {
if (this.hoverTimer) {
clearTimeout(this.hoverTimer);
}
this.prefetchCancel();
});
}
} }
setupDropdownToggle() { setupDropdownToggle() {
@@ -359,129 +487,44 @@ class PhotoGallery {
}); });
} }
setupClickHandlers() { showLoader() {
const resetEl = document.getElementById("reset-filter")?.querySelector("label");
if (resetEl) resetEl.addEventListener("click", this.reset);
const recurseEl = document.getElementById("recursive");
if (recurseEl) recurseEl.addEventListener("change", this.debounce(this.recursive, 150));
const totop = document.getElementById("totop");
if (totop) totop.addEventListener("click", this.topFunction);
const darkModeSwitch = document.getElementById("dark-mode-switch");
if (darkModeSwitch) darkModeSwitch.addEventListener("click", this.darkModeToggle);
const imagelist = document.getElementById("imagelist"); const imagelist = document.getElementById("imagelist");
if (imagelist) { imagelist.innerHTML = '<span class="loader"></span>';
imagelist.addEventListener("click", (event) => { imagelist.classList.add("centerload");
const img = event.target.closest("img"); imagelist.classList.remove("row");
if (!img || !img.dataset.index) return;
const index = parseInt(img.dataset.index);
if (!isNaN(index)) this.openSwipe(index);
});
imagelist.addEventListener("mousemove", (event) => {
const img = event.target.closest("img");
if (!img || !img.dataset.index) return;
const index = parseInt(img.dataset.index);
if (!isNaN(index)) this.prefetch(index);
});
}
} }
scrollFunction() { toggleTag(tagid) {
const totopbutton = document.getElementById("totop"); const tag = document.getElementById(tagid);
if (!totopbutton) return; const ol = tag?.closest(".tagentry")?.querySelector(".tagentryparent");
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) { const svg = tag?.parentElement.querySelector(".tagtoggle svg");
totopbutton.style.display = "block"; if (!ol || !svg) return;
} else { ol.classList.toggle("show");
totopbutton.style.display = "none"; svg.style.transform = ol.classList.contains("show") ? "rotate(180deg)" : "rotate(0deg)";
}
} }
topFunction() { topFunction() {
window.scrollTo({ top: 0, behavior: "smooth" }); window.scrollTo({ top: 0, behavior: "smooth" });
} }
darkMode() { updateImageList() {
const themeLink = document.getElementById("theme"); this.showLoader();
const darkThemeLink = document.getElementById("darktheme"); const imagelist = document.getElementById("imagelist");
localStorage.setItem("theme", "dark"); if (!imagelist) return;
if (themeLink) themeLink.disabled = true; let str = "";
if (darkThemeLink) darkThemeLink.disabled = false; this.shown.sort((a, b) => a.src.replace(a.name, "").localeCompare(b.src.replace(b.name, "")));
} this.shown.forEach((item, index) => {
let tags = this.parseHierarchicalTags(item.tags || []);
lightMode() { str += `<div class="column"><figure title="${this.renderTree(tags)}"><img src="${
const themeLink = document.getElementById("theme"); item.msrc
const darkThemeLink = document.getElementById("darktheme"); }" data-index="${index}" /><figcaption class="caption">${item.name}`;
localStorage.setItem("theme", "light"); if (item.tiff) str += `&nbsp;<a href="${item.tiff}">TIFF</a>`;
if (themeLink) themeLink.disabled = false; if (item.raw) str += `&nbsp;<a href="${item.raw}">RAW</a>`;
if (darkThemeLink) darkThemeLink.disabled = true; str += "</figcaption></figure></div>";
}
darkModeToggle(mode) {
const switchState = document.getElementById("dark-mode-switch-check");
if (mode == "dark") {
this.darkMode();
if (switchState) {
switchState.checked = true;
}
} else if (mode == "light") {
this.lightMode();
if (switchState) {
switchState.checked = false;
}
} else {
if (switchState.checked) {
switchState.checked = false;
this.lightMode();
} else {
switchState.checked = true;
this.darkMode();
}
}
}
detectDarkMode() {
if (document.getElementById("darktheme")) {
const switchState = document.getElementById("dark-mode-switch-check");
const localStorageTheme = localStorage.getItem("theme");
if (localStorageTheme === "dark") {
switchState.checked = true;
this.darkModeToggle("dark");
return;
} else if (localStorageTheme === "light") {
switchState.checked = true;
this.darkModeToggle("light");
return;
}
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
switchState.checked = true;
this.darkModeToggle("dark");
} else {
switchState.checked = false;
this.darkModeToggle("light");
}
}
}
onLoad() {
document.querySelectorAll(".tagtoggle").forEach((toggle) => {
toggle.addEventListener("mouseup", (event) => {
event.stopPropagation();
const tagid = toggle.getAttribute("data-tagid");
this.toggleTag(tagid);
}); });
}); imagelist.classList.add("row");
imagelist.classList.remove("centerload");
this.requestMetadata(); imagelist.innerHTML = str;
this.setupDropdownToggle();
this.setupTagHandlers();
this.setupClickHandlers();
this.detectDarkMode();
window.addEventListener("scroll", this.scrollFunction);
} }
init() { init() {