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

245 lines
11 KiB

{% extends "layout.html" %}
{% block main %}
<div class="bg-[#F2EFEB] min-h-[calc(100vh-6.5rem)] 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-3xl font-display font-black uppercase tracking-tighter text-[#111111]">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-[#111111]/40"></i>
<input type="text"
class="searchFilter input-neo w-full pl-10"
placeholder="Search..."
onkeyup="searchFilter()">
</div>
<button onclick="add_modal.showModal()" class="btn-primary-neo text-sm" data-demo-disabled>
<i data-lucide="plus" class="w-4 h-4"></i>
<span class="hidden sm:inline ml-1">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-[#111111]/30">
<i data-lucide="film" class="w-16 h-16 mb-4"></i>
<p class="text-lg font-mono uppercase tracking-wider">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-white border border-[#111111]">
<h3 class="font-display font-black uppercase tracking-tighter text-lg text-[#111111]">Delete Background</h3>
<p class="py-4 text-[#111111]/60 font-mono text-sm">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-ghost-neo">Cancel</button>
<button type="submit" class="btn-danger-neo text-sm" data-demo-disabled>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-white border border-[#111111] max-w-lg">
<h3 class="font-display font-black uppercase tracking-tighter text-lg text-[#111111] 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-[#111111]/70 font-mono text-xs uppercase tracking-wider">YouTube URI</span></label>
<div class="flex border border-[#111111]">
<div class="flex items-center justify-center px-3 bg-[#111111]/5 border-r border-[#111111] shrink-0">
<i data-lucide="youtube" class="w-4 h-4 text-[#111111]"></i>
</div>
<input name="youtube_uri" type="text" placeholder="https://www.youtube.com/watch?v=..."
class="input-neo border-0 flex-1">
</div>
<label class="label h-6"><span id="feedbackYT" class="label-text-alt text-[#DE6C56] font-mono text-xs hidden"></span></label>
</div>
<div class="form-control w-full">
<label class="label"><span class="label-text text-[#111111]/70 font-mono text-xs uppercase tracking-wider">Filename</span></label>
<div class="flex border border-[#111111]">
<div class="flex items-center justify-center px-3 bg-[#111111]/5 border-r border-[#111111] shrink-0">
<i data-lucide="file-video" class="w-4 h-4 text-[#111111]"></i>
</div>
<input name="filename" type="text" placeholder="e.g. minecraft-parkour"
class="input-neo border-0 flex-1">
</div>
<label class="label h-6"><span id="feedbackFilename" class="label-text-alt text-[#DE6C56] font-mono text-xs hidden"></span></label>
</div>
<div class="form-control w-full">
<label class="label"><span class="label-text text-[#111111]/70 font-mono text-xs uppercase tracking-wider">Credits</span></label>
<div class="flex border border-[#111111]">
<div class="flex items-center justify-center px-3 bg-[#111111]/5 border-r border-[#111111] shrink-0">
<i data-lucide="user" class="w-4 h-4 text-[#111111]"></i>
</div>
<input name="citation" type="text" placeholder="YouTube Channel Name"
class="input-neo border-0 flex-1">
</div>
<label class="label"><span class="label-text-alt text-[#111111]/40 font-mono text-xs">Name of the video owner.</span></label>
</div>
<div class="form-control w-full">
<label class="label"><span class="label-text text-[#111111]/70 font-mono text-xs uppercase tracking-wider">Advanced: Position</span></label>
<input name="position" type="text" placeholder="center (optional)"
class="input-neo w-full text-sm h-10">
</div>
<div class="modal-action">
<button type="button" onclick="add_modal.close()" class="btn-ghost-neo">Cancel</button>
<button type="submit" class="btn-primary-neo text-sm" data-demo-disabled>Add Background</button>
</div>
</form>
</div>
</dialog>
<script>
let keys = [];
let youtube_urls = [];
function h(str) {
return String(str ?? '')
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
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-white border border-[#111111] hover:border-[#DE6C56] transition-colors duration-200">
<div class="aspect-video w-full bg-black border-b border-[#111111] 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-[#111111] font-mono text-sm font-medium truncate mb-1" title="${h(key)}">${h(key)}</h3>
<p class="text-[#111111]/40 font-mono text-xs truncate mb-4">${h(value[2])}</p>
${window.PUBLIC_DEMO_MODE ? '' : `<div class="flex justify-end">
<button onclick="confirmDelete('${key}')" class="btn-ghost-neo p-2 hover:text-[#DE6C56]">
<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") ? 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-Za-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 %}