You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
RedditVideoMakerBot/GUI/backgrounds.html

236 lines
11 KiB

{% extends "layout.html" %}
{% block main %}
<div class="bg-slate-900 min-h-screen py-8">
<div class="container mx-auto px-4">
<!-- Header & Actions -->
<div class="flex flex-col md:flex-row justify-between items-center gap-4 mb-8">
<h1 class="text-2xl font-bold text-white">Background Manager</h1>
<div class="flex w-full md:w-auto gap-2">
<div class="relative flex-grow md:w-64">
<i data-lucide="search" class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400"></i>
<input type="text"
class="searchFilter input input-bordered w-full pl-10 bg-slate-800 border-slate-700 text-slate-200 focus:border-indigo-500"
placeholder="Search..."
onkeyup="searchFilter()">
</div>
<button onclick="add_modal.showModal()" class="btn btn-indigo bg-indigo-600 hover:bg-indigo-500 border-none text-white">
<i data-lucide="plus" class="w-4 h-4"></i>
<span class="hidden sm:inline">Add Video</span>
</button>
</div>
</div>
<!-- Background Grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6" id="backgrounds">
<!-- Backgrounds will be injected here -->
</div>
<!-- Empty State -->
<div id="empty-state" class="hidden flex flex-col items-center justify-center py-20 text-slate-500">
<i data-lucide="film" class="w-16 h-16 mb-4 opacity-20"></i>
<p class="text-lg">No backgrounds found</p>
</div>
</div>
</div>
<!-- Delete Background Modal -->
<dialog id="delete_modal" class="modal modal-bottom sm:modal-middle">
<div class="modal-box bg-slate-800 border border-white/10">
<h3 class="font-bold text-lg text-white">Delete Background</h3>
<p class="py-4 text-slate-400">Are you sure you want to delete this background video? This action cannot be undone.</p>
<div class="modal-action">
<form action="background/delete" method="post" class="flex gap-2">
<input type="hidden" id="background-key" name="background-key" value="">
<button type="button" onclick="delete_modal.close()" class="btn btn-ghost text-slate-400">Cancel</button>
<button type="submit" class="btn btn-error">Delete</button>
</form>
</div>
</div>
</dialog>
<!-- Add Background Modal -->
<dialog id="add_modal" class="modal modal-bottom sm:modal-middle">
<div class="modal-box bg-slate-800 border border-white/10 max-w-lg">
<h3 class="font-bold text-lg text-white mb-6">Add Background Video</h3>
<form id="addBgForm" action="background/add" method="post" novalidate class="space-y-4">
<div class="form-control w-full">
<label class="label"><span class="label-text text-slate-300">YouTube URI</span></label>
<div class="join w-full">
<div class="btn join-item no-animation bg-slate-900 border-slate-700 pointer-events-none">
<i data-lucide="youtube" class="w-4 h-4 text-red-500"></i>
</div>
<input name="youtube_uri" type="text" placeholder="https://www.youtube.com/watch?v=..."
class="input input-bordered join-item w-full bg-slate-900 border-slate-700 text-slate-200 focus:border-indigo-500">
</div>
<label class="label h-6"><span id="feedbackYT" class="label-text-alt text-error hidden"></span></label>
</div>
<div class="form-control w-full">
<label class="label"><span class="label-text text-slate-300">Filename</span></label>
<div class="join w-full">
<div class="btn join-item no-animation bg-slate-900 border-slate-700 pointer-events-none">
<i data-lucide="file-video" class="w-4 h-4 text-indigo-500"></i>
</div>
<input name="filename" type="text" placeholder="e.g. minecraft-parkour"
class="input input-bordered join-item w-full bg-slate-900 border-slate-700 text-slate-200 focus:border-indigo-500">
</div>
<label class="label h-6"><span id="feedbackFilename" class="label-text-alt text-error hidden"></span></label>
</div>
<div class="form-control w-full">
<label class="label"><span class="label-text text-slate-300">Credits</span></label>
<div class="join w-full">
<div class="btn join-item no-animation bg-slate-900 border-slate-700 pointer-events-none">
<i data-lucide="user" class="w-4 h-4 text-slate-400"></i>
</div>
<input name="citation" type="text" placeholder="YouTube Channel Name"
class="input input-bordered join-item w-full bg-slate-900 border-slate-700 text-slate-200 focus:border-indigo-500">
</div>
<label class="label"><span class="label-text-alt text-slate-500 italic">Name of the video owner.</span></label>
</div>
<div class="form-control w-full">
<label class="label"><span class="label-text text-slate-300 text-xs">Advanced: Position</span></label>
<input name="position" type="text" placeholder="center (optional)"
class="input input-bordered w-full bg-slate-900 border-slate-700 text-slate-200 focus:border-indigo-500 text-sm h-10">
</div>
<div class="modal-action">
<button type="button" onclick="add_modal.close()" class="btn btn-ghost text-slate-400">Cancel</button>
<button type="submit" class="btn btn-indigo bg-indigo-600 hover:bg-indigo-500 border-none text-white">Add Background</button>
</div>
</form>
</div>
</dialog>
<script>
let keys = [];
let youtube_urls = [];
async function loadBackgrounds() {
try {
const response = await fetch("backgrounds.json");
const data = await response.json();
delete data["__comment"];
const container = document.getElementById('backgrounds');
let html = '';
Object.entries(data).forEach(([key, value]) => {
keys.push(key);
youtube_urls.push(value[0]);
const videoId = value[0].includes('?v=') ? value[0].split('?v=')[1] : value[0].split('/').pop();
html += `
<div class="bg-card group bg-slate-800 rounded-xl overflow-hidden border border-white/5 hover:border-indigo-500/50 transition-all duration-300 shadow-lg">
<div class="aspect-video w-full bg-black relative">
<iframe class="w-full h-full"
src="https://www.youtube-nocookie.com/embed/${videoId}"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe>
</div>
<div class="p-4">
<h3 class="text-slate-200 font-medium truncate mb-1" title="${key}">${key}</h3>
<p class="text-slate-500 text-xs truncate mb-4">${value[2]}</p>
<div class="flex justify-end">
<button onclick="confirmDelete('${key}')" class="btn btn-square btn-sm btn-ghost hover:bg-red-500/20 hover:text-red-400">
<i data-lucide="trash-2" class="w-4 h-4"></i>
</button>
</div>
</div>
</div>
`;
});
container.innerHTML = html;
lucide.createIcons();
} catch (error) {
console.error("Error loading backgrounds:", error);
}
}
function confirmDelete(key) {
document.getElementById('background-key').value = key;
delete_modal.showModal();
}
function searchFilter() {
const query = document.querySelector(".searchFilter").value.toLowerCase();
const cards = document.querySelectorAll(".bg-card");
let visibleCount = 0;
cards.forEach(card => {
const text = card.textContent.toLowerCase();
const matches = text.includes(query);
card.classList.toggle('hidden', !matches);
if (matches) visibleCount++;
});
document.getElementById('empty-state').classList.toggle('hidden', visibleCount > 0);
}
const form = document.getElementById('addBgForm');
form.addEventListener('submit', (e) => {
let isValid = true;
form.querySelectorAll('input').forEach(input => {
if (!validate(input)) isValid = false;
});
if (!isValid) e.preventDefault();
});
form.querySelectorAll('input').forEach(input => {
input.addEventListener('keyup', () => validate(input));
});
function validate(input) {
const name = input.name;
const value = input.value;
let valid = true;
let message = "";
if (name === "youtube_uri") {
const regex = /(?:\/|%3D|v=|vi=)([0-9A-z-_]{11})(?:[%#?&]|$)/;
if (!regex.test(value)) {
message = "Invalid YouTube URI";
valid = false;
} else if (youtube_urls.includes(value)) {
message = "Background already added";
valid = false;
}
const feedback = document.getElementById('feedbackYT');
feedback.textContent = message;
feedback.classList.toggle('hidden', valid);
}
if (name === "filename") {
if (keys.includes(value)) {
message = "Filename already taken";
valid = false;
} else if (!/^([a-zA-Z0-9\s_-]{1,100})$/.test(value)) {
valid = false;
}
const feedback = document.getElementById('feedbackFilename');
feedback.textContent = message;
feedback.classList.toggle('hidden', valid);
}
if (name === "position") {
if (value && value !== "center" && isNaN(parseFloat(value))) {
valid = false;
}
}
input.classList.toggle('input-error', !valid);
return valid;
}
document.addEventListener('DOMContentLoaded', loadBackgrounds);
</script>
{% endblock %}