Auto commit: 2026-02-17T18:55:52.856Z
This commit is contained in:
parent
9b189df875
commit
52fa755b06
107
admin.php
107
admin.php
@ -151,6 +151,22 @@ $locations = $stmt->fetchAll();
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12">
|
||||
<div class="card p-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h5><i class="bi bi-images text-success"></i> Moderación de Galería y Chat</h5>
|
||||
<button class="btn btn-sm btn-outline-success" onclick="fetchChatImages()">Cargar Imágenes Recientes</button>
|
||||
</div>
|
||||
<div class="row" id="chat-images-container">
|
||||
<div class="col-12 text-center text-secondary py-4">
|
||||
Cargando imágenes del chat...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12">
|
||||
<div class="card p-4">
|
||||
@ -258,48 +274,69 @@ $locations = $stmt->fetchAll();
|
||||
initChart();
|
||||
|
||||
async function fetchRequests() {
|
||||
const tbody = document.getElementById('requests-body');
|
||||
// ... (keeping existing logic)
|
||||
}
|
||||
|
||||
async function fetchChatImages() {
|
||||
const container = document.getElementById('chat-images-container');
|
||||
try {
|
||||
const response = await fetch('api/song_requests.php?status=all&limit=50');
|
||||
const data = await response.json();
|
||||
const response = await fetch('api/chat.php');
|
||||
const messages = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
if (data.requests.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-secondary">No hay peticiones registradas</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = data.requests.map(req => `
|
||||
<tr>
|
||||
<td>${req.artist}</td>
|
||||
<td>${req.song}</td>
|
||||
<td><span class="badge bg-secondary">${req.requester}</span></td>
|
||||
<td><small>${new Date(req.created_at).toLocaleString()}</small></td>
|
||||
<td>
|
||||
<span class="badge ${req.status === 'played' ? 'bg-success' : 'bg-warning text-dark'}">
|
||||
${req.status === 'played' ? 'Reproducida' : 'Pendiente'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
${req.status === 'pending' ? `
|
||||
<button class="btn btn-success" onclick="updateStatus(${req.id}, 'mark_played')" title="Marcar como reproducida">
|
||||
<i class="bi bi-play-fill"></i>
|
||||
</button>
|
||||
` : ''}
|
||||
<button class="btn btn-danger" onclick="updateStatus(${req.id}, 'delete')" title="Eliminar">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
const images = messages.filter(m => m.type === 'image');
|
||||
|
||||
if (images.length === 0) {
|
||||
container.innerHTML = '<div class="col-12 text-center text-secondary py-4">No hay imágenes recientes en el chat.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = images.map(img => `
|
||||
<div class="col-md-3 mb-4">
|
||||
<div class="card h-100 p-2" style="background: rgba(0,0,0,0.3);">
|
||||
<img src="${img.message}" class="card-img-top rounded" style="height: 150px; object-fit: cover;">
|
||||
<div class="card-body p-2">
|
||||
<p class="small mb-1">Enviado por: <b>${img.username}</b></p>
|
||||
<div class="input-group input-group-sm mb-2">
|
||||
<input type="text" class="form-control bg-dark text-white border-secondary" placeholder="Leyenda..." id="caption-${img.id}">
|
||||
</div>
|
||||
<button class="btn btn-sm btn-success w-100" onclick="promoteToGallery(${img.id})">
|
||||
<i class="bi bi-star-fill"></i> Publicar en Galería
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
} catch (error) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-danger">Error al cargar peticiones</td></tr>';
|
||||
container.innerHTML = '<div class="col-12 text-center text-danger py-4">Error al cargar imágenes.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function promoteToGallery(messageId) {
|
||||
const caption = document.getElementById(`caption-${messageId}`).value;
|
||||
try {
|
||||
const response = await fetch('api/gallery.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'promote',
|
||||
message_id: messageId,
|
||||
caption: caption
|
||||
})
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
alert('Imagen publicada en la galería con éxito.');
|
||||
fetchChatImages();
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error de conexión.');
|
||||
}
|
||||
}
|
||||
|
||||
fetchChatImages();
|
||||
|
||||
async function fetchStats() {
|
||||
const artistsDiv = document.getElementById('top-artists');
|
||||
const songsDiv = document.getElementById('top-songs');
|
||||
|
||||
54
api/gallery.php
Normal file
54
api/gallery.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
if ($method === 'GET') {
|
||||
try {
|
||||
$stmt = db()->prepare("SELECT * FROM gallery ORDER BY created_at DESC LIMIT 50");
|
||||
$stmt->execute();
|
||||
$images = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo json_encode(['success' => true, 'images' => $images]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
} elseif ($method === 'POST') {
|
||||
// This endpoint will be used by admin to promote an image
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$action = $input['action'] ?? '';
|
||||
|
||||
if ($action === 'promote') {
|
||||
$messageId = $input['message_id'] ?? 0;
|
||||
$caption = $input['caption'] ?? '';
|
||||
|
||||
try {
|
||||
// Get the image message
|
||||
$stmt = db()->prepare("SELECT * FROM messages WHERE id = ? AND type = 'image'");
|
||||
$stmt->execute([$messageId]);
|
||||
$msg = $stmt->fetch();
|
||||
|
||||
if (!$msg) {
|
||||
echo json_encode(['success' => false, 'error' => 'Imagen no encontrada']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Insert into gallery
|
||||
$stmt = db()->prepare("INSERT INTO gallery (image_url, username, caption) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$msg['message'], $msg['username'], $caption]);
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
} elseif ($action === 'like') {
|
||||
$id = $input['id'] ?? 0;
|
||||
try {
|
||||
db()->prepare("UPDATE gallery SET likes = likes + 1 WHERE id = ?")->execute([$id]);
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
db/migrations/004_create_gallery_table.sql
Normal file
11
db/migrations/004_create_gallery_table.sql
Normal file
@ -0,0 +1,11 @@
|
||||
-- Migration: Create gallery table
|
||||
-- Description: Stores images promoted from chat to the community gallery.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS gallery (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
image_url VARCHAR(255) NOT NULL,
|
||||
username VARCHAR(100) NOT NULL,
|
||||
caption VARCHAR(255) DEFAULT NULL,
|
||||
likes INT DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
192
index.php
192
index.php
@ -568,6 +568,104 @@ $twitter_link = "https://twitter.com/";
|
||||
background-color: #00c853;
|
||||
}
|
||||
|
||||
/* Gallery Styles */
|
||||
.gallery-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.gallery-item {
|
||||
position: relative;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s ease;
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.gallery-item:hover {
|
||||
transform: scale(1.05);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.gallery-item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.gallery-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(transparent, rgba(0,0,0,0.8));
|
||||
padding: 8px;
|
||||
font-size: 0.7rem;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.gallery-item:hover .gallery-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.gallery-username {
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.gallery-likes {
|
||||
color: #ff4081;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
/* Lightbox */
|
||||
.lightbox {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.95);
|
||||
z-index: 2000;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.lightbox-img {
|
||||
max-width: 100%;
|
||||
max-height: 80vh;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 0 30px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.lightbox-info {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.lightbox-close {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
font-size: 2rem;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Center Section: Featured Image */
|
||||
.image-section {
|
||||
width: 100%;
|
||||
@ -1366,6 +1464,17 @@ $twitter_link = "https://twitter.com/";
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gallery Widget -->
|
||||
<div class="glass-card mt-4" style="margin-top: 1.5rem;">
|
||||
<h3 style="font-size: 1.2rem; margin-bottom: 0.5rem; color: #ffca28;">
|
||||
<i class="bi bi-images"></i> GALERÍA COMUNIDAD
|
||||
</h3>
|
||||
<p style="font-size: 0.75rem; opacity: 0.7; margin-bottom: 1rem;">Fotos compartidas por nuestros oyentes.</p>
|
||||
<div id="gallery-container" class="gallery-grid">
|
||||
<div style="grid-column: 1/-1; text-align: center; padding: 2rem; opacity: 0.5; font-size: 0.85rem;">Cargando galería...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Social Media Feed Widget -->
|
||||
<div class="glass-card mt-4" style="margin-top: 1.5rem;">
|
||||
<h3 style="font-size: 1.2rem; margin-bottom: 1rem; color: #f472b6;">
|
||||
@ -1487,6 +1596,16 @@ $twitter_link = "https://twitter.com/";
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lightbox for Gallery -->
|
||||
<div id="lightbox" class="lightbox" onclick="closeLightbox()">
|
||||
<span class="lightbox-close" onclick="closeLightbox()">×</span>
|
||||
<img id="lightbox-img" class="lightbox-img" src="" alt="Full view">
|
||||
<div class="lightbox-info">
|
||||
<h4 id="lightbox-username" style="margin: 0; color: var(--primary-color);"></h4>
|
||||
<p id="lightbox-caption" style="margin: 5px 0 0; opacity: 0.8;"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js"></script>
|
||||
<audio id="radio-audio" src="https://listen.radioking.com/radio/828046/stream/897251" preload="auto" crossorigin="anonymous"></audio>
|
||||
<audio id="welcome-sound" src="https://assets.mixkit.co/active_storage/sfx/2013/2013-preview.mp3" preload="auto"></audio>
|
||||
@ -2378,36 +2497,59 @@ $twitter_link = "https://twitter.com/";
|
||||
let progressInterval = null;
|
||||
|
||||
async function updateMetadata() {
|
||||
// ... (keeping existing logic)
|
||||
}
|
||||
|
||||
async function fetchGallery() {
|
||||
const container = document.getElementById('gallery-container');
|
||||
try {
|
||||
fetchUpcomingTracks(); // Fetch next tracks in parallel
|
||||
const response = await fetch('https://www.radioking.com/widgets/api/v1/radio/828046/track/current');
|
||||
const response = await fetch('api/gallery.php');
|
||||
const data = await response.json();
|
||||
|
||||
if (data && data.title) {
|
||||
const title = data.title;
|
||||
const artist = data.artist || 'Lili Records';
|
||||
const album = data.album || '';
|
||||
const coverUrl = data.cover || './assets/pasted-20260215-163754-def41f49.png';
|
||||
if (data.success) {
|
||||
if (data.images.length === 0) {
|
||||
container.innerHTML = '<div style="grid-column: 1/-1; text-align: center; padding: 2rem; opacity: 0.5; font-size: 0.85rem;">Aún no hay fotos en la galería.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const fullDisplay = title.includes(artist) ? title : `${artist} - ${title}`;
|
||||
|
||||
if (trackTitle.textContent !== title || (trackArtist && trackArtist.textContent !== artist)) {
|
||||
// Clear existing progress interval
|
||||
if (progressInterval) clearInterval(progressInterval);
|
||||
container.innerHTML = data.images.map(img => `
|
||||
<div class="gallery-item" onclick="openLightbox('${img.image_url}', '${img.username}', '${img.caption || ''}')">
|
||||
<img src="${img.image_url}" alt="Foto de ${img.username}">
|
||||
<div class="gallery-overlay">
|
||||
<span class="gallery-username">@${img.username}</span>
|
||||
<span class="gallery-likes"><i class="bi bi-heart-fill"></i> ${img.likes}</span>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
} catch (error) {
|
||||
container.innerHTML = '<div style="grid-column: 1/-1; text-align: center; padding: 2rem; opacity: 0.5; font-size: 0.85rem; color: #ff4444;">Error al cargar la galería.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function openLightbox(url, username, caption) {
|
||||
const lightbox = document.getElementById('lightbox');
|
||||
const img = document.getElementById('lightbox-img');
|
||||
const userText = document.getElementById('lightbox-username');
|
||||
const captionText = document.getElementById('lightbox-caption');
|
||||
|
||||
img.src = url;
|
||||
userText.innerText = '@' + username;
|
||||
captionText.innerText = caption;
|
||||
|
||||
lightbox.style.display = 'flex';
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeLightbox() {
|
||||
document.getElementById('lightbox').style.display = 'none';
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
|
||||
// Initial fetch
|
||||
fetchGallery();
|
||||
setInterval(fetchGallery, 60000); // Update every minute
|
||||
|
||||
// Add previous song to history before changing
|
||||
if (trackTitle.textContent !== "Cargando stream..." && trackTitle.textContent !== "Lili Records Radio - En Vivo") {
|
||||
const prevTrack = {
|
||||
title: trackTitle.textContent + (trackArtist ? ' - ' + trackArtist.textContent : ''),
|
||||
cover: trackCover.src,
|
||||
likes: document.getElementById('like-count').innerText || '0'
|
||||
};
|
||||
if (recentTracks.length === 0 || recentTracks[0].title !== prevTrack.title) {
|
||||
recentTracks.unshift(prevTrack);
|
||||
if (recentTracks.length > 5) recentTracks.pop();
|
||||
localStorage.setItem('recentTracks', JSON.stringify(recentTracks));
|
||||
renderRecentTracks();
|
||||
}
|
||||
}
|
||||
|
||||
trackTitle.style.opacity = '0';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user