Files
yaris-obd/static/js/local-videos.js
T
2026-08-09 19:04:47 -04:00

180 lines
7.0 KiB
JavaScript

(function () {
"use strict";
const summary = document.getElementById("local-video-summary");
const body = document.querySelector("#local-video-list [data-data-list-body]");
const refreshButton = document.getElementById("local-videos-refresh-btn");
const playerPanel = document.getElementById("local-video-player-panel");
const playerTitle = document.getElementById("local-video-player-title");
const player = document.getElementById("local-video-player");
const closePlayerButton = document.getElementById("local-video-player-close");
let playingPath = null;
const thumbnailObserver = new IntersectionObserver(function (entries, observer) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) {
return;
}
const image = entry.target;
image.src = `/api/local-video/thumbnail?path=${encodeURIComponent(image.dataset.path)}`;
observer.unobserve(image);
});
}, { rootMargin: "200px" });
function fetchJson(url, options) {
return fetch(url, options).then(function (response) {
return response.json().then(function (data) {
if (!response.ok) {
throw new Error(data.message || data.error || data.status || `HTTP ${response.status}`);
}
return data;
});
});
}
function textCell(text) {
const cell = document.createElement("td");
cell.textContent = text;
return cell;
}
function closePlayer() {
player.pause();
player.removeAttribute("src");
player.load();
playerPanel.hidden = true;
playingPath = null;
}
function playVideo(path) {
playingPath = path;
playerTitle.textContent = path;
player.src = `/api/local-video/file?path=${encodeURIComponent(path)}`;
playerPanel.hidden = false;
player.load();
player.play().catch(function () {
// Browser autoplay policy may require the user to press play.
});
playerPanel.scrollIntoView({ behavior: "smooth", block: "start" });
}
function renderFiles(result) {
thumbnailObserver.disconnect();
body.replaceChildren();
const files = result.files || [];
let uploadedCount = 0;
files.forEach(function (file) {
const row = document.createElement("tr");
row.dataset.path = file.path;
row.dataset.size = file.size;
row.dataset.modified = file.modified_at;
row.dataset.uploaded = file.uploaded ? "1" : "0";
const preview = document.createElement("td");
const image = document.createElement("img");
image.className = "local-video-thumbnail";
image.alt = `Preview of ${file.path}`;
image.loading = "lazy";
image.dataset.path = file.path;
image.addEventListener("error", function () {
image.hidden = true;
});
preview.appendChild(image);
row.appendChild(preview);
thumbnailObserver.observe(image);
row.appendChild(textCell(file.path));
row.appendChild(textCell(YarisUI.formatBytes(file.size)));
row.appendChild(textCell(new Date(file.modified_at * 1000).toLocaleString()));
const upload = document.createElement("td");
const badge = document.createElement("span");
badge.className = file.uploaded ? "badge bg-success" : "badge bg-warning text-dark";
badge.textContent = file.uploaded ? "Uploaded" : "Pending";
upload.appendChild(badge);
row.appendChild(upload);
if (file.uploaded) {
uploadedCount += 1;
}
const actions = document.createElement("td");
actions.className = "data-list-actions";
const playButton = document.createElement("button");
playButton.type = "button";
playButton.className = "btn btn-sm btn-outline-primary";
playButton.dataset.playPath = file.path;
playButton.innerHTML = '<i class="fas fa-play me-1"></i>Play';
actions.appendChild(playButton);
const deleteButton = document.createElement("button");
deleteButton.type = "button";
deleteButton.className = "btn btn-sm btn-outline-danger";
deleteButton.dataset.deleteLocalPath = file.path;
deleteButton.dataset.confirm = `Delete the local Pi copy of "${file.path}"? The remote archive will not be changed.`;
deleteButton.innerHTML = '<i class="fas fa-trash me-1"></i>Delete local';
actions.appendChild(deleteButton);
row.appendChild(actions);
body.appendChild(row);
});
summary.textContent = `${files.length} video(s), ${YarisUI.formatBytes(result.total_size || 0)} total, ${uploadedCount} uploaded`;
if (result.retention_days > 0) {
summary.textContent += `; uploaded copies older than ${result.retention_days} day(s) are auto-deleted`;
} else {
summary.textContent += "; automatic retention cleanup is off";
}
if (result.errors && result.errors.length) {
summary.textContent += ` (${result.errors.length} file error(s))`;
}
}
function refreshFiles() {
refreshButton.disabled = true;
summary.textContent = "Loading...";
return fetchJson("/api/local-videos")
.then(renderFiles)
.catch(function (error) {
body.replaceChildren();
summary.textContent = `Unable to list local videos: ${error.message}`;
})
.finally(function () {
refreshButton.disabled = false;
});
}
function deleteVideo(path) {
return fetchJson("/api/local-video", {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ path }),
}).then(function () {
if (playingPath === path) {
closePlayer();
}
return refreshFiles();
}).catch(function (error) {
summary.textContent = `Local delete failed: ${error.message}`;
});
}
body.addEventListener("click", function (event) {
const playButton = event.target.closest("[data-play-path]");
if (playButton) {
playVideo(playButton.dataset.playPath);
return;
}
const deleteButton = event.target.closest("[data-delete-local-path]");
if (deleteButton) {
deleteButton.disabled = true;
deleteVideo(deleteButton.dataset.deleteLocalPath).finally(function () {
deleteButton.disabled = false;
});
}
});
refreshButton.addEventListener("click", refreshFiles);
closePlayerButton.addEventListener("click", closePlayer);
refreshFiles();
}());