//js/ui-interactions.js
import {
SVG_CLOSE,
SVG_BIN,
SVG_HEART,
SVG_DOWNLOAD,
formatTime,
trackDataStore,
getTrackTitle,
getTrackArtists,
escapeHtml,
} from './utils.js';
import { sidePanelManager } from './side-panel.js';
export function initializeUIInteractions(player, api) {
const sidebar = document.querySelector('.sidebar');
const sidebarOverlay = document.getElementById('sidebar-overlay');
const hamburgerBtn = document.getElementById('hamburger-btn');
const queueBtn = document.getElementById('queue-btn');
let draggedQueueIndex = null;
// Sidebar mobile
hamburgerBtn.addEventListener('click', () => {
sidebar.classList.add('is-open');
sidebarOverlay.classList.add('is-visible');
});
const closeSidebar = () => {
sidebar.classList.remove('is-open');
sidebarOverlay.classList.remove('is-visible');
};
sidebarOverlay.addEventListener('click', closeSidebar);
sidebar.addEventListener('click', (e) => {
if (e.target.closest('a')) {
closeSidebar();
}
});
// Queue panel
const renderQueueControls = (container) => {
const currentQueue = player.getCurrentQueue();
const showActionBtns = currentQueue.length > 0;
container.innerHTML = `
`;
container.querySelector('#close-side-panel-btn').addEventListener('click', () => {
sidePanelManager.close();
});
const downloadBtn = container.querySelector('#download-queue-btn');
if (downloadBtn) {
downloadBtn.addEventListener('click', async () => {
const { downloadTracks } = await import('./downloads.js');
downloadTracks(currentQueue);
});
}
const likeBtn = container.querySelector('#like-queue-btn');
if (likeBtn) {
likeBtn.addEventListener('click', async () => {
const { db } = await import('./db.js');
const { syncManager } = await import('./firebase/sync.js');
const { showNotification } = await import('./downloads.js');
let addedCount = 0;
for (const track of currentQueue) {
const wasAdded = await db.toggleFavorite('track', track);
if (wasAdded) {
syncManager.syncLibraryItem('track', track, true);
addedCount++;
}
}
if (addedCount > 0) {
showNotification(`Added ${addedCount} track${addedCount > 1 ? 's' : ''} to Liked`);
} else {
showNotification('All tracks in queue are already liked');
}
refreshQueuePanel();
});
}
const addToPlaylistBtn = container.querySelector('#add-queue-to-playlist-btn');
if (addToPlaylistBtn) {
addToPlaylistBtn.addEventListener('click', async () => {
const { db } = await import('./db.js');
const { syncManager } = await import('./firebase/sync.js');
const { showNotification } = await import('./downloads.js');
const playlists = await db.getPlaylists();
if (playlists.length === 0) {
showNotification('No playlists yet. Create one first.');
return;
}
const modal = document.createElement('div');
modal.className = 'modal active';
modal.innerHTML = `
Add Queue to Playlist
${playlists
.map(
(p) => `
${escapeHtml(p.name)}
`
)
.join('')}
`;
document.body.appendChild(modal);
const closeModal = () => {
modal.remove();
};
modal.addEventListener('click', async (e) => {
if (e.target.classList.contains('modal-overlay') || e.target.classList.contains('cancel-btn')) {
closeModal();
return;
}
const option = e.target.closest('.modal-option');
if (option) {
const playlistId = option.dataset.id;
const playlistName = option.textContent;
try {
let addedCount = 0;
for (const track of currentQueue) {
const playlist = await db.addTrackToPlaylist(playlistId, track);
addedCount++;
}
const updatedPlaylist = await db.getPlaylist(playlistId);
syncManager.syncUserPlaylist(updatedPlaylist, 'update');
showNotification(`Added ${addedCount} tracks to playlist: ${playlistName}`);
} catch (error) {
console.error('Failed to add tracks to playlist:', error);
showNotification('Failed to add tracks to playlist');
}
closeModal();
}
});
});
}
const clearBtn = container.querySelector('#clear-queue-btn');
if (clearBtn) {
clearBtn.addEventListener('click', () => {
player.clearQueue();
refreshQueuePanel();
});
}
};
const renderQueueContent = (container) => {
const currentQueue = player.getCurrentQueue();
if (currentQueue.length === 0) {
container.innerHTML = 'Queue is empty.
';
return;
}
const html = currentQueue
.map((track, index) => {
const isPlaying = index === player.currentQueueIndex;
const trackTitle = getTrackTitle(track);
const trackArtists = getTrackArtists(track, { fallback: 'Unknown' });
return `
${escapeHtml(trackTitle)}
${escapeHtml(trackArtists)}
${formatTime(track.duration)}
`;
})
.join('');
container.innerHTML = html;
container.querySelectorAll('.queue-track-item').forEach(async (item) => {
const index = parseInt(item.dataset.queueIndex);
const track = player.getCurrentQueue()[index];
// Update like button state
const likeBtn = item.querySelector('.queue-like-btn');
if (likeBtn && track) {
const { db } = await import('./db.js');
const isLiked = await db.isFavorite('track', track.id);
likeBtn.classList.toggle('active', isLiked);
likeBtn.innerHTML = isLiked
? SVG_HEART.replace('class="heart-icon"', 'class="heart-icon filled"')
: SVG_HEART;
}
item.addEventListener('click', async (e) => {
const removeBtn = e.target.closest('.queue-remove-btn');
if (removeBtn) {
e.stopPropagation();
player.removeFromQueue(index);
refreshQueuePanel();
return;
}
const likeBtn = e.target.closest('.queue-like-btn');
if (likeBtn && likeBtn.dataset.action === 'toggle-like') {
e.stopPropagation();
const track = player.getCurrentQueue()[index];
if (track) {
const { db } = await import('./db.js');
const { syncManager } = await import('./firebase/sync.js');
const { showNotification } = await import('./downloads.js');
const added = await db.toggleFavorite('track', track);
syncManager.syncLibraryItem('track', track, added);
// Update button state
likeBtn.classList.toggle('active', added);
likeBtn.innerHTML = added
? SVG_HEART.replace('class="heart-icon"', 'class="heart-icon filled"')
: SVG_HEART;
showNotification(
added ? `Added to Liked: ${track.title}` : `Removed from Liked: ${track.title}`
);
}
return;
}
player.playAtIndex(index);
refreshQueuePanel();
});
item.addEventListener('contextmenu', async (e) => {
e.preventDefault();
const contextMenu = document.getElementById('context-menu');
if (contextMenu) {
const track = player.getCurrentQueue()[index];
if (track) {
const { db } = await import('./db.js');
const isLiked = await db.isFavorite('track', track.id);
const likeItem = contextMenu.querySelector('li[data-action="toggle-like"]');
if (likeItem) {
likeItem.textContent = isLiked ? 'Unlike' : 'Like';
}
const trackMixItem = contextMenu.querySelector('li[data-action="track-mix"]');
if (trackMixItem) {
const hasMix = track.mixes && track.mixes.TRACK_MIX;
trackMixItem.style.display = hasMix ? 'block' : 'none';
}
const rect = item.getBoundingClientRect();
const menuWidth = 150;
const menuHeight = 200;
let left = e.clientX;
let top = e.clientY;
if (left + menuWidth > window.innerWidth) {
left = window.innerWidth - menuWidth - 10;
}
if (top + menuHeight > window.innerHeight) {
top = e.clientY - menuHeight - 10;
}
contextMenu.style.left = `${left}px`;
contextMenu.style.top = `${top}px`;
contextMenu.style.display = 'block';
contextMenu._contextTrack = track;
}
}
});
item.addEventListener('dragstart', (e) => {
draggedQueueIndex = index;
item.style.opacity = '0.5';
});
item.addEventListener('dragend', () => {
item.style.opacity = '1';
});
item.addEventListener('dragover', (e) => {
e.preventDefault();
});
item.addEventListener('drop', (e) => {
e.preventDefault();
if (draggedQueueIndex !== null && draggedQueueIndex !== index) {
player.moveInQueue(draggedQueueIndex, index);
refreshQueuePanel();
}
});
});
};
const refreshQueuePanel = () => {
sidePanelManager.refresh('queue', renderQueueControls, renderQueueContent);
};
const openQueuePanel = () => {
sidePanelManager.open('queue', 'Queue', renderQueueControls, renderQueueContent);
};
queueBtn.addEventListener('click', openQueuePanel);
// Expose renderQueue for external updates (e.g. shuffle, add to queue)
window.renderQueueFunction = () => {
if (sidePanelManager.isActive('queue')) {
refreshQueuePanel();
}
};
// Search and Library tabs
document.querySelectorAll('.search-tab').forEach((tab) => {
tab.addEventListener('click', () => {
const page = tab.closest('.page');
if (!page) return;
page.querySelectorAll('.search-tab').forEach((t) => t.classList.remove('active'));
page.querySelectorAll('.search-tab-content').forEach((c) => c.classList.remove('active'));
tab.classList.add('active');
const prefix = page.id === 'page-library' ? 'library-tab-' : 'search-tab-';
const contentId = `${prefix}${tab.dataset.tab}`;
document.getElementById(contentId)?.classList.add('active');
});
});
}