/** @type TracksTable */ let audioTable /** @type TracksTable */ let subtitleTable let data = {} function flattenObj(obj) { const res = {} Object.entries(obj).forEach(([key, value]) => { if (typeof value === "object") { value = flattenObj(value) Object.entries(value).forEach(([key2, value2]) => { res[key + "/" + key2] = value2 }) } else { res[key] = value } }) return res } class TracksTable { OPTIONS = { "language": ["fre", "eng"] } CONSTRAINTS = { "flags/default": { type: "hotone" } } constructor(table, callback) { this.table = table this.headers = this.table.querySelector("thead tr") this.body = this.table.querySelector("tbody") this.fields = [] this.tracks = [] this.callback = callback this.hotones = {} } showTracks(tracks) { this.tracks = tracks.map(flattenObj) this.clear() if (tracks.length === 0) { return } this.detectFields() this.addHeaders() this.tracks.forEach((track, i) => { const tr = document.createElement("tr") tr.dataset.i = i this.fields.forEach(field => { const td = tr.insertCell(-1) const input = this.makeInput(field, track[field.key], i) td.appendChild(input) }) this.body.appendChild(tr) }) } clear() { this.headers.innerHTML = "" this.body.innerHTML = "" this.fields = [] } detectFields() { Object.entries(this.tracks[0]).forEach(([key, value]) => { let type = { boolean: "bool", number: "num" }[typeof value] ?? "str" if (key === "language") { type = "sel" } const name = key.split("/").slice(-1)[0] this.fields.push({name, type, key}) }) } addHeaders() { this.fields.forEach(field => { const th = document.createElement("th") th.innerText = field.name this.headers.appendChild(th) }) } makeInput(field, value, trackIdx) { let input = document.createElement("input") switch (field.type) { case "num": input.type = "number" input.value = value break case "str": input.type = "text" input.value = value break case "bool": input.type = "checkbox" input.checked = value const hotone = this.CONSTRAINTS[field.key]?.type == "hotone" if (hotone) { if (value) { if (field.key in this.hotones) { alert("Error in metadata file: field ${field.name} is hotone but multiple tracks are enabled ") } this.hotones[field.key] = input } input.addEventListener("click", e => { console.log("HOTONE") if (!input.checked) { console.log("Already checked, preventing uncheck") e.preventDefault() } else { if (field.key in this.hotones) { this.hotones[field.key].checked = false this.hotones[field.key].dispatchEvent(new Event("change")) } this.hotones[field.key] = input } }) } input.addEventListener("change", () => { this.callback(trackIdx, field.key, input.checked) }) break case "sel": input = document.createElement("select") const options = this.OPTIONS[field.name] options.forEach(option => { const opt = document.createElement("option") opt.innerText = option opt.value = option input.appendChild(opt) }) input.value = value default: break } input.dataset.key = field.key return input } } function fetchData(filename) { fetch(`/api/file/${filename}`).then(res => { if (res.ok) { return res.json() } return null }).then(res => { if (res !== null) { data = res displayData() } }) } 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) => { editTrack("audio_tracks", i, key, value) }) subtitleTable = new TracksTable(document.getElementById("subtitle-tracks"), (i, key, value) => { editTrack("subtitle_tracks", i, key, value) }) const params = new URLSearchParams(window.location.search) const file = params.get("f") 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) } })