From f8f945acc471983e263fc6b96f212b32b0543de7 Mon Sep 17 00:00:00 2001 From: MrLetsplay Date: Mon, 17 Feb 2025 23:04:50 +0100 Subject: [PATCH] Implement metadata editing (WIP) --- js/api.js | 22 ++++++++++--- js/components.js | 73 +++++++++++++++++++++++++++++++++++--------- js/series.js | 9 ++++++ js/watch.js | 11 +++++-- series.html | 27 ++++++++++++++++ style/components.css | 1 + style/series.css | 1 + 7 files changed, 122 insertions(+), 22 deletions(-) create mode 100644 js/series.js create mode 100644 series.html create mode 100644 style/series.css diff --git a/js/api.js b/js/api.js index 87f5a58..cd42a68 100644 --- a/js/api.js +++ b/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 */ /** diff --git a/js/components.js b/js/components.js index b7fd95f..7a36ea8 100644 --- a/js/components.js +++ b/js/components.js @@ -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() }) ]); diff --git a/js/series.js b/js/series.js new file mode 100644 index 0000000..16c762d --- /dev/null +++ b/js/series.js @@ -0,0 +1,9 @@ +const api = new API(API_URL); + +const root = document.getElementById("root"); + +async function init() { + finishedLoading(); +} + +init(); diff --git a/js/watch.js b/js/watch.js index 8542bc1..64920b7 100644 --- a/js/watch.js +++ b/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(); } diff --git a/series.html b/series.html new file mode 100644 index 0000000..f4933f5 --- /dev/null +++ b/series.html @@ -0,0 +1,27 @@ + + + + + VideoBase + + + + + + + + + + + + + + + +
Loading...
+ + +
+ + + diff --git a/style/components.css b/style/components.css index d9cb9a3..82299b1 100644 --- a/style/components.css +++ b/style/components.css @@ -61,6 +61,7 @@ .player>video { width: 1000px; + max-height: 500px; max-width: 100%; } diff --git a/style/series.css b/style/series.css new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/style/series.css @@ -0,0 +1 @@ +