165 lines
4.6 KiB
JavaScript
165 lines
4.6 KiB
JavaScript
/**
|
|
* @param {string} type
|
|
* @param {?{classNames: ?string[], content: ?string, attributes: ?Object.<string, string>, onClick: ?(() => void)}} props
|
|
* @param {?HTMLElement[]} children
|
|
* @returns {HTMLElement}
|
|
*/
|
|
function element(type, props, children) {
|
|
let element = document.createElement(type);
|
|
|
|
if (props?.classNames != null && props.classNames.length > 0) {
|
|
element.classList.add(props.classNames);
|
|
}
|
|
|
|
if (props?.content != null) {
|
|
element.innerText = props.content;
|
|
}
|
|
|
|
if (props?.attributes != null) {
|
|
for (let attribute in props.attributes) {
|
|
element.setAttribute(attribute, props.attributes[attribute]);
|
|
}
|
|
}
|
|
|
|
if (props?.onClick != null) {
|
|
element.onclick = props.onClick;
|
|
}
|
|
|
|
if (children != null) {
|
|
for (let child of children) {
|
|
element.appendChild(child);
|
|
}
|
|
}
|
|
|
|
return element;
|
|
}
|
|
|
|
const components = {
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {Video[]} videos
|
|
* @returns {HTMLElement}
|
|
*/
|
|
series(name, videos) {
|
|
return element("div", { classNames: ["series"] }, [
|
|
element("a", { content: name, attributes: { "href": "/series.html?name=" + encodeURIComponent(name) } }),
|
|
element("div", { content: [...new Set(videos.map(v => v.metadata.author))].join(", "), classNames: ["series-authors"] }),
|
|
element("div", { classNames: ["series-videos"] }, videos.map(v => components.video(v)))
|
|
]);
|
|
},
|
|
|
|
/**
|
|
* @param {Video} video
|
|
* @returns {HTMLElement}
|
|
*/
|
|
video(video) {
|
|
return element("a", { classNames: ["video"], attributes: { "href": "/watch.html?id=" + encodeURIComponent(video.id), "target": "_blank" } }, [
|
|
element("img", { attributes: { "src": API_URL + "/library/video/" + encodeURIComponent(video.id) + "/thumbnail" } }),
|
|
element("span", { content: video.metadata.title }),
|
|
]);
|
|
},
|
|
|
|
/**
|
|
* @param {Video} video
|
|
* @returns {HTMLElement}
|
|
*/
|
|
async player(video) {
|
|
const streamURL = API_URL + "/library/video/" + encodeURIComponent(video.id) + "/stream";
|
|
|
|
const downloadVideo = () => {
|
|
window.location.href = streamURL;
|
|
};
|
|
|
|
return element("div", { classNames: ["player"] }, [
|
|
element("div", { content: video.metadata.title, classNames: ["player-title"] }),
|
|
element("div", { content: video.metadata.author, classNames: ["player-author"] }),
|
|
element("video", { attributes: { "poster": API_URL + "/library/video/" + encodeURIComponent(video.id) + "/thumbnail", "controls": "" } }, [
|
|
element("source", { attributes: { "src": streamURL } })
|
|
]),
|
|
element("button", { onClick: downloadVideo }, [
|
|
element("img", { attributes: { "src": "/icon/download.svg", "width": "24", "height": "24" } }),
|
|
element("span", { content: "Download" }),
|
|
]),
|
|
await components.videoMetadata(video)
|
|
]);
|
|
},
|
|
|
|
/**
|
|
* @param {Video} video
|
|
* @returns {HTMLElement}
|
|
*/
|
|
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" }),
|
|
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 error occurred" }),
|
|
element("span", { content: errorToString(error) }),
|
|
element("button", { content: "Reload", onClick: () => window.location.reload() })
|
|
]);
|
|
}
|
|
|
|
}
|