feat: add saving
This commit is contained in:
parent
477e8951a9
commit
d19ab90f38
@ -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)
|
||||||
|
}
|
||||||
})
|
})
|
@ -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:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user