From 51e01139eddc46dbebb2f1f9248ca4e304e8f98c Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Wed, 30 Apr 2025 23:12:46 +0200 Subject: [PATCH] feat: rework home page file selector --- editor/public/index.html | 15 +- editor/public/static/css/index.css | 37 ++++ editor/public/static/images/film.svg | 148 ++++++++++++++ editor/public/static/images/series.svg | 188 ++++++++++++++++++ editor/public/static/js/index.js | 68 +++++-- editor/public/static/js/integrity_manager.mjs | 2 +- editor/server.py | 41 +++- 7 files changed, 473 insertions(+), 26 deletions(-) create mode 100644 editor/public/static/css/index.css create mode 100644 editor/public/static/images/film.svg create mode 100644 editor/public/static/images/series.svg diff --git a/editor/public/index.html b/editor/public/index.html index 63b9471..80bfce8 100644 --- a/editor/public/index.html +++ b/editor/public/index.html @@ -5,6 +5,7 @@ Metadata Editor + @@ -12,10 +13,16 @@

Metadata Editor

-
- - -
+ + +
+
+ + +
+
episode(s)
+
+
\ No newline at end of file diff --git a/editor/public/static/css/index.css b/editor/public/static/css/index.css new file mode 100644 index 0000000..0179dbd --- /dev/null +++ b/editor/public/static/css/index.css @@ -0,0 +1,37 @@ +#files { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(15em, 1fr)); + grid-auto-rows: 15em; + gap: 0.8em; + place-items: center; + + .file { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + width: 100%; + height: 100%; + text-decoration: none; + color: black; + font-family: inherit; + font-size: inherit; + padding: 0.4em; + border-radius: 1.2em; + + &:hover { + background-color: #f8f8f8; + } + + img { + width: 10em; + height: 10em; + } + + .title { + overflow-wrap: anywhere; + text-align: center; + font-weight: bold; + } + } +} \ No newline at end of file diff --git a/editor/public/static/images/film.svg b/editor/public/static/images/film.svg new file mode 100644 index 0000000..2c19dae --- /dev/null +++ b/editor/public/static/images/film.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/editor/public/static/images/series.svg b/editor/public/static/images/series.svg new file mode 100644 index 0000000..37603b6 --- /dev/null +++ b/editor/public/static/images/series.svg @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/editor/public/static/js/index.js b/editor/public/static/js/index.js index 71fd02d..0ecd6dd 100644 --- a/editor/public/static/js/index.js +++ b/editor/public/static/js/index.js @@ -1,31 +1,59 @@ -function addOptions(files) { - const select = document.getElementById("file-sel") - select.innerHTML = "" - const defaultOpt = document.createElement("option") - defaultOpt.innerText = "----- Select a file -----" - defaultOpt.value = "" - select.appendChild(defaultOpt) - files.sort().forEach(file => { - const option = document.createElement("option") - option.innerText = file - option.value = file - select.appendChild(option) - }) +function makeFilm(meta) { + const file = document.getElementById("film-template").cloneNode(true) + file.querySelector(".title").innerText = meta.title + return file } -function selectFile(event) { - const file = event.target.value - if (file !== "") { - const url = new URL("/edit/", window.location.origin) - url.searchParams.set("f", file) - window.location.href = url.href +function makeSeries(meta) { + const file = document.getElementById("series-template").cloneNode(true) + file.querySelector(".title").innerText = meta.title + file.querySelector(".episodes .num").innerText = meta.episodes + return file +} + +function makeFile(meta) { + let file + switch (meta.type) { + case "film": + file = makeFilm(meta) + break + case "series": + file = makeSeries(meta) + break + default: + throw new Error(`Invalid file type '${meta.type}'`) } + + file.title = meta.filename + file.id = null + file.classList.remove("template") + const url = new URL("/edit/", window.location.origin) + url.searchParams.set("f", meta.filename) + file.href = url.href + return file +} + +/** + * + * @param {object[]} files + */ +function addFiles(files) { + const list = document.getElementById("files") + list.innerHTML = "" + const filenames = files.map(meta => meta.filename) + // Copy array because sort changes it in place + Array.from(filenames).sort().forEach(filename => { + const i = filenames.indexOf(filename) + const meta = files[i] + const file = makeFile(meta) + list.appendChild(file) + }) } window.addEventListener("load", () => { fetch("/api/files").then(res => { return res.json() }).then(files => { - addOptions(files) + addFiles(files) }) }) \ No newline at end of file diff --git a/editor/public/static/js/integrity_manager.mjs b/editor/public/static/js/integrity_manager.mjs index cc568c0..4b9309c 100644 --- a/editor/public/static/js/integrity_manager.mjs +++ b/editor/public/static/js/integrity_manager.mjs @@ -316,7 +316,7 @@ export default class IntegrityManager { if (fields.flags.hearing_impaired) { name += " SDH" } - name += " : " + fields.type + name += " | " + fields.type break } return name diff --git a/editor/server.py b/editor/server.py index 2fbd994..d58da48 100644 --- a/editor/server.py +++ b/editor/server.py @@ -11,6 +11,7 @@ MAX_SIZE = 10e6 class MyHandler(SimpleHTTPRequestHandler): DATA_DIR = "metadata" + CACHE = {} def __init__(self, *args, **kwargs): super().__init__( @@ -55,7 +56,7 @@ class MyHandler(SimpleHTTPRequestHandler): def handle_api_get(self, path: str): self.log_message(f"API request at {path}") if path == "files": - files: list[str] = self.get_files() + files: list[str] = self.get_files_meta() self.send_json(files) elif path.startswith("file"): filename: str = path.split("/", 1)[1] @@ -108,6 +109,44 @@ class MyHandler(SimpleHTTPRequestHandler): return False return True + def get_files_meta(self): + files: list[str] = self.get_files() + files_meta: list[dict] = [] + + deleted = set(self.CACHE.keys()) - set(files) + for filename in deleted: + del self.CACHE[deleted] + + for filename in files: + path: str = os.path.join(self.DATA_DIR, filename) + last_modified: float = os.path.getmtime(path) + if filename not in self.CACHE or self.CACHE[filename]["ts"] < last_modified: + self.update_file_meta(filename) + + files_meta.append(self.CACHE[filename]) + + return files_meta + + def update_file_meta(self, filename: str): + path: str = os.path.join(self.DATA_DIR, filename) + + meta = { + "filename": filename, + "ts": os.path.getmtime(path) + } + + with open(path, "r") as f: + data = json.load(f) + is_series = "filename" not in data + meta["type"] = "series" if is_series else "film" + if is_series: + meta["episodes"] = len(data) + meta["title"] = filename.split("_metadata")[0] + else: + meta["title"] = data["title"] + + self.CACHE[filename] = meta + def main(): with socketserver.TCPServer(("", PORT), MyHandler) as httpd: