feat: dodanie historii kolejki z możliwością ponownego odtwarzania w dashboardzie oraz komendy /history i /replay w Discord
This commit is contained in:
4 files changed
+248
-4
No files matched your search
@@ -50,6 +50,9 @@ const uploadStatusText = document.getElementById('upload-status-text');
|
||||
const uploadsList = document.getElementById('uploads-list');
|
||||
const emptyUploadsMsg = document.getElementById('empty-uploads-msg');
|
||||
const uploadsCountBadge = document.getElementById('uploads-count');
|
||||
const historyList = document.getElementById('history-list');
|
||||
const emptyHistoryMsg = document.getElementById('empty-history-msg');
|
||||
const historyCountBadge = document.getElementById('history-count');
|
||||
|
||||
// Progress Bar DOM Elements
|
||||
const progressContainer = document.getElementById('progress-container');
|
||||
@@ -390,6 +393,7 @@ function updateUI(status) {
|
||||
// 6. Queue render (if not currently dragging)
|
||||
if (!isDragging) {
|
||||
renderQueue(status.queue);
|
||||
renderHistory(status.history || []);
|
||||
}
|
||||
|
||||
// Re-trigger lucide icons update
|
||||
@@ -862,3 +866,75 @@ async function deleteUploadedFile(filename) {
|
||||
}
|
||||
}
|
||||
|
||||
// Render History
|
||||
function renderHistory(history) {
|
||||
if (!historyList) return;
|
||||
const items = historyList.querySelectorAll('.queue-item');
|
||||
items.forEach(el => el.remove());
|
||||
|
||||
if (!history || history.length === 0) {
|
||||
if (emptyHistoryMsg) emptyHistoryMsg.style.display = 'flex';
|
||||
if (historyCountBadge) historyCountBadge.textContent = '0 utworów';
|
||||
return;
|
||||
}
|
||||
|
||||
if (emptyHistoryMsg) emptyHistoryMsg.style.display = 'none';
|
||||
if (historyCountBadge) historyCountBadge.textContent = `${history.length} ${history.length === 1 ? 'utwór' : history.length < 5 ? 'utwory' : 'utworów'}`;
|
||||
|
||||
history.forEach((item, index) => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'queue-item';
|
||||
|
||||
let durationStr = '--:--';
|
||||
if (item.duration) {
|
||||
const mins = Math.floor(item.duration / 60);
|
||||
const secs = Math.floor(item.duration % 60);
|
||||
durationStr = `${mins}:${secs < 10 ? '0' : ''}${secs}`;
|
||||
}
|
||||
|
||||
const safeTitle = (item.title || 'Nieznany tytuł').replace(/'/g, "\\'");
|
||||
const safeQuery = (item.is_local ? (item.filepath || item.title) : item.url).replace(/'/g, "\\'");
|
||||
|
||||
li.innerHTML = `
|
||||
<div class="queue-index"><i data-lucide="rotate-ccw"></i></div>
|
||||
<div class="queue-track-info" style="flex: 1; min-width: 0;">
|
||||
<div class="queue-track-title" title="${item.title}" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${item.title}</div>
|
||||
<div class="queue-track-artist">${durationStr} ${item.is_local ? '• plik lokalny' : ''}</div>
|
||||
</div>
|
||||
<div class="queue-item-actions">
|
||||
<button class="btn btn-secondary" style="padding: 4px 10px; font-size: 0.8rem; display: flex; align-items: center; gap: 4px;" onclick="replayFromHistory('${safeQuery}', ${item.is_local})" title="Dodaj ponownie do kolejki">
|
||||
<i data-lucide="plus"></i> Dodaj
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
historyList.appendChild(li);
|
||||
});
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
// Replay from History
|
||||
async function replayFromHistory(queryOrFile, isLocal) {
|
||||
if (!currentStatus || !currentStatus.active_voice_channel) {
|
||||
showToast('Bot musi być połączony z kanałem głosowym!', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLocal) {
|
||||
const filename = queryOrFile.split('/').pop().split('\\').pop();
|
||||
playUploadedFile(filename);
|
||||
} else {
|
||||
try {
|
||||
const res = await fetch('/api/play', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query: queryOrFile })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.detail || 'Błąd odtwarzania');
|
||||
showToast('Dodano z historii do kolejki!', 'success');
|
||||
} catch (err) {
|
||||
showToast(`Błąd: ${err.message}`, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-3
@@ -181,15 +181,31 @@
|
||||
<!-- Queue List Panel -->
|
||||
<div class="glass-card queue-panel">
|
||||
<div class="queue-header">
|
||||
<h2 class="card-title"><i data-lucide="list-music"></i> Kolejka utworów</h2>
|
||||
<h2 class="card-title"><i data-lucide="list-music"></i> Kolejka odtwarzania</h2>
|
||||
<span class="queue-count" id="queue-count">0 utworów</span>
|
||||
</div>
|
||||
<div class="queue-list-container">
|
||||
<ul class="queue-list" id="queue-list">
|
||||
<!-- Items dynamically injected here -->
|
||||
<!-- Populated via JS -->
|
||||
<div class="empty-queue-message" id="empty-queue-msg">
|
||||
<i data-lucide="disc"></i>
|
||||
<p>Kolejka jest pusta. Dodaj utwór powyżej!</p>
|
||||
<p>Kolejka jest pusta. Dodaj coś!</p>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- History List Panel -->
|
||||
<div class="glass-card history-panel">
|
||||
<div class="queue-header">
|
||||
<h2 class="card-title"><i data-lucide="history"></i> Historia odtwarzania</h2>
|
||||
<span class="queue-count" id="history-count">0 utworów</span>
|
||||
</div>
|
||||
<div class="queue-list-container" style="max-height: 250px;">
|
||||
<ul class="queue-list" id="history-list">
|
||||
<div class="empty-queue-message" id="empty-history-msg">
|
||||
<i data-lucide="clock"></i>
|
||||
<p>Historia odtwarzania jest pusta.</p>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user