refactor: split editor in JS modules
This commit is contained in:
@ -1,68 +0,0 @@
|
||||
#filename {
|
||||
font-size: 80%;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#unsaved {
|
||||
color: #9d4916;
|
||||
font-size: 80%;
|
||||
|
||||
&:not(.show) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
|
||||
th, td {
|
||||
padding: 0.4em 0.8em;
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
&:nth-child(even) {
|
||||
background-color: #ececec;
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
--size: 0.6em;
|
||||
--pad: 0.2em;
|
||||
width: calc((var(--size) * 2 + var(--pad)) * 2);
|
||||
height: calc((var(--size) + var(--pad)) * 2);
|
||||
border-radius: calc(var(--size) + var(--pad));
|
||||
appearance: none;
|
||||
background-color: #e1e1e1;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
width: calc(var(--size) * 2);
|
||||
height: calc(var(--size) * 2);
|
||||
border-radius: calc(var(--size));
|
||||
background-color: #9b9b9b;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: var(--pad);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
&:checked {
|
||||
background-color: #daf0d1;
|
||||
|
||||
&::after {
|
||||
left: auto;
|
||||
right: var(--pad);
|
||||
background-color: #6ee74a;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,9 +4,9 @@
|
||||
<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>
|
||||
<link rel="stylesheet" href="/static/css/base.css">
|
||||
<link rel="stylesheet" href="/static/css/edit.css">
|
||||
<script src="/static/js/edit.mjs" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header id="topbar">
|
||||
@ -44,5 +44,18 @@
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
<div class="popup" id="subtitle-integrity">
|
||||
<div class="container">
|
||||
<h3 class="title">Subtitle Track Integrity</h3>
|
||||
<p id="st-integrity-desc"></p>
|
||||
<div class="original">
|
||||
<label for="st-integrity-og">Original Value</label>
|
||||
<input type="text" disabled id="st-integrity-og">
|
||||
</div>
|
||||
<div class="correction">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,231 +0,0 @@
|
||||
/** @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)
|
||||
}
|
||||
})
|
Reference in New Issue
Block a user