feat: add web editor base

This commit is contained in:
2025-04-28 09:07:45 +02:00
parent 3c4cc4b331
commit 79b3a2196c
7 changed files with 361 additions and 0 deletions

View File

@ -0,0 +1,13 @@
#filename {
font-size: 80%;
font-style: italic;
}
#unsaved {
color: #9d4916;
font-size: 80%;
&:not(.show) {
display: none;
}
}

View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit</title>
<link rel="stylesheet" href="/base.css">
<link rel="stylesheet" href="index.css">
<script src="index.js"></script>
</head>
<body>
<header id="topbar">
<nav>
</nav>
</header>
<aside id="toolbar">
</aside>
<main id="main">
<h1>Edit <code id="filename"></code><span id="unsaved"> - Unsaved</span></h1>
<div class="fields">
<div class="field">
<label for="title">Title</label>
<input type="text" id="title" name="title">
</div>
</div>
<div class="audio">
<h3>Audio Tracks</h3>
<table id="audio-tracks">
<thead>
<tr></tr>
</thead>
<tbody></tbody>
</table>
</div>
<div class="table">
<h3>Subtitle Tracks</h3>
<table id="subtitle-tracks">
<thead>
<tr></tr>
</thead>
<tbody></tbody>
</table>
</div>
</main>
</body>
</html>

157
editor/public/edit/index.js Normal file
View File

@ -0,0 +1,157 @@
/** @type TracksTable */
let audioTable
/** @type TracksTable */
let subtitleTable
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"]
}
constructor(table) {
this.table = table
this.headers = this.table.querySelector("thead tr")
this.body = this.table.querySelector("tbody")
this.fields = []
this.tracks = []
}
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])
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) {
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
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
}
return input
}
}
function fetchData(filename) {
fetch("/api/file", {
method: "POST",
body: JSON.stringify({
file: filename
}),
headers: {
"Content-Type": "application/json"
}
}).then(res => {
if (res.ok) {
return res.json()
}
return null
}).then(res => {
if (res !== null) {
displayData(res)
}
})
}
function displayData(data) {
document.getElementById("title").value = data.title
audioTable.showTracks(data.audio_tracks)
subtitleTable.showTracks(data.subtitle_tracks)
}
window.addEventListener("load", () => {
audioTable = new TracksTable(document.getElementById("audio-tracks"))
subtitleTable = new TracksTable(document.getElementById("subtitle-tracks"))
const params = new URLSearchParams(window.location.search)
const file = params.get("f")
document.getElementById("filename").innerText = file
fetchData(file)
})