This commit is contained in:
Local Server
2025-12-19 20:44:46 -06:00
parent 701f799cde
commit e4b3de4a46
113 changed files with 16673 additions and 2174 deletions

View File

@@ -0,0 +1,535 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Media Library - Sky Art Shop</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
/>
<link rel="stylesheet" href="/admin/css/admin-style.css" />
<style>
.media-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
padding: 20px;
}
.media-item {
position: relative;
border: 2px solid #dee2e6;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s;
}
.media-item:hover {
border-color: #7c3aed;
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(124, 58, 237, 0.3);
}
.media-item.selected {
border-color: #7c3aed;
border-width: 3px;
}
.media-item img {
width: 100%;
height: 200px;
object-fit: cover;
}
.media-item-name {
padding: 10px;
background: #f8f9fa;
font-size: 12px;
word-break: break-all;
}
.media-item-actions {
position: absolute;
top: 5px;
right: 5px;
display: none;
}
.media-item:hover .media-item-actions {
display: block;
}
.upload-zone {
border: 3px dashed #dee2e6;
border-radius: 10px;
padding: 60px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
background: #f8f9fa;
}
.upload-zone:hover,
.upload-zone.dragover {
border-color: #7c3aed;
background: #f3f0ff;
}
.upload-zone i {
font-size: 48px;
color: #7c3aed;
}
.toolbar {
background: #fff;
padding: 15px;
border-bottom: 1px solid #dee2e6;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.selected-count {
background: #7c3aed;
color: white;
padding: 5px 15px;
border-radius: 20px;
font-size: 14px;
}
</style>
</head>
<body>
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
<ul class="sidebar-menu">
<li>
<a href="/admin/dashboard.html"
><i class="bi bi-speedometer2"></i> Dashboard</a
>
</li>
<li>
<a href="/admin/homepage.html"
><i class="bi bi-house"></i> Homepage Editor</a
>
</li>
<li>
<a href="/admin/products.html"><i class="bi bi-box"></i> Products</a>
</li>
<li>
<a href="/admin/portfolio.html"
><i class="bi bi-easel"></i> Portfolio</a
>
</li>
<li>
<a href="/admin/blog.html"><i class="bi bi-newspaper"></i> Blog</a>
</li>
<li>
<a href="/admin/pages.html"
><i class="bi bi-file-text"></i> Custom Pages</a
>
</li>
<li>
<a href="/admin/media-library.html" class="active"
><i class="bi bi-images"></i> Media Library</a
>
</li>
<li>
<a href="/admin/menu.html"><i class="bi bi-list"></i> Menu</a>
</li>
<li>
<a href="/admin/settings.html"><i class="bi bi-gear"></i> Settings</a>
</li>
<li>
<a href="/admin/users.html"><i class="bi bi-people"></i> Users</a>
</li>
</ul>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Top Bar -->
<div class="top-bar">
<div>
<h3>Media Library</h3>
<p class="mb-0 text-muted">Manage your images and media files</p>
</div>
<div>
<button class="btn-logout" onclick="logout()">
<i class="bi bi-box-arrow-right"></i> Logout
</button>
</div>
</div>
<div class="container-fluid">
<div class="toolbar">
<div>
<span
class="selected-count"
id="selectedCount"
style="display: none"
>0 selected</span
>
</div>
<div class="d-flex gap-2">
<button class="btn btn-primary" id="uploadBtn">
<i class="bi bi-cloud-upload"></i> Upload Files
</button>
<button
class="btn btn-success"
id="selectBtn"
style="display: none"
>
<i class="bi bi-check-lg"></i> Select
</button>
<button class="btn btn-outline-secondary" id="closeBtn">
<i class="bi bi-x-lg"></i> Close
</button>
</div>
</div>
</div>
<div class="container-fluid p-4">
<!-- Upload Zone -->
<div class="upload-zone mb-4" id="uploadZone" style="display: none">
<i class="bi bi-cloud-arrow-up"></i>
<h4 class="mt-3">Drop files here or click to browse</h4>
<p class="text-muted">
Supported: JPG, PNG, GIF, WebP (Max 5MB each)
</p>
<input
type="file"
id="fileInput"
multiple
accept="image/*"
style="display: none"
/>
</div>
<!-- Upload Progress -->
<div id="uploadProgress" style="display: none" class="mb-4">
<div class="progress" style="height: 30px">
<div
class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar"
style="width: 0%"
id="progressBar"
>
0%
</div>
</div>
</div>
<!-- Search and Filter -->
<div class="row mb-3">
<div class="col-md-6">
<input
type="text"
class="form-control"
id="searchInput"
placeholder="Search files..."
/>
</div>
<div class="col-md-3">
<select class="form-select" id="filterType">
<option value="all">All Types</option>
<option value="image">Images</option>
<option value="recent">Recently Uploaded</option>
</select>
</div>
<div class="col-md-3">
<button
class="btn btn-outline-danger w-100"
id="deleteSelectedBtn"
style="display: none"
>
<i class="bi bi-trash"></i> Delete Selected
</button>
</div>
</div>
<!-- Media Grid -->
<div class="media-grid" id="mediaGrid">
<!-- Media items will be loaded here -->
</div>
<!-- Empty State -->
<div id="emptyState" style="display: none" class="text-center py-5">
<i class="bi bi-images" style="font-size: 64px; color: #dee2e6"></i>
<h4 class="mt-3 text-muted">No files yet</h4>
<p class="text-muted">Upload your first image to get started</p>
</div>
</div>
</div>
<!-- End Main Content -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="/admin/js/auth.js"></script>
<script>
let selectedFiles = [];
let allFiles = [];
let allowMultiple = false;
// Initialize
document.addEventListener("DOMContentLoaded", function () {
checkAuth().then((authenticated) => {
if (authenticated) {
init();
}
});
});
function init() {
// Get parameters from URL
const urlParams = new URLSearchParams(window.location.search);
allowMultiple = urlParams.get("multiple") === "true";
const callback = urlParams.get("callback");
// Setup event listeners
document
.getElementById("uploadBtn")
.addEventListener("click", showUploadZone);
document
.getElementById("uploadZone")
.addEventListener("click", () =>
document.getElementById("fileInput").click()
);
document
.getElementById("fileInput")
.addEventListener("change", handleFileSelect);
document
.getElementById("selectBtn")
.addEventListener("click", handleSelect);
document
.getElementById("closeBtn")
.addEventListener("click", () => window.close());
document
.getElementById("deleteSelectedBtn")
.addEventListener("click", handleDeleteSelected);
document
.getElementById("searchInput")
.addEventListener("input", handleSearch);
document
.getElementById("filterType")
.addEventListener("change", handleFilter);
// Drag and drop
const uploadZone = document.getElementById("uploadZone");
uploadZone.addEventListener("dragover", (e) => {
e.preventDefault();
uploadZone.classList.add("dragover");
});
uploadZone.addEventListener("dragleave", () => {
uploadZone.classList.remove("dragover");
});
uploadZone.addEventListener("drop", handleDrop);
loadFiles();
}
function showUploadZone() {
document.getElementById("uploadZone").style.display = "block";
}
async function loadFiles() {
try {
const response = await fetch("/api/admin/uploads", {
credentials: "include",
});
const data = await response.json();
if (data.success) {
allFiles = data.files;
renderFiles(allFiles);
}
} catch (error) {
console.error("Failed to load files:", error);
}
}
function renderFiles(files) {
const grid = document.getElementById("mediaGrid");
const emptyState = document.getElementById("emptyState");
if (files.length === 0) {
grid.style.display = "none";
emptyState.style.display = "block";
return;
}
grid.style.display = "grid";
emptyState.style.display = "none";
grid.innerHTML = files
.map(
(file) => `
<div class="media-item" data-file="${file.filename}" onclick="toggleSelect('${file.filename}')">
<img src="/uploads/${file.filename}" alt="${file.filename}">
<div class="media-item-name">${file.filename}</div>
<div class="media-item-actions">
<button class="btn btn-sm btn-danger" onclick="deleteFile(event, '${file.filename}')">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
`
)
.join("");
}
function toggleSelect(filename) {
const item = document.querySelector(`[data-file="${filename}"]`);
if (!allowMultiple) {
// Clear other selections
document.querySelectorAll(".media-item.selected").forEach((el) => {
if (el.dataset.file !== filename) {
el.classList.remove("selected");
}
});
selectedFiles = [];
}
const index = selectedFiles.indexOf(filename);
if (index > -1) {
selectedFiles.splice(index, 1);
item.classList.remove("selected");
} else {
selectedFiles.push(filename);
item.classList.add("selected");
}
updateSelection();
}
function updateSelection() {
const countEl = document.getElementById("selectedCount");
const selectBtn = document.getElementById("selectBtn");
const deleteBtn = document.getElementById("deleteSelectedBtn");
if (selectedFiles.length > 0) {
countEl.textContent = `${selectedFiles.length} selected`;
countEl.style.display = "block";
selectBtn.style.display = "block";
deleteBtn.style.display = "block";
} else {
countEl.style.display = "none";
selectBtn.style.display = "none";
deleteBtn.style.display = "none";
}
}
function handleSelect() {
if (window.opener && window.opener.receiveMediaFiles) {
const files = selectedFiles.map((f) => `/uploads/${f}`);
window.opener.receiveMediaFiles(allowMultiple ? files : files[0]);
window.close();
}
}
async function handleFileSelect(e) {
const files = Array.from(e.target.files);
await uploadFiles(files);
}
async function handleDrop(e) {
e.preventDefault();
e.currentTarget.classList.remove("dragover");
const files = Array.from(e.dataTransfer.files);
await uploadFiles(files);
}
async function uploadFiles(files) {
const formData = new FormData();
files.forEach((file) => formData.append("files", file));
const progressBar = document.getElementById("progressBar");
const progressContainer = document.getElementById("uploadProgress");
progressContainer.style.display = "block";
try {
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", (e) => {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
progressBar.style.width = percentComplete + "%";
progressBar.textContent = Math.round(percentComplete) + "%";
}
});
xhr.addEventListener("load", function () {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
if (data.success) {
setTimeout(() => {
progressContainer.style.display = "none";
document.getElementById("uploadZone").style.display = "none";
loadFiles();
}, 500);
}
}
});
xhr.open("POST", "/api/admin/upload");
xhr.withCredentials = true;
xhr.send(formData);
} catch (error) {
console.error("Upload failed:", error);
alert("Upload failed: " + error.message);
progressContainer.style.display = "none";
}
}
async function deleteFile(event, filename) {
event.stopPropagation();
if (!confirm("Delete this file?")) return;
try {
const response = await fetch(`/api/admin/uploads/${filename}`, {
method: "DELETE",
credentials: "include",
});
const data = await response.json();
if (data.success) {
loadFiles();
}
} catch (error) {
console.error("Failed to delete file:", error);
}
}
async function handleDeleteSelected() {
if (!confirm(`Delete ${selectedFiles.length} files?`)) return;
for (const filename of selectedFiles) {
await deleteFile(new Event("click"), filename);
}
selectedFiles = [];
updateSelection();
}
function handleSearch(e) {
const query = e.target.value.toLowerCase();
const filtered = allFiles.filter((f) =>
f.filename.toLowerCase().includes(query)
);
renderFiles(filtered);
}
function handleFilter(e) {
const filter = e.target.value;
let filtered = allFiles;
if (filter === "recent") {
filtered = allFiles
.slice()
.sort((a, b) => new Date(b.uploadDate) - new Date(a.uploadDate))
.slice(0, 20);
}
renderFiles(filtered);
}
</script>
<script src="/admin/js/auth.js"></script>
</body>
</html>