diff --git a/templates/functionality.js b/templates/functionality.js index 5fc90fa..a1cfc1a 100644 --- a/templates/functionality.js +++ b/templates/functionality.js @@ -6,30 +6,66 @@ class PhotoGallery { this.subfolders = []; this.tagDropdownShown = false; + this.darkMode = this.darkMode.bind(this); + this.darkModeToggle = this.darkModeToggle.bind(this); this.debounce = this.debounce.bind(this); - this.openSwipe = this.openSwipe.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.detectDarkMode = this.detectDarkMode.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.recursive = this.recursive.bind(this); + this.renderTree = this.renderTree.bind(this); + this.requestMetadata = this.requestMetadata.bind(this); + this.reset = this.reset.bind(this); + this.scrollFunction = this.scrollFunction.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.setupTagHandlers = this.setupTagHandlers.bind(this); - this.setupClickHandlers = this.setupClickHandlers.bind(this); - this.scrollFunction = this.scrollFunction.bind(this); + this.showLoader = this.showLoader.bind(this); + this.toggleTag = this.toggleTag.bind(this); this.topFunction = this.topFunction.bind(this); - this.onLoad = this.onLoad.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.updateImageList = this.updateImageList.bind(this); 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) { let timeoutId; return (...args) => { @@ -38,141 +74,27 @@ class PhotoGallery { }; } - openSwipe(imgIndex) { - const options = { index: imgIndex }; - const gallery = new PhotoSwipe(this.pswpElement, PhotoSwipeUI_Default, this.shown, options); - gallery.init(); - } - - prefetch(imgIndex) { - const prefetchDiv = document.getElementById("img-prefetch"); - if (!prefetchDiv) return; - - const img = document.createElement("img"); - img.src = this.shown[imgIndex]?.src || ""; - prefetchDiv.removeChild(prefetchDiv.firstChild); - prefetchDiv.appendChild(img); - } - - reset() { - const content = document.documentElement.innerHTML; - const title = document.title; - const folders = document.querySelector(".folders"); - let path = window.location.origin + window.location.pathname; - 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 = ''; - imagelist.classList.add("centerload"); - imagelist.classList.remove("row"); - } - - async recursive() { - this.showLoader(); - const loc = new URL(window.location.href); - const content = document.documentElement.innerHTML; - const title = document.title; - const isChecked = document.getElementById("recursive")?.checked; - const folders = document.querySelector(".folders"); - - if (!isChecked) { - if (folders) folders.style.display = ""; - loc.searchParams.delete("recursive"); - window.history.replaceState({ html: content, pageTitle: title }, "", loc); - this.requestMetadata(); - return; - } - - if (folders) folders.style.display = "none"; - loc.searchParams.delete("recursive"); - loc.searchParams.append("recursive", true); - window.history.replaceState({ html: content, pageTitle: title }, "", loc); - - const visited = new Set(); - const existingItems = new Set(); - const newItems = []; - - try { - const response = await fetch(".metadata.json"); - if (!response.ok) throw new Error("Failed to fetch metadata"); - const data = await response.json(); - - this.items = []; - this.subfolders = data.subfolders || []; - - for (const image of Object.values(data.images || {})) { - newItems.push(image); - existingItems.add(image.src); + 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"); } - } catch { - return; } - - const fetchFoldersRecursively = async (folderList) => { - if (!Array.isArray(folderList)) return; - const nextLevel = []; - await Promise.all( - folderList.map(async (folder) => { - if (!folder || !folder.metadata || visited.has(folder.url)) return; - visited.add(folder.url); - try { - const response = await fetch(folder.metadata); - if (!response.ok) throw new Error(); - const data = await response.json(); - for (const image of Object.values(data.images || {})) { - if (!existingItems.has(image.src)) { - newItems.push(image); - existingItems.add(image.src); - } - } - if (Array.isArray(data.subfolders)) nextLevel.push(...data.subfolders); - } catch {} - }), - ); - if (nextLevel.length > 0) await fetchFoldersRecursively(nextLevel); - }; - - await fetchFoldersRecursively(this.subfolders); - this.items = [...newItems]; - this.filter(); - } - - requestMetadata() { - this.showLoader(); - const hash = window.location.hash; - const searchParams = new URLSearchParams(window.location.search); - fetch(".metadata.json") - .then((response) => { - if (!response.ok) throw new Error("Failed to fetch metadata"); - return response.json(); - }) - .then((data) => { - this.items = Object.values(data.images || {}); - this.subfolders = data.subfolders || []; - - if (hash != "") { - const selected = hash.replace("#", "").split(","); - this.setFilter(selected); - } - if (searchParams.get("recursive") != null) { - const recChk = document.getElementById("recursive"); - if (recChk) recChk.checked = true; - this.recursive(); - } else { - this.filter(); - } - }) - .catch(() => {}); } filter() { @@ -224,23 +146,6 @@ class PhotoGallery { } } - 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 = {}; @@ -258,6 +163,55 @@ class PhotoGallery { 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) { + const options = { index: imgIndex }; + const gallery = new PhotoSwipe(this.pswpElement, PhotoSwipeUI_Default, this.shown, options); + gallery.init(); + } + parseHierarchicalTags(tags, delimiter = "|") { const tree = {}; for (const tag of tags) { @@ -267,6 +221,88 @@ class PhotoGallery { return this.finalize(tree); } + prefetch(imgIndex) { + const prefetchDiv = document.getElementById("img-prefetch"); + if (!prefetchDiv) return; + + const img = document.createElement("img"); + img.src = this.shown[imgIndex]?.src || ""; + prefetchDiv.removeChild(prefetchDiv.firstChild); + prefetchDiv.appendChild(img); + } + + async recursive() { + this.showLoader(); + const loc = new URL(window.location.href); + const content = document.documentElement.innerHTML; + const title = document.title; + const isChecked = document.getElementById("recursive")?.checked; + const folders = document.querySelector(".folders"); + + if (!isChecked) { + if (folders) folders.style.display = ""; + loc.searchParams.delete("recursive"); + window.history.replaceState({ html: content, pageTitle: title }, "", loc); + this.requestMetadata(); + return; + } + + this.showLoader(); + if (folders) folders.style.display = "none"; + loc.searchParams.delete("recursive"); + loc.searchParams.append("recursive", true); + window.history.replaceState({ html: content, pageTitle: title }, "", loc); + + const visited = new Set(); + const existingItems = new Set(); + const newItems = []; + + try { + const response = await fetch(".metadata.json"); + if (!response.ok) throw new Error("Failed to fetch metadata"); + const data = await response.json(); + + this.items = []; + this.subfolders = data.subfolders || []; + + for (const image of Object.values(data.images || {})) { + newItems.push(image); + existingItems.add(image.src); + } + } catch { + return; + } + + const fetchFoldersRecursively = async (folderList) => { + if (!Array.isArray(folderList)) return; + const nextLevel = []; + await Promise.all( + folderList.map(async (folder) => { + if (!folder || !folder.metadata || visited.has(folder.url)) return; + visited.add(folder.url); + try { + const response = await fetch(folder.metadata); + if (!response.ok) throw new Error(); + const data = await response.json(); + for (const image of Object.values(data.images || {})) { + if (!existingItems.has(image.src)) { + newItems.push(image); + existingItems.add(image.src); + } + } + if (Array.isArray(data.subfolders)) nextLevel.push(...data.subfolders); + } catch {} + }), + ); + if (nextLevel.length > 0) await fetchFoldersRecursively(nextLevel); + }; + + this.showLoader(); + await fetchFoldersRecursively(this.subfolders); + this.items = [...newItems]; + this.filter(); + } + renderTree = (obj, depth = 0) => { let lines = []; const indent = " ".repeat(depth); @@ -283,24 +319,58 @@ class PhotoGallery { return lines.join("\n"); }; - updateImageList() { + requestMetadata() { this.showLoader(); - const imagelist = document.getElementById("imagelist"); - if (!imagelist) return; - let str = ""; - 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 || []); - str += `
"; - }); - imagelist.classList.add("row"); - imagelist.classList.remove("centerload"); - imagelist.innerHTML = str; + const hash = window.location.hash; + const searchParams = new URLSearchParams(window.location.search); + fetch(".metadata.json") + .then((response) => { + if (!response.ok) throw new Error("Failed to fetch metadata"); + return response.json(); + }) + .then((data) => { + this.items = Object.values(data.images || {}); + this.subfolders = data.subfolders || []; + + if (hash != "") { + const selected = hash.replace("#", "").split(","); + this.setFilter(selected); + } + if (searchParams.get("recursive") != null) { + const recChk = document.getElementById("recursive"); + if (recChk) recChk.checked = true; + this.recursive(); + } else { + this.filter(); + } + }) + .catch(() => {}); + } + + reset() { + const content = document.documentElement.innerHTML; + const title = document.title; + const folders = document.querySelector(".folders"); + let path = window.location.origin + window.location.pathname; + 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(); + } + + scrollFunction() { + const totopbutton = document.getElementById("totop"); + if (!totopbutton) return; + if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) { + totopbutton.style.display = "block"; + } else { + totopbutton.style.display = "none"; + } } setFilter(selected) { @@ -313,13 +383,35 @@ class PhotoGallery { }); } - toggleTag(tagid) { - const tag = document.getElementById(tagid); - const ol = tag?.closest(".tagentry")?.querySelector(".tagentryparent"); - const svg = tag?.parentElement.querySelector(".tagtoggle svg"); - if (!ol || !svg) return; - ol.classList.toggle("show"); - svg.style.transform = ol.classList.contains("show") ? "rotate(180deg)" : "rotate(0deg)"; + setupClickHandlers() { + 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"); + 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("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); + }); + } } setupDropdownToggle() { @@ -362,129 +454,44 @@ class PhotoGallery { }); } - setupClickHandlers() { - 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); - + showLoader() { 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("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); - }); - } + imagelist.innerHTML = ''; + imagelist.classList.add("centerload"); + imagelist.classList.remove("row"); } - scrollFunction() { - const totopbutton = document.getElementById("totop"); - if (!totopbutton) return; - if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) { - totopbutton.style.display = "block"; - } else { - totopbutton.style.display = "none"; - } + toggleTag(tagid) { + const tag = document.getElementById(tagid); + const ol = tag?.closest(".tagentry")?.querySelector(".tagentryparent"); + const svg = tag?.parentElement.querySelector(".tagtoggle svg"); + if (!ol || !svg) return; + ol.classList.toggle("show"); + svg.style.transform = ol.classList.contains("show") ? "rotate(180deg)" : "rotate(0deg)"; } topFunction() { window.scrollTo({ top: 0, behavior: "smooth" }); } - 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; - } - - 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; - } - - 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); - }); + updateImageList() { + this.showLoader(); + const imagelist = document.getElementById("imagelist"); + if (!imagelist) return; + let str = ""; + 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 || []); + str += `"; }); - - this.requestMetadata(); - this.setupDropdownToggle(); - this.setupTagHandlers(); - this.setupClickHandlers(); - this.detectDarkMode(); - - window.addEventListener("scroll", this.scrollFunction); + imagelist.classList.add("row"); + imagelist.classList.remove("centerload"); + imagelist.innerHTML = str; } init() {