gallery202508 / index.html
Dagfinn1962's picture
Update index.html
89c48f7 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Easy Gallery System</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
<style>
.dropzone {
border: 2px dashed #ccc;
transition: all 0.3s;
}
.dropzone.active {
border-color: #4f46e5;
background-color: #f0f7ff;
}
.gallery-item {
transition: transform 0.3s, box-shadow 0.3s;
}
.gallery-item:hover {
transform: translateY(-5px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="mb-8 text-center">
<h1 class="text-4xl font-bold text-indigo-700 mb-2">Easy Gallery System</h1>
<p class="text-gray-600">Upload and manage your media with ease</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Upload Section -->
<div class="bg-white rounded-xl shadow-md p-6">
<h2 class="text-2xl font-semibold text-gray-800 mb-4">Upload Media</h2>
<div id="dropzone" class="dropzone rounded-lg p-8 mb-4 text-center cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg>
<p class="mt-2 text-gray-600">Drag & drop files here or click to browse</p>
<input type="file" id="fileInput" class="hidden" multiple accept="image/*,video/*">
</div>
<div class="mb-4">
<label for="description" class="block text-sm font-medium text-gray-700 mb-1">Description</label>
<textarea id="description" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Add a description for your media..."></textarea>
</div>
<button id="uploadBtn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-4 rounded-md transition duration-300 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
Upload to Gallery
</button>
<div id="uploadProgress" class="mt-4 hidden">
<div class="flex justify-between mb-1">
<span class="text-sm font-medium text-indigo-700">Uploading...</span>
<span id="progressPercent" class="text-sm font-medium text-indigo-700">0%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div id="progressBar" class="bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div>
</div>
</div>
</div>
<!-- Gallery Section -->
<div class="bg-white rounded-xl shadow-md p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-2xl font-semibold text-gray-800">Your Gallery</h2>
<div class="relative">
<select id="filterSelect" class="appearance-none bg-gray-100 border border-gray-300 text-gray-700 py-2 px-4 pr-8 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="all">All Media</option>
<option value="image">Images</option>
<option value="video">Videos</option>
</select>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/></svg>
</div>
</div>
</div>
<div id="galleryContainer" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<!-- Gallery items will be loaded here -->
<div class="text-center py-8 text-gray-500" id="emptyGalleryMessage">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
Your gallery is empty. Upload some media to get started!
</div>
</div>
<div id="pagination" class="mt-6 flex justify-center hidden">
<button id="prevPage" class="px-4 py-2 mx-1 rounded-md bg-gray-200 text-gray-700 disabled:opacity-50">&larr; Previous</button>
<span id="pageInfo" class="px-4 py-2 mx-1">Page 1 of 1</span>
<button id="nextPage" class="px-4 py-2 mx-1 rounded-md bg-gray-200 text-gray-700 disabled:opacity-50">Next &rarr;</button>
</div>
</div>
</div>
</div>
<!-- Media Modal -->
<div id="mediaModal" class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg max-w-4xl w-full max-h-screen overflow-auto">
<div class="flex justify-between items-center p-4 border-b">
<h3 id="modalTitle" class="text-lg font-semibold"></h3>
<button id="closeModal" class="text-gray-500 hover:text-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="p-4">
<div id="modalContent" class="flex justify-center">
<!-- Media will be displayed here -->
</div>
<p id="modalDescription" class="mt-4 text-gray-700"></p>
<p id="modalDate" class="mt-2 text-sm text-gray-500"></p>
</div>
</div>
</div>
<script>
// Initialize Supabase
const supabaseUrl = 'https://kjioumobitojvvlsfewp.supabase.co';
const supabaseKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtqaW91bW9iaXRvanZ2bHNmZXdwIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTUzNzg5NjksImV4cCI6MjA3MDk1NDk2OX0.oH2_2B7K4Uyw-glERw1bTgxXUdoyaZ1k7TM57E1KRkc';
const supabase = supabase.createClient(supabaseUrl, supabaseKey);
// DOM Elements
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('fileInput');
const uploadBtn = document.getElementById('uploadBtn');
const description = document.getElementById('description');
const uploadProgress = document.getElementById('uploadProgress');
const progressBar = document.getElementById('progressBar');
const progressPercent = document.getElementById('progressPercent');
const galleryContainer = document.getElementById('galleryContainer');
const emptyGalleryMessage = document.getElementById('emptyGalleryMessage');
const filterSelect = document.getElementById('filterSelect');
const pagination = document.getElementById('pagination');
const prevPage = document.getElementById('prevPage');
const nextPage = document.getElementById('nextPage');
const pageInfo = document.getElementById('pageInfo');
const mediaModal = document.getElementById('mediaModal');
const modalTitle = document.getElementById('modalTitle');
const modalContent = document.getElementById('modalContent');
const modalDescription = document.getElementById('modalDescription');
const modalDate = document.getElementById('modalDate');
const closeModal = document.getElementById('closeModal');
// State
let currentFiles = [];
let currentPage = 1;
const itemsPerPage = 9;
let currentFilter = 'all';
// Event Listeners
dropzone.addEventListener('click', () => fileInput.click());
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
dropzone.classList.add('active');
});
dropzone.addEventListener('dragleave', () => {
dropzone.classList.remove('active');
});
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
dropzone.classList.remove('active');
if (e.dataTransfer.files.length) {
fileInput.files = e.dataTransfer.files;
handleFilesSelected();
}
});
fileInput.addEventListener('change', handleFilesSelected);
uploadBtn.addEventListener('click', uploadFiles);
filterSelect.addEventListener('change', (e) => {
currentFilter = e.target.value;
currentPage = 1;
loadGalleryItems();
});
prevPage.addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
loadGalleryItems();
}
});
nextPage.addEventListener('click', () => {
currentPage++;
loadGalleryItems();
});
closeModal.addEventListener('click', () => {
mediaModal.classList.add('hidden');
});
// Initialize
loadGalleryItems();
// Functions
function handleFilesSelected() {
currentFiles = Array.from(fileInput.files);
if (currentFiles.length > 0) {
const fileNames = currentFiles.map(file => file.name).join(', ');
dropzone.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
<p class="mt-2 font-medium text-gray-700">${currentFiles.length} file(s) selected</p>
<p class="text-sm text-gray-500 truncate">${fileNames}</p>
`;
}
}
async function uploadFiles() {
if (currentFiles.length === 0) {
alert('Please select files to upload');
return;
}
const desc = description.value.trim();
uploadProgress.classList.remove('hidden');
uploadBtn.disabled = true;
try {
for (let i = 0; i < currentFiles.length; i++) {
const file = currentFiles[i];
const fileType = file.type.startsWith('image/') ? 'image' : 'video';
const fileName = `${Date.now()}-${file.name}`;
const filePath = `${fileType}s/${fileName}`;
// Update progress
const progress = Math.round((i / currentFiles.length) * 100);
progressBar.style.width = `${progress}%`;
progressPercent.textContent = `${progress}%`;
// Upload file to storage
const { data: uploadData, error: uploadError } = await supabase.storage
.from('media')
.upload(filePath, file);
if (uploadError) throw uploadError;
// Get public URL
const { data: urlData } = supabase.storage
.from('media')
.getPublicUrl(filePath);
// Insert record into database
const { error: dbError } = await supabase
.from('gallery')
.insert([
{
url: urlData.publicUrl,
type: fileType,
description: desc,
filename: fileName,
created_at: new Date().toISOString()
}
]);
if (dbError) throw dbError;
}
// Complete progress
progressBar.style.width = '100%';
progressPercent.textContent = '100%';
// Reset form
setTimeout(() => {
uploadProgress.classList.add('hidden');
uploadBtn.disabled = false;
progressBar.style.width = '0%';
progressPercent.textContent = '0%';
fileInput.value = '';
description.value = '';
dropzone.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg>
<p class="mt-2 text-gray-600">Drag & drop files here or click to browse</p>
`;
// Reload gallery
loadGalleryItems();
}, 1000);
} catch (error) {
console.error('Upload error:', error);
alert('Error uploading files. Please try again.');
uploadProgress.classList.add('hidden');
uploadBtn.disabled = false;
}
}
async function loadGalleryItems() {
try {
galleryContainer.innerHTML = '<div class="col-span-full text-center py-8">Loading...</div>';
// Calculate range for pagination
const from = (currentPage - 1) * itemsPerPage;
const to = from + itemsPerPage - 1;
// Build query based on filter
let query = supabase
.from('gallery')
.select('*', { count: 'exact' })
.order('created_at', { ascending: false })
.range(from, to);
if (currentFilter !== 'all') {
query = query.eq('type', currentFilter);
}
const { data: items, count, error } = await query;
if (error) throw error;
// Update gallery display
if (items && items.length > 0) {
emptyGalleryMessage.classList.add('hidden');
renderGalleryItems(items);
// Update pagination
const totalPages = Math.ceil(count / itemsPerPage);
pageInfo.textContent = `Page ${currentPage} of ${totalPages}`;
prevPage.disabled = currentPage <= 1;
nextPage.disabled = currentPage >= totalPages;
if (totalPages > 1) {
pagination.classList.remove('hidden');
} else {
pagination.classList.add('hidden');
}
} else {
galleryContainer.innerHTML = '';
emptyGalleryMessage.classList.remove('hidden');
pagination.classList.add('hidden');
}
} catch (error) {
console.error('Error loading gallery items:', error);
galleryContainer.innerHTML = '<div class="col-span-full text-center py-8 text-red-500">Error loading gallery. Please try again.</div>';
}
}
function renderGalleryItems(items) {
galleryContainer.innerHTML = '';
items.forEach(item => {
const galleryItem = document.createElement('div');
galleryItem.className = 'gallery-item bg-white rounded-lg overflow-hidden shadow-sm border border-gray-100 cursor-pointer';
galleryItem.addEventListener('click', () => openMediaModal(item));
if (item.type === 'image') {
galleryItem.innerHTML = `
<img src="${item.url}" alt="${item.description || 'Gallery image'}" class="w-full h-48 object-cover">
<div class="p-3">
<p class="text-sm text-gray-700 truncate">${item.description || 'No description'}</p>
<p class="text-xs text-gray-500 mt-1">${formatDate(item.created_at)}</p>
</div>
`;
} else {
galleryItem.innerHTML = `
<div class="relative w-full h-48 bg-gray-200 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
<div class="absolute bottom-2 right-2 bg-black bg-opacity-50 text-white text-xs px-1 rounded">VIDEO</div>
</div>
<div class="p-3">
<p class="text-sm text-gray-700 truncate">${item.description || 'No description'}</p>
<p class="text-xs text-gray-500 mt-1">${formatDate(item.created_at)}</p>
</div>
`;
}
galleryContainer.appendChild(galleryItem);
});
}
function openMediaModal(item) {
modalTitle.textContent = item.description || (item.type === 'image' ? 'Image' : 'Video');
modalDescription.textContent = item.description || 'No description provided';
modalDate.textContent = formatDate(item.created_at, true);
modalContent.innerHTML = '';
if (item.type === 'image') {
const img = document.createElement('img');
img.src = item.url;
img.alt = item.description || 'Gallery image';
img.className = 'max-w-full max-h-[70vh] object-contain';
modalContent.appendChild(img);
} else {
const videoContainer = document.createElement('div');
videoContainer.className = 'w-full';
videoContainer.innerHTML = `
<video controls class="max-w-full max-h-[70vh]">
<source src="${item.url}" type="video/mp4">
Your browser does not support the video tag.
</video>
`;
modalContent.appendChild(videoContainer);
}
mediaModal.classList.remove('hidden');
}
function formatDate(dateString, full = false) {
const date = new Date(dateString);
if (full) {
return date.toLocaleString();
}
return date.toLocaleDateString();
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Dagfinn1962/gallery202508" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>