Implement metadata editing (WIP)
This commit is contained in:
parent
8b4cdef690
commit
f8f945acc4
22
js/api.js
22
js/api.js
@ -34,21 +34,23 @@ class API {
|
||||
{
|
||||
method,
|
||||
headers: (body != null ? { "Content-Type": "application/json" } : undefined),
|
||||
body
|
||||
body: (body != null ? JSON.stringify(body) : undefined)
|
||||
}
|
||||
);
|
||||
|
||||
let json = null;
|
||||
try {
|
||||
json = await response.json();
|
||||
} catch (e) {
|
||||
throw "Failed to decode response";
|
||||
}
|
||||
} catch (e) { }
|
||||
|
||||
if (!response.ok) {
|
||||
throw json?.error || "Request failed: " + response.status + " " + response.statusText;
|
||||
}
|
||||
|
||||
if (json == null) {
|
||||
throw "Failed to decode response";
|
||||
}
|
||||
|
||||
return json
|
||||
}
|
||||
|
||||
@ -72,13 +74,22 @@ class API {
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @returns {Video}
|
||||
* @returns {FullVideoMetadata}
|
||||
* @throws {APIError}
|
||||
*/
|
||||
async getVideoMetadata(id) {
|
||||
return await this.call("GET", "/library/video/" + encodeURIComponent(id) + "/metadata", null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {VideoMetadata} metadata
|
||||
* @returns {VideoMetadata}
|
||||
*/
|
||||
async updateVideoMetadata(id, metadata) {
|
||||
return await this.call("PUT", "/library/video/" + encodeURIComponent(id) + "/metadata", null, metadata);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class APIError {
|
||||
@ -122,6 +133,7 @@ class Video {
|
||||
|
||||
/**
|
||||
* @typedef {{series:string, title: string, author: string, index: number, [key: string]: any}} VideoMetadata
|
||||
* @typedef {{raw: VideoMetadata, inherited: VideoMetadata, effective: VideoMetadata}} FullVideoMetadata
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -64,7 +64,7 @@ const components = {
|
||||
* @param {Video} video
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
player(video) {
|
||||
async player(video) {
|
||||
const streamURL = API_URL + "/library/video/" + encodeURIComponent(video.id) + "/stream";
|
||||
|
||||
const downloadVideo = () => {
|
||||
@ -81,7 +81,7 @@ const components = {
|
||||
element("img", { attributes: { "src": "/icon/download.svg", "width": "24", "height": "24" } }),
|
||||
element("span", { content: "Download" }),
|
||||
]),
|
||||
components.videoMetadata(video)
|
||||
await components.videoMetadata(video)
|
||||
]);
|
||||
},
|
||||
|
||||
@ -89,30 +89,73 @@ const components = {
|
||||
* @param {Video} video
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
videoMetadata(video) {
|
||||
async videoMetadata(video) {
|
||||
let metadata;
|
||||
try {
|
||||
metadata = await api.getVideoMetadata(video.id);
|
||||
} catch (e) {
|
||||
showError(e);
|
||||
return element("span", { content: "Failed to load" });
|
||||
}
|
||||
|
||||
let form = element("form", { classNames: ["video-metadata-table"] }, [
|
||||
...components.metadataInput(metadata, "Title", "title"),
|
||||
...components.metadataInput(metadata, "Series", "series"),
|
||||
...components.metadataInput(metadata, "Author", "author"),
|
||||
...components.metadataInput(metadata, "Index", "index"),
|
||||
]);
|
||||
|
||||
let updateMetadata = async () => {
|
||||
let formData = new FormData(form);
|
||||
let metadata = {};
|
||||
formData.forEach((v, k) => {
|
||||
if (v != "") {
|
||||
if (k == "index") { // TODO: card coded check, improve
|
||||
v = parseInt(v);
|
||||
}
|
||||
|
||||
metadata[k] = v;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await api.updateVideoMetadata(video.id, metadata);
|
||||
window.location.reload();
|
||||
} catch (e) {
|
||||
showError(e);
|
||||
}
|
||||
};
|
||||
|
||||
return element("details", { classNames: ["video-metadata"] }, [
|
||||
element("summary", { content: "Metadata" }),
|
||||
element("div", { classNames: ["video-metadata-table"] }, [
|
||||
element("span", { content: "Title" }),
|
||||
element("input", { attributes: { "value": video.metadata.title } }),
|
||||
element("span", { content: "Series" }),
|
||||
element("input", { attributes: { "value": video.metadata.series } }),
|
||||
element("span", { content: "Author" }),
|
||||
element("input", { attributes: { "value": video.metadata.author } }),
|
||||
element("span", { content: "Index" }),
|
||||
element("input", { attributes: { "value": video.metadata.index } }),
|
||||
]),
|
||||
element("button", { content: "Update" })
|
||||
form,
|
||||
element("button", { content: "Update", onClick: updateMetadata })
|
||||
]);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {FullVideoMetadata} metadata
|
||||
* @param {string} label
|
||||
* @param {string} key
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
metadataInput(metadata, label, key) {
|
||||
let raw = metadata.raw[key];
|
||||
let inherited = metadata.inherited[key];
|
||||
|
||||
return [
|
||||
element("span", { content: label }),
|
||||
element("input", { attributes: { "name": key, "value": raw ?? "", "placeholder": "(inherited: " + inherited + ")" } }),
|
||||
];
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {any} error
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
error(error) {
|
||||
return element("div", { classNames: ["error"] }, [
|
||||
element("b", { content: "Oops, an unexpected error occurred" }),
|
||||
element("b", { content: "Oops, an error occurred" }),
|
||||
element("span", { content: errorToString(error) }),
|
||||
element("button", { content: "Reload", onClick: () => window.location.reload() })
|
||||
]);
|
||||
|
9
js/series.js
Normal file
9
js/series.js
Normal file
@ -0,0 +1,9 @@
|
||||
const api = new API(API_URL);
|
||||
|
||||
const root = document.getElementById("root");
|
||||
|
||||
async function init() {
|
||||
finishedLoading();
|
||||
}
|
||||
|
||||
init();
|
11
js/watch.js
11
js/watch.js
@ -5,8 +5,15 @@ const root = document.getElementById("root");
|
||||
async function init() {
|
||||
let query = new URLSearchParams(document.location.search);
|
||||
|
||||
let video = await api.getVideo(query.get("id"));
|
||||
root.appendChild(components.player(video));
|
||||
let video;
|
||||
try {
|
||||
video = await api.getVideo(query.get("id"));
|
||||
} catch (e) {
|
||||
showError(e);
|
||||
return;
|
||||
}
|
||||
|
||||
root.appendChild(await components.player(video));
|
||||
|
||||
finishedLoading();
|
||||
}
|
||||
|
27
series.html
Normal file
27
series.html
Normal file
@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>VideoBase</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<script src="js/config.js" defer></script>
|
||||
<script src="js/api.js" defer></script>
|
||||
<script src="js/components.js" defer></script>
|
||||
<script src="js/base.js" defer></script>
|
||||
<script src="js/series.js" defer></script>
|
||||
|
||||
<link rel="stylesheet" href="style/base.css">
|
||||
<link rel="stylesheet" href="style/components.css">
|
||||
<link rel="stylesheet" href="style/series.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>Unfortunately, this page requires JavaScript to work correctly</noscript>
|
||||
<div id="loading">Loading...</div>
|
||||
<div id="error" style="display: none;"></div>
|
||||
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -61,6 +61,7 @@
|
||||
|
||||
.player>video {
|
||||
width: 1000px;
|
||||
max-height: 500px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
|
1
style/series.css
Normal file
1
style/series.css
Normal file
@ -0,0 +1 @@
|
||||
|
Loading…
x
Reference in New Issue
Block a user