From 372a2a91bcf16fc4d3f47e49449329dcec62b347 Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Tue, 14 Apr 2026 22:30:58 +0200 Subject: [PATCH 1/3] chore(tools): add a script to populate static/js/core/icons.js --- .gitignore | 5 +- tools/check-icons.py | 181 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 tools/check-icons.py diff --git a/.gitignore b/.gitignore index 1467894e..fdddd636 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,9 @@ .cargo/* !.cargo/config.toml +# temporary file to tools +tmp/ + # Visual Studio Code directory .vscode/ @@ -82,4 +85,4 @@ nohup.out resources/gen/ # Helm chart dependencies -charts/*/charts/* \ No newline at end of file +charts/*/charts/* diff --git a/tools/check-icons.py b/tools/check-icons.py new file mode 100644 index 00000000..01426bc0 --- /dev/null +++ b/tools/check-icons.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +""" +check-icons.py — Audit FA icon usage against the inline SVG registry. + +Usage: + python3 tools/check-icons.py [--dry-run] + +What it does: + 1. Scans every file under static/ for fas fa- occurrences. + 2. Reads the _ICONS registry from static/js/core/icons.js. + 3. For each icon name that is missing from the registry, looks up + tmp/Font-Awesome/svgs/solid/.svg + and adds the entry '': [, ''] to _ICONS. + 4. Rewrites icons.js in-place (unless --dry-run is given). + +FontAwesome source: tmp/Font-Awesome/svgs/solid/ +Icons registry: static/js/core/icons.js +""" + +import re +import subprocess +import sys +import os +from pathlib import Path + +# ── Paths ────────────────────────────────────────────────────────────────────── +REPO_ROOT = Path(__file__).resolve().parent.parent +STATIC_DIR = REPO_ROOT / "static" +ICONS_JS = STATIC_DIR / "js" / "core" / "icons.js" +FA_SVG_DIR = REPO_ROOT / "tmp" / "Font-Awesome" / "svgs" / "solid" + +DRY_RUN = "--dry-run" in sys.argv + +# ── 0. Ensure Font-Awesome source is available ──────────────────────────────── +TMP_DIR = REPO_ROOT / "tmp" +if not TMP_DIR.exists(): + print(f"Creating {TMP_DIR.relative_to(REPO_ROOT)}/") + TMP_DIR.mkdir(parents=True, exist_ok=True) + +FA_REPO = TMP_DIR / "Font-Awesome" +if not FA_REPO.exists(): + print(f"Font-Awesome not found at {FA_REPO.relative_to(REPO_ROOT)} — cloning …") + result = subprocess.run( + ["git", "clone", "https://github.com/FortAwesome/Font-Awesome.git", str(FA_REPO)], + check=False, + ) + if result.returncode != 0: + print("✗ git clone failed — cannot continue without Font-Awesome source.") + sys.exit(1) + print("✓ Font-Awesome cloned successfully.\n") + +# ── 1. Scan static/ for all fas fa- occurrences ─────────────────────── +FA_RE = re.compile(r'\bfas fa-([\w-]+)') + +used_icons: dict[str, list[str]] = {} # name → [file, …] + +SKIP_DIRS = {".git", "node_modules"} + +for path in sorted(STATIC_DIR.rglob("*")): + if any(part in SKIP_DIRS for part in path.parts): + continue + if not path.is_file(): + continue + # Only scan text files we care about + if path.suffix not in {".html", ".js", ".css"}: + continue + try: + text = path.read_text(encoding="utf-8", errors="replace") + except OSError: + continue + for m in FA_RE.finditer(text): + name = m.group(1) + rel = str(path.relative_to(REPO_ROOT)) + used_icons.setdefault(name, []).append(rel) + +print(f"Found {len(used_icons)} distinct FA icon(s) referenced in static/") + +# ── 2. Parse existing _ICONS keys from icons.js ─────────────────────────────── +icons_src = ICONS_JS.read_text(encoding="utf-8") + +# Extract only the _ICONS object body so we never accidentally match keys from +# other objects or functions elsewhere in the file. +_ICONS_BLOCK_RE = re.compile(r'const _ICONS\s*=\s*\{(.*?)^};', re.DOTALL | re.MULTILINE) +block_match = _ICONS_BLOCK_RE.search(icons_src) +if not block_match: + print("✗ Could not locate 'const _ICONS = { … };' in icons.js — aborting.") + sys.exit(1) +icons_block = block_match.group(1) + +# Now parse keys only within that block. +# Keys may be quoted ('bars', "bars") or bare (bars) — make the quotes optional. +# Hyphenated names like 'arrow-left' must be quoted in JS; bare keys are word-only. +ICON_KEY_RE = re.compile(r"""['"]?([\w-]+)['"]?\s*:\s*\[""") +registered: set[str] = {m.group(1) for m in ICON_KEY_RE.finditer(icons_block)} + +print(f"Registry has {len(registered)} icon(s) in _ICONS") + +# ── 3. Find missing icons ───────────────────────────────────────────────────── +missing = {name: files for name, files in used_icons.items() if name not in registered} + +if not missing: + print("✓ All used icons are present in the registry — nothing to do.") + sys.exit(0) + +print(f"\n{len(missing)} missing icon(s):") + +# ── 4. Resolve each missing icon from FA SVG files ──────────────────────────── +VIEWBOX_RE = re.compile(r'viewBox="0 0 (\d+) (\d+)"') +PATH_D_RE = re.compile(r']+\bd="([^"]+)"') + +new_entries: list[tuple[str, int, str]] = [] # (name, width, d) +not_found: list[str] = [] + +for name, files in sorted(missing.items()): + svg_path = FA_SVG_DIR / f"{name}.svg" + print(f" • {name:30s} used in: {', '.join(files)}", end="") + + if not svg_path.exists(): + print(f" ✗ SVG not found: {svg_path.relative_to(REPO_ROOT)}") + not_found.append(name) + continue + + svg_text = svg_path.read_text(encoding="utf-8") + + vb = VIEWBOX_RE.search(svg_text) + pd = PATH_D_RE.search(svg_text) + + if not vb or not pd: + print(f" ✗ Could not parse SVG (viewBox={bool(vb)}, path={bool(pd)})") + not_found.append(name) + continue + + width = int(vb.group(1)) + d = pd.group(1) + print(f" ✓ viewBox=0 0 {width} 512") + new_entries.append((name, width, d)) + +# ── 5. Patch icons.js ───────────────────────────────────────────────────────── +if not new_entries: + if not_found: + print(f"\n✗ {len(not_found)} icon(s) could not be resolved — no changes written.") + sys.exit(1 if not_found else 0) + +# Build the text block to insert, sorted alphabetically for readability +new_entries.sort(key=lambda x: x[0]) + +insert_lines = [] +for name, width, d in new_entries: + insert_lines.append(f" '{name}': [\n {width},\n '{d}'\n ],") + +insert_block = "\n".join(insert_lines) + "\n" + +# Insert just before the closing "};" of _ICONS (line 396 area) +# Anchor: the line that is exactly "};" +ICONS_END_RE = re.compile(r'^};$', re.MULTILINE) +m = ICONS_END_RE.search(icons_src) +if not m: + print("\n✗ Could not locate the closing '}; ' of _ICONS in icons.js — aborting.") + sys.exit(1) + +# Ensure the last existing entry has a trailing comma before we append. +before = icons_src[: m.start()] +if before.rstrip()[-1:] != ',': + # Insert comma right after the last non-whitespace character + rstripped = before.rstrip() + trailing_ws = before[len(rstripped):] + before = rstripped + ',\n' + trailing_ws + +new_src = before + insert_block + icons_src[m.start() :] + +if DRY_RUN: + print(f"\n-- DRY RUN: would insert into icons.js --\n{insert_block}") +else: + ICONS_JS.write_text(new_src, encoding="utf-8") + print(f"\n✓ Added {len(new_entries)} icon(s) to {ICONS_JS.relative_to(REPO_ROOT)}") + +if not_found: + print(f"\n⚠ {len(not_found)} icon(s) still missing (no SVG source found):") + for n in not_found: + print(f" - {n}") + sys.exit(1) From 4c327b3fe1cbc0eb5e800cebad8a63439c017435 Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Tue, 14 Apr 2026 22:43:52 +0200 Subject: [PATCH 2/3] fix(icons): add missing referenced icons - icons added via tools/check-icons.py --- static/js/core/icons.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/static/js/core/icons.js b/static/js/core/icons.js index 41efa5a4..9437a3b5 100644 --- a/static/js/core/icons.js +++ b/static/js/core/icons.js @@ -388,6 +388,34 @@ const _ICONS = { vial: [ 512, 'M342.6 9.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l9.4 9.4L28.1 342.6C10.1 360.6 0 385 0 410.5L0 416c0 53 43 96 96 96l5.5 0c25.5 0 49.9-10.1 67.9-28.1L448 205.3l9.4 9.4c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-32-32-96-96-32-32zM205.3 256L352 109.3 402.7 160l-96 96-101.5 0z' + ], + camera: [ + 512, + 'M149.1 64.8L138.7 96 64 96C28.7 96 0 124.7 0 160L0 416c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-256c0-35.3-28.7-64-64-64l-74.7 0-10.4-31.2C356.4 45.2 338.1 32 317.4 32L194.6 32c-20.7 0-39 13.2-45.5 32.8zM256 192a96 96 0 1 1 0 192 96 96 0 1 1 0-192z' + ], + 'external-link-alt': [ + 512, + 'M290.4 19.8C295.4 7.8 307.1 0 320 0L480 0c17.7 0 32 14.3 32 32l0 160c0 12.9-7.8 24.6-19.8 29.6s-25.7 2.2-34.9-6.9L400 157.3 246.6 310.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L354.7 112 297.4 54.6c-9.2-9.2-11.9-22.9-6.9-34.9zM0 176c0-44.2 35.8-80 80-80l80 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-80 0c-8.8 0-16 7.2-16 16l0 256c0 8.8 7.2 16 16 16l256 0c8.8 0 16-7.2 16-16l0-80c0-17.7 14.3-32 32-32s32 14.3 32 32l0 80c0 44.2-35.8 80-80 80L80 512c-44.2 0-80-35.8-80-80L0 176z' + ], + 'grip-vertical': [ + 320, + 'M128 40c0-22.1-17.9-40-40-40L40 0C17.9 0 0 17.9 0 40L0 88c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48zm0 192c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48zM0 424l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40zM320 40c0-22.1-17.9-40-40-40L232 0c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48zM192 232l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40zM320 424c0-22.1-17.9-40-40-40l-48 0c-22.1 0-40 17.9-40 40l0 48c0 22.1 17.9 40 40 40l48 0c22.1 0 40-17.9 40-40l0-48z' + ], + 'level-up-alt': [ + 384, + 'M169.4 9.4c12.5-12.5 32.8-12.5 45.3 0l128 128c9.2 9.2 11.9 22.9 6.9 34.9S332.9 192 320 192l-64 0 0 160c0 88.4-71.6 160-160 160l-64 0c-17.7 0-32-14.3-32-32l0-64c0-17.7 14.3-32 32-32l64 0c17.7 0 32-14.3 32-32l0-160-64 0c-12.9 0-24.6-7.8-29.6-19.8s-2.2-25.7 6.9-34.9l128-128z' + ], + music: [ + 512, + 'M468 7c7.6 6.1 12 15.3 12 25l0 304c0 44.2-43 80-96 80s-96-35.8-96-80 43-80 96-80c11.2 0 22 1.6 32 4.6l0-116.7-224 49.8 0 206.3c0 44.2-43 80-96 80s-96-35.8-96-80 43-80 96-80c11.2 0 22 1.6 32 4.6L128 96c0-15 10.4-28 25.1-31.2l288-64c9.5-2.1 19.4 .2 27 6.3z' + ], + plus: [ + 448, + 'M256 64c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 160-160 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l160 0 0 160c0 17.7 14.3 32 32 32s32-14.3 32-32l0-160 160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-160 0 0-160z' + ], + 'volume-up': [ + 640, + 'M533.6 32.5c-10.3-8.4-25.4-6.8-33.8 3.5s-6.8 25.4 3.5 33.8C557.5 113.8 592 180.8 592 256s-34.5 142.2-88.7 186.3c-10.3 8.4-11.8 23.5-3.5 33.8s23.5 11.8 33.8 3.5C598.5 426.7 640 346.2 640 256S598.5 85.2 533.6 32.5zM473.1 107c-10.3-8.4-25.4-6.8-33.8 3.5s-6.8 25.4 3.5 33.8C475.3 170.7 496 210.9 496 256s-20.7 85.3-53.2 111.8c-10.3 8.4-11.8 23.5-3.5 33.8s23.5 11.8 33.8 3.5c43.2-35.2 70.9-88.9 70.9-149s-27.7-113.8-70.9-149zm-60.5 74.5c-10.3-8.4-25.4-6.8-33.8 3.5s-6.8 25.4 3.5 33.8C393.1 227.6 400 241 400 256s-6.9 28.4-17.7 37.3c-10.3 8.4-11.8 23.5-3.5 33.8s23.5 11.8 33.8 3.5C434.1 312.9 448 286.1 448 256s-13.9-56.9-35.4-74.5zM80 352l48 0 134.1 119.2c6.4 5.7 14.6 8.8 23.1 8.8 19.2 0 34.8-15.6 34.8-34.8l0-378.4c0-19.2-15.6-34.8-34.8-34.8-8.5 0-16.7 3.1-23.1 8.8L128 160 80 160c-26.5 0-48 21.5-48 48l0 96c0 26.5 21.5 48 48 48z' ] }; From 30fffbdd337bbd68a0c0671210c3cd8ae643fc2a Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Tue, 14 Apr 2026 23:59:18 +0200 Subject: [PATCH 3/3] refactor(music): use music icon rather svg --- static/index.html | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/static/index.html b/static/index.html index b6b034cc..599ea053 100644 --- a/static/index.html +++ b/static/index.html @@ -91,11 +91,7 @@ Photos