mirror of
https://github.com/greflm13/StaticGalleryBuilder.git
synced 2026-02-05 02:59:27 +00:00
added automatic dark theme detection and dark mode switch
This commit is contained in:
97
builder.py
97
builder.py
@@ -18,11 +18,7 @@ from modules.argumentparser import parse_arguments, Args
|
||||
|
||||
# fmt: off
|
||||
# Constants
|
||||
if __package__ is None:
|
||||
PACKAGE = ""
|
||||
else:
|
||||
PACKAGE = __package__
|
||||
SCRIPTDIR = os.path.dirname(os.path.realpath(__file__).removesuffix(PACKAGE))
|
||||
SCRIPTDIR = os.path.dirname(os.path.realpath(__file__)).removesuffix(__package__ if __package__ else "")
|
||||
STATIC_FILES_DIR = os.path.join(os.path.abspath(SCRIPTDIR), "files")
|
||||
VERSION = open(os.path.join(SCRIPTDIR, ".version"), "r", encoding="utf-8").read()
|
||||
RAW_EXTENSIONS = [
|
||||
@@ -75,7 +71,48 @@ def init_globals(_args: Args, raw: list[str]) -> tuple[Args, list[str]]:
|
||||
return _args, raw
|
||||
|
||||
|
||||
def copy_static_files(_args: Args) -> None:
|
||||
def handle_theme_icon(themepath: str, dest: str) -> None:
|
||||
"""
|
||||
Handle the icon specified in the theme file.
|
||||
"""
|
||||
logger.info("reading theme file", extra={"theme": themepath})
|
||||
with open(themepath, "r", encoding="utf-8") as f:
|
||||
theme = f.read()
|
||||
split = theme.split(".foldericon {")
|
||||
split2 = split[1].split("}", maxsplit=1)
|
||||
themehead = split[0]
|
||||
themetail = split2[1]
|
||||
foldericon = split2[0].strip()
|
||||
foldericon = re.sub(r"/\*.*\*/", "", foldericon)
|
||||
|
||||
for match in re.finditer(r"content: (.*);", foldericon):
|
||||
foldericon = match[1]
|
||||
foldericon = foldericon.replace('"', "")
|
||||
logger.info("found foldericon", extra={"foldericon": foldericon})
|
||||
break
|
||||
|
||||
if "url" in foldericon:
|
||||
logger.info("foldericon in theme file, using it")
|
||||
shutil.copyfile(themepath, dest)
|
||||
else:
|
||||
with open(os.path.join(SCRIPTDIR, foldericon), "r", encoding="utf-8") as f:
|
||||
logger.info("Reading foldericon svg")
|
||||
svg = f.read()
|
||||
|
||||
if "svg.j2" in foldericon:
|
||||
logger.info("foldericon in theme file is a jinja2 template")
|
||||
colorscheme = extract_colorscheme(themepath)
|
||||
for color_key, color_value in colorscheme.items():
|
||||
svg = svg.replace(f"{{{{ {color_key} }}}}", color_value)
|
||||
logger.info("replaced colors in svg")
|
||||
|
||||
svg = urllib.parse.quote(svg)
|
||||
with open(dest, "x", encoding="utf-8") as f:
|
||||
logger.info("writing theme file")
|
||||
f.write(themehead + '\n.foldericon {\n content: url("data:image/svg+xml,' + svg + '");\n}\n' + themetail)
|
||||
|
||||
|
||||
def copy_static_files(_args: Args) -> bool:
|
||||
"""
|
||||
Copy static files to the root directory.
|
||||
|
||||
@@ -85,6 +122,7 @@ def copy_static_files(_args: Args) -> None:
|
||||
Parsed command-line arguments.
|
||||
"""
|
||||
static_dir = os.path.join(_args.root_directory, ".static")
|
||||
darktheme = False
|
||||
if os.path.exists(static_dir):
|
||||
print("Removing existing .static folder...")
|
||||
logger.info("removing existing .static folder")
|
||||
@@ -93,42 +131,21 @@ def copy_static_files(_args: Args) -> None:
|
||||
print("Copying static files...")
|
||||
logger.info("copying static files")
|
||||
shutil.copytree(STATIC_FILES_DIR, static_dir, dirs_exist_ok=True)
|
||||
logger.info("reading theme file", extra={"theme": _args.theme_path})
|
||||
with open(_args.theme_path, "r", encoding="utf-8") as f:
|
||||
theme = f.read()
|
||||
split = theme.split(".foldericon {")
|
||||
split2 = split[1].split("}", maxsplit=1)
|
||||
themehead = split[0]
|
||||
themetail = split2[1]
|
||||
foldericon = split2[0].strip()
|
||||
foldericon = re.sub(r"/\*.*\*/", "", foldericon)
|
||||
for match in re.finditer(r"content: (.*);", foldericon):
|
||||
foldericon = match[1]
|
||||
foldericon = foldericon.replace('"', "")
|
||||
logger.info("found foldericon", extra={"foldericon": foldericon})
|
||||
break
|
||||
if "url" in foldericon:
|
||||
logger.info("foldericon in theme file, using it")
|
||||
shutil.copyfile(_args.theme_path, os.path.join(static_dir, "theme.css"))
|
||||
return
|
||||
with open(os.path.join(SCRIPTDIR, foldericon), "r", encoding="utf-8") as f:
|
||||
logger.info("Reading foldericon svg")
|
||||
svg = f.read()
|
||||
if "svg.j2" in foldericon:
|
||||
logger.info("foldericon in theme file is a jinja2 template")
|
||||
colorscheme = extract_colorscheme(_args.theme_path)
|
||||
for color_key, color_value in colorscheme.items():
|
||||
svg = svg.replace(f"{{{{ {color_key} }}}}", color_value)
|
||||
logger.info("replaced colors in svg")
|
||||
svg = urllib.parse.quote(svg)
|
||||
with open(os.path.join(static_dir, "theme.css"), "x", encoding="utf-8") as f:
|
||||
logger.info("writing theme file")
|
||||
f.write(themehead + '\n.foldericon {\n content: url("data:image/svg+xml,' + svg + '");\n}\n' + themetail)
|
||||
|
||||
theme = os.path.splitext(os.path.abspath(_args.theme_path))[0]
|
||||
darktheme_path = f"{theme}-dark.css"
|
||||
if os.path.exists(darktheme_path):
|
||||
handle_theme_icon(darktheme_path, os.path.join(static_dir, "theme-dark.css"))
|
||||
darktheme = True
|
||||
handle_theme_icon(_args.theme_path, os.path.join(static_dir, "theme.css"))
|
||||
|
||||
logger.info("minifying javascript")
|
||||
with open(os.path.join(SCRIPTDIR, "templates", "functionality.js"), "r", encoding="utf-8") as js_file:
|
||||
with open(os.path.join(static_dir, "functionality.min.js"), "w+", encoding="utf-8") as min_file:
|
||||
min_file.write(jsmin(js_file.read()))
|
||||
|
||||
return darktheme
|
||||
|
||||
|
||||
def generate_thumbnail(arguments: tuple[str, str, str]) -> None:
|
||||
"""
|
||||
@@ -201,7 +218,7 @@ def main(args) -> None:
|
||||
shutil.rmtree(thumbdir)
|
||||
os.makedirs(thumbdir, exist_ok=True)
|
||||
|
||||
copy_static_files(args)
|
||||
darktheme = copy_static_files(args)
|
||||
icons(args)
|
||||
|
||||
if args.generate_webmanifest:
|
||||
@@ -211,13 +228,13 @@ def main(args) -> None:
|
||||
if args.non_interactive_mode:
|
||||
logger.info("generating HTML files")
|
||||
print("Generating HTML files...")
|
||||
thumbnails = list_folder(args.root_directory, args.site_title, args, raw, VERSION, logo)
|
||||
thumbnails = list_folder(args.root_directory, args.site_title, args, raw, VERSION, logo, darktheme=darktheme)
|
||||
with Pool(os.cpu_count()) as pool:
|
||||
logger.info("generating thumbnails")
|
||||
print("Generating thumbnails...")
|
||||
pool.map(generate_thumbnail, thumbnails)
|
||||
else:
|
||||
thumbnails = list_folder(args.root_directory, args.site_title, args, raw, VERSION, logo)
|
||||
thumbnails = list_folder(args.root_directory, args.site_title, args, raw, VERSION, logo, darktheme=darktheme)
|
||||
|
||||
with Pool(os.cpu_count()) as pool:
|
||||
logger.info("generating thumbnails")
|
||||
|
||||
@@ -77,10 +77,6 @@ figure {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.licensefile {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.caption {
|
||||
padding-top: 4px;
|
||||
text-align: center;
|
||||
@@ -93,7 +89,6 @@ figure {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
font-size: xx-small;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
@@ -102,7 +97,8 @@ figure {
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding: 6px;
|
||||
min-height: calc(6.75pt + 12px);
|
||||
height: calc(9.75pt + 12px);
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
@@ -110,7 +106,7 @@ figure {
|
||||
}
|
||||
|
||||
.footer a img {
|
||||
height: 22px !important;
|
||||
height: 9.75pt !important;
|
||||
margin-left: 3px;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
@@ -140,16 +136,19 @@ figure {
|
||||
|
||||
.navbar .navleft {
|
||||
float: left;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.navbar .navcenter {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.navbar .navright {
|
||||
float: right
|
||||
float: right;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.navbar li .header {
|
||||
@@ -303,6 +302,54 @@ input {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
#dark-mode-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#dark-mode-switch .checkbox {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#dark-mode-switch .knobs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: 1em;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#dark-mode-switch .light,
|
||||
#dark-mode-switch .dark {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#dark-mode-switch .slider {
|
||||
position: absolute;
|
||||
width: calc(2em - 2px);
|
||||
height: calc(2em - 2px);
|
||||
border: 1px solid currentColor;
|
||||
border-radius: 3px;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: left 0.25s ease, transform 0.25s ease;
|
||||
left: calc(50% - 1em + 1px);
|
||||
}
|
||||
|
||||
#dark-mode-switch .checkbox:checked+.knobs .slider {
|
||||
left: calc(50% + 1em - 1px);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.column {
|
||||
-ms-flex: 25%;
|
||||
@@ -310,6 +357,14 @@ input {
|
||||
max-width: 25%;
|
||||
}
|
||||
|
||||
.footer {
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.footer a img {
|
||||
height: 9.75pt !important;
|
||||
}
|
||||
|
||||
.folders figure {
|
||||
width: 160px;
|
||||
}
|
||||
@@ -343,6 +398,14 @@ input {
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.footer {
|
||||
font-size: x-small;
|
||||
}
|
||||
|
||||
.footer a img {
|
||||
height: 7.5pt !important;
|
||||
}
|
||||
|
||||
.folders figure {
|
||||
width: 140px;
|
||||
}
|
||||
@@ -384,6 +447,14 @@ input {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.footer {
|
||||
font-size: xx-small;
|
||||
}
|
||||
|
||||
.footer a img {
|
||||
height: 6.75pt !important;
|
||||
}
|
||||
|
||||
.folders figure {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
@@ -12,11 +12,7 @@ except ModuleNotFoundError:
|
||||
RICH = False
|
||||
|
||||
|
||||
if __package__ is None:
|
||||
PACKAGE = ""
|
||||
else:
|
||||
PACKAGE = __package__
|
||||
SCRIPTDIR = os.path.dirname(os.path.realpath(__file__).removesuffix(PACKAGE))
|
||||
SCRIPTDIR = os.path.dirname(os.path.realpath(__file__)).removesuffix(__package__)
|
||||
DEFAULT_THEME_PATH = os.path.join(SCRIPTDIR, "templates", "default.css")
|
||||
DEFAULT_AUTHOR = "Author"
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ def extract_colorscheme(theme_path: str) -> dict[str, str]:
|
||||
dictionary containing color scheme variables and their hexadecimal values.
|
||||
"""
|
||||
logger.info("extracting color scheme from theme file", extra={"theme_path": theme_path})
|
||||
pattern = r"--(color[1-4]|bcolor1):\s*(#[0-9a-fA-F]+|rgba?\([^)]*\)|hsla?\([^)]*\)|[a-zA-Z]+);"
|
||||
pattern = r"--(color\d+|bcolor\d+):\s*(#[0-9a-fA-F]+|rgba?\([^)]*\)|hsla?\([^)]*\)|[a-zA-Z]+);"
|
||||
colorscheme = {}
|
||||
|
||||
with open(theme_path, "r", encoding="utf-8") as f:
|
||||
|
||||
@@ -20,11 +20,7 @@ from modules.argumentparser import Args
|
||||
from modules.datatypes.metadata import Metadata, ImageMetadata, SubfolderMetadata
|
||||
|
||||
# Constants for file paths and exclusions
|
||||
if __package__ is None:
|
||||
PACKAGE = ""
|
||||
else:
|
||||
PACKAGE = __package__
|
||||
SCRIPTDIR = os.path.dirname(os.path.realpath(__file__).removesuffix(PACKAGE))
|
||||
SCRIPTDIR = os.path.dirname(os.path.realpath(__file__)).removesuffix(__package__)
|
||||
FAVICON_PATH = ".static/favicon.ico"
|
||||
GLOBAL_CSS_PATH = ".static/global.css"
|
||||
EXCLUDES = ["index.html", "manifest.json", "robots.txt"]
|
||||
@@ -393,7 +389,7 @@ def process_image(item: str, folder: str, _args: Args, baseurl: str, metadata: M
|
||||
return image, metadata
|
||||
|
||||
|
||||
def generate_html(folder: str, title: str, _args: Args, raw: list[str], version: str, logo: str) -> set[str]:
|
||||
def generate_html(folder: str, title: str, _args: Args, raw: list[str], version: str, logo: str, darktheme: bool = False) -> set[str]:
|
||||
"""
|
||||
Generates HTML content for a folder of images.
|
||||
|
||||
@@ -463,7 +459,7 @@ def generate_html(folder: str, title: str, _args: Args, raw: list[str], version:
|
||||
update_metadata(metadata, folder)
|
||||
|
||||
if should_generate_html(images, contains_files, _args):
|
||||
subfoldertags = create_html_file(folder, title, foldername, images, subfolders, _args, version, logo, subfoldertags)
|
||||
subfoldertags = create_html_file(folder, title, foldername, images, subfolders, _args, version, logo, subfoldertags, darktheme=darktheme)
|
||||
else:
|
||||
if os.path.exists(os.path.join(folder, "index.html")):
|
||||
logger.info("removing existing index.html", extra={"folder": folder})
|
||||
@@ -568,7 +564,16 @@ def format_html(html: str) -> str:
|
||||
|
||||
|
||||
def create_html_file(
|
||||
folder: str, title: str, foldername: str, images: list[ImageMetadata], subfolders: list[SubfolderMetadata], _args: Args, version: str, logo: str, subfoldertags: set[str]
|
||||
folder: str,
|
||||
title: str,
|
||||
foldername: str,
|
||||
images: list[ImageMetadata],
|
||||
subfolders: list[SubfolderMetadata],
|
||||
_args: Args,
|
||||
version: str,
|
||||
logo: str,
|
||||
subfoldertags: set[str],
|
||||
darktheme: bool = False,
|
||||
) -> set[str]:
|
||||
"""
|
||||
Creates the HTML file using the template.
|
||||
@@ -625,6 +630,7 @@ def create_html_file(
|
||||
favicon=f"{_args.web_root_url}{FAVICON_PATH}",
|
||||
stylesheet=f"{_args.web_root_url}{GLOBAL_CSS_PATH}",
|
||||
theme=f"{_args.web_root_url}.static/theme.css",
|
||||
darktheme=f"{_args.web_root_url}.static/theme-dark.css" if darktheme else None,
|
||||
root=_args.web_root_url,
|
||||
parent=f"{_args.web_root_url}{urllib.parse.quote(foldername)}",
|
||||
header=f"{header} - LICENSE",
|
||||
@@ -642,6 +648,7 @@ def create_html_file(
|
||||
favicon=f"{_args.web_root_url}{FAVICON_PATH}",
|
||||
stylesheet=f"{_args.web_root_url}{GLOBAL_CSS_PATH}",
|
||||
theme=f"{_args.web_root_url}.static/theme.css",
|
||||
darktheme=f"{_args.web_root_url}.static/theme-dark.css" if darktheme else None,
|
||||
root=_args.web_root_url,
|
||||
parent=parent,
|
||||
header=header,
|
||||
@@ -662,7 +669,7 @@ def create_html_file(
|
||||
return set(sorted(alltags))
|
||||
|
||||
|
||||
def list_folder(folder: str, title: str, _args: Args, raw: list[str], version: str, logo: str) -> list[tuple[str, str, str]]:
|
||||
def list_folder(folder: str, title: str, _args: Args, raw: list[str], version: str, logo: str, darktheme: bool = False) -> list[tuple[str, str, str]]:
|
||||
"""
|
||||
lists and processes a folder, generating HTML files.
|
||||
|
||||
@@ -676,5 +683,5 @@ def list_folder(folder: str, title: str, _args: Args, raw: list[str], version: s
|
||||
Returns:
|
||||
list[tuple[str, str]]: list of thumbnails generated.
|
||||
"""
|
||||
generate_html(folder, title, _args, raw, version, logo)
|
||||
generate_html(folder, title, _args, raw, version, logo, darktheme=darktheme)
|
||||
return thumbnails
|
||||
|
||||
@@ -21,11 +21,7 @@ from datetime import datetime
|
||||
from pythonjsonlogger import jsonlogger
|
||||
|
||||
# Constants for file paths and exclusions
|
||||
if __package__ is None:
|
||||
PACKAGE = ""
|
||||
else:
|
||||
PACKAGE = __package__
|
||||
SCRIPTDIR = os.path.dirname(os.path.realpath(__file__).removesuffix(PACKAGE))
|
||||
SCRIPTDIR = os.path.dirname(os.path.realpath(__file__)).removesuffix(__package__)
|
||||
LOG_DIR = os.path.join(SCRIPTDIR, "logs")
|
||||
LATEST_LOG_FILE = os.path.join(LOG_DIR, "latest.jsonl")
|
||||
|
||||
|
||||
@@ -18,11 +18,7 @@ from modules.argumentparser import Args
|
||||
from modules.css_color import extract_theme_color, extract_colorscheme
|
||||
|
||||
# Define constants for static files directory and icon sizes
|
||||
if __package__ is None:
|
||||
PACKAGE = ""
|
||||
else:
|
||||
PACKAGE = __package__
|
||||
SCRIPTDIR = os.path.dirname(os.path.realpath(__file__).removesuffix(PACKAGE))
|
||||
SCRIPTDIR = os.path.dirname(os.path.realpath(__file__)).removesuffix(__package__)
|
||||
STATIC_FILES_DIR = os.path.join(SCRIPTDIR, "files")
|
||||
ICON_SIZES = ["36x36", "48x48", "72x72", "96x96", "144x144", "192x192", "512x512"]
|
||||
|
||||
|
||||
111
templates/default-dark.css
Normal file
111
templates/default-dark.css
Normal file
@@ -0,0 +1,111 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap");
|
||||
|
||||
* {
|
||||
--color1: #262a2b;
|
||||
--color2: #0d0e0e;
|
||||
--color3: #313537;
|
||||
--color4: #181a1b;
|
||||
--color5: #5483ef;
|
||||
--bcolor1: #e8e6e3;
|
||||
--bcolor2: #0c0d0e;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
font-weight: bold;
|
||||
color: var(--bcolor1);
|
||||
background-color: var(--color1);
|
||||
}
|
||||
|
||||
.navbar li a {
|
||||
font-weight: bold;
|
||||
color: var(--bcolor1);
|
||||
}
|
||||
|
||||
/* Change the link color on hover */
|
||||
.navbar li a:hover {
|
||||
background-color: var(--color2);
|
||||
}
|
||||
|
||||
.footer {
|
||||
color: var(--bcolor1);
|
||||
background-color: var(--color3);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: var(--color5);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.foldericon {
|
||||
content: "themes/icons/folder-2.svg.j2";
|
||||
}
|
||||
|
||||
.folders a {
|
||||
font-weight: 700;
|
||||
color: var(--color5);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tooltiptext {
|
||||
font-weight: 400;
|
||||
background-color: var(--color3);
|
||||
}
|
||||
|
||||
.tagentry label:hover {
|
||||
background-color: var(--color4);
|
||||
}
|
||||
|
||||
.tagentry .tagtoggle:hover {
|
||||
background-color: var(--color4);
|
||||
}
|
||||
|
||||
.column img {
|
||||
background-color: var(--bcolor2);
|
||||
}
|
||||
|
||||
#totop:hover {
|
||||
background-color: var(--color2);
|
||||
}
|
||||
|
||||
#totop {
|
||||
background-color: var(--color1);
|
||||
color: var(--bcolor1);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.loader {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
border-top: 3px solid var(--bcolor1);
|
||||
border-right: 3px solid transparent;
|
||||
box-sizing: border-box;
|
||||
animation: rotation 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotation {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--bcolor1);
|
||||
background-color: var(--color4);
|
||||
font-family: "Ubuntu", sans-serif;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
body a {
|
||||
font-weight: 400;
|
||||
color: var(--color5);
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -24,6 +24,10 @@ class PhotoGallery {
|
||||
this.scrollFunction = this.scrollFunction.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.init();
|
||||
}
|
||||
@@ -38,12 +42,7 @@ class PhotoGallery {
|
||||
|
||||
openSwipe(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();
|
||||
}
|
||||
|
||||
@@ -78,9 +77,7 @@ class PhotoGallery {
|
||||
|
||||
if (folders) folders.style.display = "";
|
||||
document.getElementById("recursive").checked = false;
|
||||
document
|
||||
.querySelectorAll("#tagdropdown input.tagcheckbox:checked")
|
||||
.forEach((checkbox) => (checkbox.checked = false));
|
||||
document.querySelectorAll("#tagdropdown input.tagcheckbox:checked").forEach((checkbox) => (checkbox.checked = false));
|
||||
window.history.replaceState({ html: content, pageTitle: title }, "", path);
|
||||
this.requestMetadata();
|
||||
}
|
||||
@@ -150,10 +147,9 @@ class PhotoGallery {
|
||||
existingItems.add(image.src);
|
||||
}
|
||||
}
|
||||
if (Array.isArray(data.subfolders))
|
||||
nextLevel.push(...data.subfolders);
|
||||
if (Array.isArray(data.subfolders)) nextLevel.push(...data.subfolders);
|
||||
} catch {}
|
||||
})
|
||||
}),
|
||||
);
|
||||
if (nextLevel.length > 0) await fetchFoldersRecursively(nextLevel);
|
||||
};
|
||||
@@ -194,21 +190,15 @@ class PhotoGallery {
|
||||
filter() {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
this.shown = [];
|
||||
let path = decodeURIComponent(
|
||||
window.location.origin +
|
||||
window.location.pathname.replace("index.html", "")
|
||||
);
|
||||
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) => {
|
||||
document.querySelectorAll("#tagdropdown input.tagcheckbox:checked").forEach((checkbox) => {
|
||||
let tag = checkbox.parentElement.id.trim().substring(1);
|
||||
if (checkbox.parentElement.parentElement.children.length > 1)
|
||||
tag += "|";
|
||||
if (checkbox.parentElement.parentElement.children.length > 1) tag += "|";
|
||||
selectedTags.push(tag);
|
||||
});
|
||||
|
||||
@@ -216,17 +206,14 @@ class PhotoGallery {
|
||||
|
||||
let isRecursiveChecked = false;
|
||||
try {
|
||||
isRecursiveChecked =
|
||||
document.getElementById("recursive")?.checked || false;
|
||||
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);
|
||||
return isParent ? tags.some((t) => t.startsWith(selected)) : tags.includes(selected);
|
||||
});
|
||||
|
||||
if (include || selectedTags.length === 0) {
|
||||
@@ -313,9 +300,7 @@ class PhotoGallery {
|
||||
let str = "";
|
||||
this.shown.forEach((item, index) => {
|
||||
let tags = this.parseHierarchicalTags(item.tags || []);
|
||||
str += `<div class="column"><figure title="${this.renderTree(
|
||||
tags
|
||||
)}"><img src="${
|
||||
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 += ` <a href="${item.tiff}">TIFF</a>`;
|
||||
@@ -328,16 +313,9 @@ class PhotoGallery {
|
||||
}
|
||||
|
||||
setFilter(selected) {
|
||||
document
|
||||
.querySelectorAll("#tagdropdown input.tagcheckbox")
|
||||
.forEach((checkbox) => {
|
||||
document.querySelectorAll("#tagdropdown input.tagcheckbox").forEach((checkbox) => {
|
||||
selected.forEach((tag) => {
|
||||
if (
|
||||
checkbox.parentElement.id
|
||||
.trim()
|
||||
.substring(1)
|
||||
.replace(" ", "%20") === tag
|
||||
) {
|
||||
if (checkbox.parentElement.id.trim().substring(1).replace(" ", "%20") === tag) {
|
||||
checkbox.checked = true;
|
||||
}
|
||||
});
|
||||
@@ -350,9 +328,7 @@ class PhotoGallery {
|
||||
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)";
|
||||
svg.style.transform = ol.classList.contains("show") ? "rotate(180deg)" : "rotate(0deg)";
|
||||
}
|
||||
|
||||
setupDropdownToggle() {
|
||||
@@ -364,18 +340,12 @@ class PhotoGallery {
|
||||
event.stopPropagation();
|
||||
const svg = toggleLink.querySelector("svg");
|
||||
dropdown.classList.toggle("show");
|
||||
if (svg)
|
||||
svg.style.transform = dropdown.classList.contains("show")
|
||||
? "rotate(180deg)"
|
||||
: "rotate(0deg)";
|
||||
if (svg) svg.style.transform = dropdown.classList.contains("show") ? "rotate(180deg)" : "rotate(0deg)";
|
||||
this.tagDropdownShown = dropdown.classList.contains("show");
|
||||
});
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
if (
|
||||
!dropdown.contains(event.target) &&
|
||||
!toggleLink.contains(event.target)
|
||||
) {
|
||||
if (!dropdown.contains(event.target) && !toggleLink.contains(event.target)) {
|
||||
dropdown.classList.remove("show");
|
||||
this.tagDropdownShown = false;
|
||||
const svg = toggleLink.querySelector("svg");
|
||||
@@ -402,18 +372,18 @@ class PhotoGallery {
|
||||
}
|
||||
|
||||
setupClickHandlers() {
|
||||
const resetEl = document
|
||||
.getElementById("reset-filter")
|
||||
?.querySelector("label");
|
||||
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));
|
||||
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) => {
|
||||
@@ -442,10 +412,7 @@ class PhotoGallery {
|
||||
scrollFunction() {
|
||||
const totopbutton = document.getElementById("totop");
|
||||
if (!totopbutton) return;
|
||||
if (
|
||||
document.body.scrollTop > 20 ||
|
||||
document.documentElement.scrollTop > 20
|
||||
) {
|
||||
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
|
||||
totopbutton.style.display = "block";
|
||||
} else {
|
||||
totopbutton.style.display = "none";
|
||||
@@ -456,6 +423,53 @@ class PhotoGallery {
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
|
||||
darkMode() {
|
||||
const themeLink = document.getElementById("theme");
|
||||
const darkThemeLink = document.getElementById("darktheme");
|
||||
if (themeLink) themeLink.disabled = true;
|
||||
if (darkThemeLink) darkThemeLink.disabled = false;
|
||||
}
|
||||
|
||||
lightMode() {
|
||||
const themeLink = document.getElementById("theme");
|
||||
const darkThemeLink = document.getElementById("darktheme");
|
||||
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")) {
|
||||
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
this.darkModeToggle("dark");
|
||||
} else {
|
||||
this.darkModeToggle("light");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onLoad() {
|
||||
document.querySelectorAll(".tagtoggle").forEach((toggle) => {
|
||||
toggle.addEventListener("mouseup", (event) => {
|
||||
@@ -469,6 +483,7 @@ class PhotoGallery {
|
||||
this.setupDropdownToggle();
|
||||
this.setupTagHandlers();
|
||||
this.setupClickHandlers();
|
||||
this.detectDarkMode();
|
||||
|
||||
window.addEventListener("scroll", this.scrollFunction);
|
||||
}
|
||||
|
||||
@@ -34,10 +34,16 @@
|
||||
{%- if theme %}
|
||||
<link rel="preload" href="{{ theme }}" as="style">
|
||||
{%- endif %}
|
||||
{%- if darktheme %}
|
||||
<link rel="preload" href="{{ darktheme }}" as="style">
|
||||
{%- endif %}
|
||||
<link rel="icon" type="image/x-icon" href="{{ favicon }}">
|
||||
<link rel="stylesheet" href="{{ stylesheet }}">
|
||||
{%- if theme %}
|
||||
<link rel="stylesheet" href="{{ theme }}">
|
||||
<link rel="stylesheet" href="{{ theme }}" id="theme">
|
||||
{%- endif %}
|
||||
{%- if darktheme %}
|
||||
<link rel="stylesheet" href="{{ darktheme }}" id="darktheme" disabled>
|
||||
{%- endif %}
|
||||
<link rel="preload" href="{{ root }}.static/pswp/photoswipe.css" as="style">
|
||||
<link rel="preload" href="{{ root }}.static/pswp/default-skin/default-skin.css" as="style">
|
||||
@@ -89,10 +95,22 @@
|
||||
{{ render_tags(tags, '') }}
|
||||
</ol>
|
||||
</li>
|
||||
{% endif %}
|
||||
{%- endif %}
|
||||
{%- if licensefile %}
|
||||
<li class="license"><a href="{{ licensefile }}">License</a></li>
|
||||
{%- endif %}
|
||||
{%- if darktheme %}
|
||||
<li>
|
||||
<a class="button" id="dark-mode-switch">
|
||||
<input type="checkbox" class="checkbox" id="dark-mode-switch-check" />
|
||||
<div class="knobs">
|
||||
<span class="light">☀︎</span>
|
||||
<span class="slider"></span>
|
||||
<span class="dark">☽</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
{%- endif %}
|
||||
</div>
|
||||
</ol>
|
||||
{% if subdirectories %}
|
||||
@@ -120,7 +138,7 @@
|
||||
<a property="dct:title" rel="cc:attributionURL" href="{{ root }}">{{ license.project }}</a> by <span property="cc:attributionName">{{ license.author }}</span> is marked with
|
||||
<a href="{{ license.url }}" target="_blank" rel="license noopener noreferrer" style="display: inline-block">CC0 1.0
|
||||
{%- for pic in license.pics %}
|
||||
<img style="height: 22px !important; margin-left: 3px; vertical-align: text-bottom" src="{{ pic }}" alt="" />
|
||||
<img src="{{ pic }}" alt="" />
|
||||
{%- endfor %}
|
||||
</a>
|
||||
{%- else %}
|
||||
|
||||
2
themes
2
themes
Submodule themes updated: b98012752f...51696f2a4d
Reference in New Issue
Block a user