feat: add saving

This commit is contained in:
Louis Heredero 2025-04-28 19:07:39 +02:00
parent 477e8951a9
commit d19ab90f38
Signed by: HEL
GPG Key ID: 8D83DE470F8544E7
2 changed files with 94 additions and 26 deletions

View File

@ -4,6 +4,8 @@ let audioTable
/** @type TracksTable */ /** @type TracksTable */
let subtitleTable let subtitleTable
let data = {}
function flattenObj(obj) { function flattenObj(obj) {
const res = {} const res = {}
Object.entries(obj).forEach(([key, value]) => { Object.entries(obj).forEach(([key, value]) => {
@ -152,39 +154,64 @@ class TracksTable {
} }
function fetchData(filename) { function fetchData(filename) {
fetch("/api/file", { fetch(`/api/file/${filename}`).then(res => {
method: "POST",
body: JSON.stringify({
file: filename
}),
headers: {
"Content-Type": "application/json"
}
}).then(res => {
if (res.ok) { if (res.ok) {
return res.json() return res.json()
} }
return null return null
}).then(res => { }).then(res => {
if (res !== null) { if (res !== null) {
displayData(res) data = res
displayData()
} }
}) })
} }
function displayData(data) { function displayData() {
document.getElementById("title").value = data.title document.getElementById("title").value = data.title
audioTable.showTracks(data.audio_tracks) audioTable.showTracks(data.audio_tracks)
subtitleTable.showTracks(data.subtitle_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", () => { window.addEventListener("load", () => {
audioTable = new TracksTable(document.getElementById("audio-tracks"), (i, key, value) => { 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) => { 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) const params = new URLSearchParams(window.location.search)
@ -192,4 +219,13 @@ window.addEventListener("load", () => {
document.getElementById("filename").innerText = file document.getElementById("filename").innerText = file
fetchData(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)
}
}) })

View File

@ -4,9 +4,10 @@ import json
import os import os
import socketserver import socketserver
from typing import Optional from typing import Optional
from urllib.parse import urlparse, parse_qs from urllib.parse import urlparse, parse_qs, unquote
PORT = 8000 PORT = 8000
MAX_SIZE = 10e6
class MyHandler(SimpleHTTPRequestHandler): class MyHandler(SimpleHTTPRequestHandler):
DATA_DIR = "metadata" DATA_DIR = "metadata"
@ -23,7 +24,12 @@ class MyHandler(SimpleHTTPRequestHandler):
def read_body_data(self): def read_body_data(self):
self.log_message("Reading body data") self.log_message("Reading body data")
try: 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) self.data = json.loads(raw_data)
except: except:
self.send_error(HTTPStatus.NOT_ACCEPTABLE, "Malformed JSON body") self.send_error(HTTPStatus.NOT_ACCEPTABLE, "Malformed JSON body")
@ -32,6 +38,7 @@ class MyHandler(SimpleHTTPRequestHandler):
return True return True
def do_GET(self): def do_GET(self):
self.path = unquote(self.path)
self.query = parse_qs(urlparse(self.path).query) self.query = parse_qs(urlparse(self.path).query)
if self.path.startswith("/api/"): if self.path.startswith("/api/"):
self.handle_api_get(self.path.removeprefix("/api/").removesuffix("/")) self.handle_api_get(self.path.removeprefix("/api/").removesuffix("/"))
@ -39,6 +46,7 @@ class MyHandler(SimpleHTTPRequestHandler):
super().do_GET() super().do_GET()
def do_POST(self): def do_POST(self):
self.path = unquote(self.path)
self.query = parse_qs(urlparse(self.path).query) self.query = parse_qs(urlparse(self.path).query)
if self.path.startswith("/api/"): if self.path.startswith("/api/"):
self.handle_api_post(self.path.removeprefix("/api/").removesuffix("/")) self.handle_api_post(self.path.removeprefix("/api/").removesuffix("/"))
@ -50,21 +58,32 @@ class MyHandler(SimpleHTTPRequestHandler):
if path == "files": if path == "files":
files: list[str] = self.get_files() files: list[str] = self.get_files()
self.send_json(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): def handle_api_post(self, path: str):
if path == "file": if path.startswith("file"):
if self.read_body_data(): if self.read_body_data():
data = self.get_file(self.data["file"]) filename: str = path.split("/", 1)[1]
if data is None: if self.write_file(filename, self.data):
self.send_error(HTTPStatus.NOT_FOUND) self.send_response(HTTPStatus.OK)
self.log_message("File not found") self.end_headers()
else: else:
self.log_message("Got file") self.send_response(HTTPStatus.NOT_FOUND, f"Unknown path {path}")
self.send_json(data) self.end_headers()
def send_json(self, data: dict|list): def send_json(self, data: dict|list):
self.send_response(200) self.send_response(HTTPStatus.OK)
self.send_header("Content-Type", "application/json") self.send_header("Content-Type", "application/json")
self.end_headers() self.end_headers()
self.wfile.write(json.dumps(data).encode("utf-8")) self.wfile.write(json.dumps(data).encode("utf-8"))
@ -72,13 +91,26 @@ class MyHandler(SimpleHTTPRequestHandler):
def get_files(self): def get_files(self):
return os.listdir(self.DATA_DIR) 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(): if filename not in self.get_files():
return None return None
with open(os.path.join(self.DATA_DIR, filename), "r") as f: with open(os.path.join(self.DATA_DIR, filename), "r") as f:
data = json.load(f) data = json.load(f)
return data 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(): def main():
with socketserver.TCPServer(("", PORT), MyHandler) as httpd: with socketserver.TCPServer(("", PORT), MyHandler) as httpd: