From d87298e0b3814051f7af91d82906c73b664b0cd7 Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Mon, 28 Apr 2025 19:07:39 +0200 Subject: [PATCH] feat: add saving --- editor/public/edit/index.js | 62 +++++++++++++++++++++++++++++-------- editor/server.py | 58 ++++++++++++++++++++++++++-------- 2 files changed, 94 insertions(+), 26 deletions(-) diff --git a/editor/public/edit/index.js b/editor/public/edit/index.js index 7f20c28..824f3d1 100644 --- a/editor/public/edit/index.js +++ b/editor/public/edit/index.js @@ -4,6 +4,8 @@ let audioTable /** @type TracksTable */ let subtitleTable +let data = {} + function flattenObj(obj) { const res = {} Object.entries(obj).forEach(([key, value]) => { @@ -152,39 +154,64 @@ class TracksTable { } function fetchData(filename) { - fetch("/api/file", { - method: "POST", - body: JSON.stringify({ - file: filename - }), - headers: { - "Content-Type": "application/json" - } - }).then(res => { + fetch(`/api/file/${filename}`).then(res => { if (res.ok) { return res.json() } return null }).then(res => { if (res !== null) { - displayData(res) + data = res + displayData() } }) } -function displayData(data) { +function displayData() { document.getElementById("title").value = data.title audioTable.showTracks(data.audio_tracks) subtitleTable.showTracks(data.subtitle_tracks) } +function setDirty() { + dirty = true + document.getElementById("unsaved").classList.add("show") +} + +function save(filename) { + fetch(`/api/file/${filename}`, { + method: "POST", + body: JSON.stringify(data), + headers: { + "Content-Type": "application/json" + } + }).then(res => { + if (res.ok) { + dirty = false + document.getElementById("unsaved").classList.remove("show") + } else { + alert(`Error ${res.status}: ${res.statusText}`) + } + }) +} + +function editTrack(listKey, trackIdx, key, value) { + const keyParts = key.split("/") + let obj = data[listKey][trackIdx] + for (const part of keyParts.slice(0, -1)) { + obj = obj[part] + } + obj[keyParts[keyParts.length - 1]] = value + setDirty() +} + window.addEventListener("load", () => { audioTable = new TracksTable(document.getElementById("audio-tracks"), (i, key, value) => { - console.log(i, key, value) + editTrack("audio_tracks", i, key, value) }) subtitleTable = new TracksTable(document.getElementById("subtitle-tracks"), (i, key, value) => { - console.log(i, key, value) + editTrack("subtitle_tracks", i, key, value) }) const params = new URLSearchParams(window.location.search) @@ -192,4 +219,13 @@ window.addEventListener("load", () => { document.getElementById("filename").innerText = file fetchData(file) +}) + +window.addEventListener("keydown", e => { + if (e.key === "s" && e.ctrlKey) { + e.preventDefault() + const params = new URLSearchParams(window.location.search) + const file = params.get("f") + save(file) + } }) \ No newline at end of file diff --git a/editor/server.py b/editor/server.py index 1133468..5d67811 100644 --- a/editor/server.py +++ b/editor/server.py @@ -4,9 +4,10 @@ import json import os import socketserver from typing import Optional -from urllib.parse import urlparse, parse_qs +from urllib.parse import urlparse, parse_qs, unquote PORT = 8000 +MAX_SIZE = 10e6 class MyHandler(SimpleHTTPRequestHandler): DATA_DIR = "metadata" @@ -23,7 +24,12 @@ class MyHandler(SimpleHTTPRequestHandler): def read_body_data(self): self.log_message("Reading body data") try: - raw_data = self.rfile.read(int(self.headers["Content-Length"])) + size: int = int(self.headers["Content-Length"]) + if size > MAX_SIZE: + self.send_error(HTTPStatus.CONTENT_TOO_LARGE) + self.log_error(f"Payload is too big ({MAX_SIZE=}B)") + return False + raw_data = self.rfile.read(size) self.data = json.loads(raw_data) except: self.send_error(HTTPStatus.NOT_ACCEPTABLE, "Malformed JSON body") @@ -32,6 +38,7 @@ class MyHandler(SimpleHTTPRequestHandler): return True def do_GET(self): + self.path = unquote(self.path) self.query = parse_qs(urlparse(self.path).query) if self.path.startswith("/api/"): self.handle_api_get(self.path.removeprefix("/api/").removesuffix("/")) @@ -39,6 +46,7 @@ class MyHandler(SimpleHTTPRequestHandler): super().do_GET() def do_POST(self): + self.path = unquote(self.path) self.query = parse_qs(urlparse(self.path).query) if self.path.startswith("/api/"): self.handle_api_post(self.path.removeprefix("/api/").removesuffix("/")) @@ -50,21 +58,32 @@ class MyHandler(SimpleHTTPRequestHandler): if path == "files": files: list[str] = self.get_files() self.send_json(files) - return + elif path.startswith("file"): + filename: str = path.split("/", 1)[1] + data = self.read_file(filename) + if data is None: + self.send_error(HTTPStatus.NOT_FOUND) + self.log_message("File not found") + else: + self.log_message("Got file") + self.send_json(data) + else: + self.send_response(HTTPStatus.NOT_FOUND, f"Unknown path {path}") + self.end_headers() def handle_api_post(self, path: str): - if path == "file": + if path.startswith("file"): if self.read_body_data(): - data = self.get_file(self.data["file"]) - if data is None: - self.send_error(HTTPStatus.NOT_FOUND) - self.log_message("File not found") - else: - self.log_message("Got file") - self.send_json(data) + filename: str = path.split("/", 1)[1] + if self.write_file(filename, self.data): + self.send_response(HTTPStatus.OK) + self.end_headers() + else: + self.send_response(HTTPStatus.NOT_FOUND, f"Unknown path {path}") + self.end_headers() def send_json(self, data: dict|list): - self.send_response(200) + self.send_response(HTTPStatus.OK) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps(data).encode("utf-8")) @@ -72,13 +91,26 @@ class MyHandler(SimpleHTTPRequestHandler): def get_files(self): return os.listdir(self.DATA_DIR) - def get_file(self, filename: str) -> Optional[dict|list]: + def read_file(self, filename: str) -> Optional[dict|list]: if filename not in self.get_files(): return None with open(os.path.join(self.DATA_DIR, filename), "r") as f: data = json.load(f) return data + def write_file(self, filename: str, data: dict|list) -> bool: + if filename not in self.get_files(): + self.send_error(HTTPStatus.NOT_FOUND) + return False + + try: + with open(os.path.join(self.DATA_DIR, filename), "w") as f: + json.dump(data, f, indent=2) + except: + self.send_error(HTTPStatus.INTERNAL_SERVER_ERROR) + return False + return True + def main(): with socketserver.TCPServer(("", PORT), MyHandler) as httpd: