diff --git a/files/global.css b/files/global.css
index 8cb9ff6..17d714b 100644
--- a/files/global.css
+++ b/files/global.css
@@ -176,6 +176,7 @@ figure {
.tooltip .tagdropdown {
padding: 0;
+ margin: 0;
}
.tooltip:hover .tooltiptext {
@@ -196,6 +197,11 @@ figure {
padding: 0;
}
+.tooltip .tooltiptext ol {
+ margin-left: 0;
+ padding-left: 0.5em;
+}
+
.tooltip .tooltiptext .tagentry label {
cursor: pointer;
width: 100%;
diff --git a/modules/generate_html.py b/modules/generate_html.py
index 5a20487..12bdc81 100644
--- a/modules/generate_html.py
+++ b/modules/generate_html.py
@@ -5,6 +5,7 @@ import fnmatch
import json
from typing import Any
from datetime import datetime
+from collections import defaultdict
from tqdm.auto import tqdm
from PIL import Image, ExifTags, TiffImagePlugin, UnidentifiedImageError
@@ -189,27 +190,66 @@ def get_image_info(item: str, folder: str) -> dict[str, Any]:
tags = xmpdata["xmpmeta"]["RDF"]["Description"]["subject"]["Bag"]["li"]
if isinstance(tags, str):
tags = [tags]
- xmp = xmpdata
except TypeError:
- ...
+ pass
except KeyError:
- ...
+ pass
try:
tags = xmpdata["xapmeta"]["RDF"]["Description"]["subject"]["Bag"]["li"]
if isinstance(tags, str):
tags = [tags]
- xmp = xmpdata
except TypeError:
- ...
+ pass
except KeyError:
- ...
+ pass
+ try:
+ tags = xmpdata["xmpmeta"]["RDF"]["Description"]["hierarchicalSubject"]["Bag"]["li"]
+ if isinstance(tags, str):
+ tags = [tags]
+ except TypeError:
+ pass
+ except KeyError:
+ pass
+ try:
+ tags = xmpdata["xapmeta"]["RDF"]["Description"]["hierarchicalSubject"]["Bag"]["li"]
+ if isinstance(tags, str):
+ tags = [tags]
+ except TypeError:
+ pass
+ except KeyError:
+ pass
if None in tags:
tags.remove(None)
- if "st" in tags:
- tags.remove("st")
return {"width": width, "height": height, "tags": tags, "exifdata": exifdata, "xmp": xmp}
+def nested_dict():
+ return defaultdict(nested_dict)
+
+
+def insert_path(d, path):
+ for part in path[:-1]:
+ d = d[part]
+ last = path[-1]
+ if not isinstance(d[last], dict):
+ d[last] = {}
+
+
+def finalize(d):
+ if isinstance(d, defaultdict):
+ # Sort keys before recursion
+ return {k: finalize(d[k]) for k in sorted(d)}
+ return d or []
+
+
+def parse_hierarchical_tags(tags, delimiter="|"):
+ tree = nested_dict()
+ for tag in tags:
+ parts = tag.split(delimiter)
+ insert_path(tree, parts)
+ return finalize(tree)
+
+
def get_tags(sidecarfile: str) -> list[str]:
"""
Extracts Tags from XMP sidecar file
@@ -230,21 +270,35 @@ def get_tags(sidecarfile: str) -> list[str]:
if isinstance(tags, str):
tags = [tags]
except TypeError:
- ...
+ pass
except KeyError:
- ...
+ pass
try:
tags = xmpdata["xapmeta"]["RDF"]["Description"]["subject"]["Bag"]["li"]
if isinstance(tags, str):
tags = [tags]
except TypeError:
- ...
+ pass
except KeyError:
- ...
+ pass
+ try:
+ tags = xmpdata["xmpmeta"]["RDF"]["Description"]["hierarchicalSubject"]["Bag"]["li"]
+ if isinstance(tags, str):
+ tags = [tags]
+ except TypeError:
+ pass
+ except KeyError:
+ pass
+ try:
+ tags = xmpdata["xapmeta"]["RDF"]["Description"]["hierarchicalSubject"]["Bag"]["li"]
+ if isinstance(tags, str):
+ tags = [tags]
+ except TypeError:
+ pass
+ except KeyError:
+ pass
if None in tags:
tags.remove(None)
- if "st" in tags:
- tags.remove("st")
return tags
@@ -489,9 +543,9 @@ def create_html_file(folder: str, title: str, foldername: str, images: list[dict
alltags = set()
for img in images:
- for tag in img["tags"]:
- alltags.add(tag)
- alltags = sorted(alltags)
+ alltags.update(img["tags"])
+
+ nested_tags = parse_hierarchical_tags(alltags)
folder_info = info.get(urllib.parse.quote(folder), "").split("\n")
_info = [i for i in folder_info if len(i) > 1] if folder_info else None
@@ -541,7 +595,7 @@ def create_html_file(folder: str, title: str, foldername: str, images: list[dict
version=version,
logo=logo,
licensefile=license_url,
- tags=alltags,
+ tags=nested_tags,
)
with open(html_file, "w", encoding="utf-8") as f:
diff --git a/templates/default.css b/templates/default.css
index 5f650be..4e238d1 100644
--- a/templates/default.css
+++ b/templates/default.css
@@ -52,7 +52,7 @@
background-color: var(--color2);
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--color4);
}
diff --git a/templates/index.html.j2 b/templates/index.html.j2
index e3ea461..d4f2346 100644
--- a/templates/index.html.j2
+++ b/templates/index.html.j2
@@ -1,3 +1,17 @@
+{%- macro render_tags(tag_tree, parent) -%}
+
+ {%- for key, value in tag_tree.items() %}
+ -
+
+ {%- if value %}
+ {{ render_tags(value, parent + '|' + key) }}
+ {%- endif %}
+
+ {%- endfor %}
+
+{%- endmacro -%}
@@ -49,15 +63,14 @@
- {%- if tags|length > 0 %}
-
Filter by Tags
+ {% if tags %}
+
+ Filter by Tags
- {%- for tag in tags -%}
-
- {%- endfor -%}
+ {{ render_tags(tags, '') }}
- {%- endif %}
+ {% endif %}
{%- if licensefile %}
License
{%- endif %}
@@ -165,7 +178,6 @@
{%- endif %}
{%- endfor %}
];
- var shown = [];
var re = /pid=(\d+)/;
var filterre = /#(.*)/;
var controllers = {}
@@ -194,10 +206,10 @@
window.scrollTo({ top: 0, behavior: 'smooth' })
}
- function updateImageList() {
+ function updateImageList(images) {
var str = ""
var imagelist = document.getElementById("imagelist");
- shown.forEach((item, index) => {
+ images.forEach((item, index) => {
str += '
' + item.name;
if (item.tiff != "") {
str += ' TIFF';
@@ -229,31 +241,31 @@
}
function filter() {
- window.location.href = window.location.href.split("#")[0] + "#"
- var selected_tags = [];
- var tagdropdown, tags, incl;
- shown = [];
- tagdropdown = document.getElementById("tagdropdown").getElementsByTagName("li");
- for (var i = 0; i < tagdropdown.length; i++) {
- if (tagdropdown[i].firstChild.firstChild.checked) {
- selected_tags.push([tagdropdown[i].innerText])
- }
- }
- var urltags = selected_tags.join(",");
- items.forEach((item, index) => {
- tags = item.tags;
- incl = true;
- selected_tags.forEach((tag) => {
- if (tags.indexOf(tag) == -1) {
- incl = false;
- }
- });
- if (incl | selected_tags == []) {
- shown.push(item)
+ window.location.href = window.location.href.split("#")[0] + "#";
+
+ const selected_tags = [];
+ const shown = [];
+
+ const tagcheckboxes = document.querySelectorAll("#tagdropdown input[type='checkbox']:checked");
+
+ tagcheckboxes.forEach((checkbox) => {
+ const tag = checkbox.parentElement.id.trim().substring(1);
+ selected_tags.push(tag);
+ });
+ console.log(selected_tags);
+
+ const urltags = selected_tags.join(",");
+
+ items.forEach((item) => {
+ const tags = item.tags || [];
+ const include = selected_tags.every(tag => tags.includes(tag));
+ if (include || selected_tags.length === 0) {
+ shown.push(item);
}
});
- updateImageList();
- window.location.href += urltags
+
+ updateImageList(shown);
+ window.location.href += urltags;
}
function setFilter(selected) {
@@ -275,8 +287,7 @@
}
filter();
{%- else %}
- shown = items;
- updateImageList();
+ updateImageList(items);
{%- endif %}
if (re.test(window.location.href)) {
diff --git a/themes/alpenglow-dark.css b/themes/alpenglow-dark.css
index df5b057..da6046c 100644
--- a/themes/alpenglow-dark.css
+++ b/themes/alpenglow-dark.css
@@ -97,7 +97,7 @@ body {
background-color: var(--color6);
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--color3);
}
diff --git a/themes/alpenglow.css b/themes/alpenglow.css
index a35b69b..f6c80e0 100644
--- a/themes/alpenglow.css
+++ b/themes/alpenglow.css
@@ -96,7 +96,7 @@ body {
background-color: var(--color3);
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--color7);
}
diff --git a/themes/aritim-dark.css b/themes/aritim-dark.css
index b5f55a9..c156a22 100644
--- a/themes/aritim-dark.css
+++ b/themes/aritim-dark.css
@@ -74,7 +74,7 @@
background-color: var(--bcolor1);
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--bcolor3);
}
diff --git a/themes/aritim.css b/themes/aritim.css
index 0e53073..45b0310 100644
--- a/themes/aritim.css
+++ b/themes/aritim.css
@@ -74,7 +74,7 @@
background-color: var(--bcolor1);
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--bcolor3);
}
diff --git a/themes/autumn.css b/themes/autumn.css
index 62f21f6..9eb82c5 100644
--- a/themes/autumn.css
+++ b/themes/autumn.css
@@ -79,7 +79,7 @@
font-family: "Playfair Display", serif;
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--color3);
}
diff --git a/themes/carnation.css b/themes/carnation.css
index 8739d30..e9b2b3a 100644
--- a/themes/carnation.css
+++ b/themes/carnation.css
@@ -73,7 +73,7 @@
background-color: var(--bcolor2);
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--bcolor4);
}
diff --git a/themes/catpuccin.css b/themes/catpuccin.css
index 423c84a..9c3dc66 100644
--- a/themes/catpuccin.css
+++ b/themes/catpuccin.css
@@ -79,7 +79,7 @@
font-family: "Nunito", sans-serif;
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--color4);
}
diff --git a/themes/cornflower.css b/themes/cornflower.css
index e25936d..27af7ed 100644
--- a/themes/cornflower.css
+++ b/themes/cornflower.css
@@ -73,7 +73,7 @@
background-color: var(--bcolor2);
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--bcolor4);
}
diff --git a/themes/default-dark.css b/themes/default-dark.css
index f4235a3..5af9582 100644
--- a/themes/default-dark.css
+++ b/themes/default-dark.css
@@ -52,7 +52,7 @@
background-color: var(--color3);
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--color4);
}
diff --git a/themes/default.css b/themes/default.css
index 9d8b1e2..5250ede 100644
--- a/themes/default.css
+++ b/themes/default.css
@@ -52,7 +52,7 @@
background-color: var(--color2);
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--color4);
}
diff --git a/themes/ivy.css b/themes/ivy.css
index b053ba0..d36deef 100644
--- a/themes/ivy.css
+++ b/themes/ivy.css
@@ -73,7 +73,7 @@
background-color: var(--bcolor2);
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--bcolor4);
}
diff --git a/themes/kjoe.css b/themes/kjoe.css
index 221a6e5..ad746bc 100644
--- a/themes/kjoe.css
+++ b/themes/kjoe.css
@@ -72,7 +72,7 @@
background-color: var(--color3);
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--color2);
}
diff --git a/themes/monokai-vibrant.css b/themes/monokai-vibrant.css
index 4982e0b..59d68c3 100644
--- a/themes/monokai-vibrant.css
+++ b/themes/monokai-vibrant.css
@@ -55,7 +55,7 @@
background-color: var(--bcolor2);
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--bcolor3);
}
diff --git a/themes/rainbow.css b/themes/rainbow.css
index 5f53b52..88ed0d5 100644
--- a/themes/rainbow.css
+++ b/themes/rainbow.css
@@ -76,7 +76,7 @@
background-color: var(--bcolor2);
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--bcolor4);
}
diff --git a/themes/spring.css b/themes/spring.css
index ef623ae..5730598 100644
--- a/themes/spring.css
+++ b/themes/spring.css
@@ -79,7 +79,7 @@
font-family: "Lora", serif;
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--bcolor3);
}
diff --git a/themes/steam.css b/themes/steam.css
index 20b14cb..e51094b 100644
--- a/themes/steam.css
+++ b/themes/steam.css
@@ -80,7 +80,7 @@
background-color: var(--color4);
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--color3);
}
diff --git a/themes/summer.css b/themes/summer.css
index 2b10662..e3f927a 100644
--- a/themes/summer.css
+++ b/themes/summer.css
@@ -80,7 +80,7 @@
font-family: "Roboto", sans-serif;
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--bcolor3);
color: var(--bcolor2);
}
diff --git a/themes/sunflower.css b/themes/sunflower.css
index 0b40435..c86774d 100644
--- a/themes/sunflower.css
+++ b/themes/sunflower.css
@@ -74,7 +74,7 @@
background-color: var(--bcolor2);
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--bcolor4);
}
diff --git a/themes/winter.css b/themes/winter.css
index 42c58b4..1bdc3a3 100644
--- a/themes/winter.css
+++ b/themes/winter.css
@@ -88,7 +88,7 @@
font-family: "Montserrat", sans-serif;
}
-.tagentry:hover {
+.tagentry > label:hover {
background-color: var(--color2);
}