Implement Playlist Creation in Video Library
PLAYLIST CREATION FEATURES: - Multi-selection system with visual checkboxes and green highlighting - Select mode toggle ( Select / Exit Select) for batch operations - Dynamic playlist creation button with selection counter - Professional creation modal with video preview and removal options - Two creation modes: Create Playlist & Create & Switch to Playlist VIDEO LIBRARY ENHANCEMENTS: - Added playlist management controls to library header - Implemented multi-selection state tracking with Set-based storage - Enhanced video cards/items with selection checkboxes and styling - Smart playlist naming with auto-increment (My Playlist 1, 2, etc.) - Duplicate name handling with user confirmation for overwrites UI/UX IMPROVEMENTS: - Professional playlist creation modal with responsive design - Visual selection indicators (green borders, checkboxes) - Real-time button text updates based on selection count - Individual video removal in creation dialog - Consistent styling with existing cinema theme TECHNICAL IMPLEMENTATION: - Extended VideoLibrary class with playlist creation methods - Integration with existing localStorage playlist system - Cross-feature compatibility with save/load functionality - Error handling and user feedback notifications - Keyboard support (Enter to create) and accessibility features WORKFLOW BENEFITS: - Bulk playlist creation from selected videos - Empty playlist creation for manual building - Immediate playlist switching after creation - Seamless integration with existing playlist management - Enhanced user experience for playlist organization The video library now provides complete playlist creation capabilities, allowing users to efficiently organize their video collections into custom playlists.
This commit is contained in:
parent
a34aa9c86b
commit
b360e4d68d
|
|
@ -151,6 +151,10 @@
|
|||
<button id="grid-view-btn" class="view-btn active" title="Grid View">⊞</button>
|
||||
<button id="list-view-btn" class="view-btn" title="List View">☰</button>
|
||||
</div>
|
||||
<div class="playlist-management">
|
||||
<button id="create-playlist-btn" class="btn btn-mini" title="Create New Playlist">📝 New Playlist</button>
|
||||
<button id="select-mode-btn" class="btn btn-mini" title="Select Multiple Videos">☑️ Select</button>
|
||||
</div>
|
||||
<div class="sort-controls">
|
||||
<select id="sort-select" class="sort-dropdown">
|
||||
<option value="name">Sort by Name</option>
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -13,6 +13,8 @@ class VideoLibrary {
|
|||
this.sortDirection = 'asc';
|
||||
this.searchQuery = '';
|
||||
this.selectedVideo = null;
|
||||
this.selectedVideos = new Set(); // For multi-selection
|
||||
this.isSelectMode = false; // Track if we're in multi-select mode
|
||||
|
||||
this.initializeElements();
|
||||
this.attachEventListeners();
|
||||
|
|
@ -31,6 +33,10 @@ class VideoLibrary {
|
|||
this.searchInput = document.getElementById('library-search');
|
||||
this.refreshBtn = document.getElementById('refresh-library');
|
||||
|
||||
// Playlist controls
|
||||
this.createPlaylistBtn = document.getElementById('create-playlist-btn');
|
||||
this.selectModeBtn = document.getElementById('select-mode-btn');
|
||||
|
||||
// Content container
|
||||
this.libraryContent = document.getElementById('library-content');
|
||||
}
|
||||
|
|
@ -47,6 +53,10 @@ class VideoLibrary {
|
|||
// Search
|
||||
this.searchInput.addEventListener('input', (e) => this.setSearchQuery(e.target.value));
|
||||
this.refreshBtn.addEventListener('click', () => this.refreshLibrary());
|
||||
|
||||
// Playlist controls
|
||||
this.createPlaylistBtn.addEventListener('click', () => this.createNewPlaylist());
|
||||
this.selectModeBtn.addEventListener('click', () => this.toggleSelectMode());
|
||||
}
|
||||
|
||||
async loadVideoLibrary() {
|
||||
|
|
@ -282,13 +292,16 @@ class VideoLibrary {
|
|||
createVideoElement(video) {
|
||||
const duration = this.formatDuration(video.duration);
|
||||
const fileSize = this.formatFileSize(video.size);
|
||||
const isSelected = this.selectedVideos.has(video.path);
|
||||
const selectionClass = isSelected ? 'multi-selected' : '';
|
||||
|
||||
// Generate or get thumbnail
|
||||
const thumbnailSrc = this.getThumbnailSrc(video);
|
||||
|
||||
if (this.currentView === 'grid') {
|
||||
return `
|
||||
<div class="video-card" data-video-path="${video.path}">
|
||||
<div class="video-card ${selectionClass}" data-video-path="${video.path}">
|
||||
${this.isSelectMode ? `<div class="video-selection-checkbox ${isSelected ? 'checked' : ''}">✓</div>` : ''}
|
||||
<div class="video-thumbnail" data-video-path="${video.path}">
|
||||
${thumbnailSrc ?
|
||||
`<img src="${thumbnailSrc}" alt="${video.name}" onload="this.style.opacity=1" style="opacity:0; transition: opacity 0.3s;">` :
|
||||
|
|
@ -311,7 +324,8 @@ class VideoLibrary {
|
|||
`;
|
||||
} else {
|
||||
return `
|
||||
<div class="video-list-item" data-video-path="${video.path}">
|
||||
<div class="video-list-item ${selectionClass}" data-video-path="${video.path}">
|
||||
${this.isSelectMode ? `<div class="video-selection-checkbox ${isSelected ? 'checked' : ''}">✓</div>` : ''}
|
||||
<div class="list-thumbnail" data-video-path="${video.path}">
|
||||
${thumbnailSrc ?
|
||||
`<img src="${thumbnailSrc}" alt="${video.name}" class="list-thumbnail" onload="this.style.opacity=1" style="opacity:0; transition: opacity 0.3s;">` :
|
||||
|
|
@ -341,7 +355,22 @@ class VideoLibrary {
|
|||
if (e.target.closest('.video-actions')) return; // Don't trigger on action buttons
|
||||
|
||||
const videoPath = element.dataset.videoPath;
|
||||
this.selectVideo(videoPath);
|
||||
|
||||
if (this.isSelectMode) {
|
||||
this.toggleVideoSelection(videoPath);
|
||||
} else {
|
||||
this.selectVideo(videoPath);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Selection checkbox click events
|
||||
const checkboxes = this.libraryContent.querySelectorAll('.video-selection-checkbox');
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const videoPath = checkbox.closest('[data-video-path]').dataset.videoPath;
|
||||
this.toggleVideoSelection(videoPath);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -830,4 +859,298 @@ class VideoLibrary {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== PLAYLIST CREATION METHODS =====
|
||||
|
||||
toggleSelectMode() {
|
||||
this.isSelectMode = !this.isSelectMode;
|
||||
|
||||
// Update button appearance
|
||||
this.selectModeBtn.classList.toggle('active', this.isSelectMode);
|
||||
this.selectModeBtn.textContent = this.isSelectMode ? '✓ Exit Select' : '☑️ Select';
|
||||
|
||||
// Clear selections when exiting select mode
|
||||
if (!this.isSelectMode) {
|
||||
this.selectedVideos.clear();
|
||||
}
|
||||
|
||||
// Re-render library to show/hide checkboxes
|
||||
this.displayLibrary();
|
||||
|
||||
// Show create playlist button if videos are selected
|
||||
this.updatePlaylistControls();
|
||||
}
|
||||
|
||||
toggleVideoSelection(videoPath) {
|
||||
if (this.selectedVideos.has(videoPath)) {
|
||||
this.selectedVideos.delete(videoPath);
|
||||
} else {
|
||||
this.selectedVideos.add(videoPath);
|
||||
}
|
||||
|
||||
// Update visual selection
|
||||
const videoElement = this.libraryContent.querySelector(`[data-video-path="${videoPath}"]`);
|
||||
if (videoElement) {
|
||||
videoElement.classList.toggle('multi-selected', this.selectedVideos.has(videoPath));
|
||||
|
||||
const checkbox = videoElement.querySelector('.video-selection-checkbox');
|
||||
if (checkbox) {
|
||||
checkbox.classList.toggle('checked', this.selectedVideos.has(videoPath));
|
||||
}
|
||||
}
|
||||
|
||||
this.updatePlaylistControls();
|
||||
}
|
||||
|
||||
updatePlaylistControls() {
|
||||
// Update create playlist button text based on selection
|
||||
if (this.selectedVideos.size > 0) {
|
||||
this.createPlaylistBtn.textContent = `📝 Create Playlist (${this.selectedVideos.size})`;
|
||||
this.createPlaylistBtn.classList.add('has-selection');
|
||||
} else {
|
||||
this.createPlaylistBtn.textContent = '📝 New Playlist';
|
||||
this.createPlaylistBtn.classList.remove('has-selection');
|
||||
}
|
||||
}
|
||||
|
||||
createNewPlaylist() {
|
||||
if (this.selectedVideos.size > 0) {
|
||||
// Create playlist with selected videos
|
||||
this.createPlaylistFromSelection();
|
||||
} else {
|
||||
// Create empty playlist
|
||||
this.showCreatePlaylistDialog();
|
||||
}
|
||||
}
|
||||
|
||||
createPlaylistFromSelection() {
|
||||
const selectedVideoObjects = Array.from(this.selectedVideos).map(path =>
|
||||
this.videos.find(video => video.path === path)
|
||||
).filter(Boolean);
|
||||
|
||||
this.showCreatePlaylistDialog(selectedVideoObjects);
|
||||
}
|
||||
|
||||
showCreatePlaylistDialog(preselectedVideos = []) {
|
||||
// Create modal dialog for playlist creation
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'playlist-modal';
|
||||
modal.innerHTML = `
|
||||
<div class="playlist-modal-content create-playlist-modal">
|
||||
<div class="playlist-modal-header">
|
||||
<h3>Create New Playlist</h3>
|
||||
<button class="playlist-modal-close">✕</button>
|
||||
</div>
|
||||
<div class="playlist-modal-body">
|
||||
<div class="create-playlist-form">
|
||||
<label for="new-playlist-name">Playlist Name:</label>
|
||||
<input type="text" id="new-playlist-name" placeholder="Enter playlist name..." value="My Playlist ${this.getExistingPlaylistCount() + 1}">
|
||||
|
||||
${preselectedVideos.length > 0 ? `
|
||||
<div class="preselected-videos">
|
||||
<h4>Selected Videos (${preselectedVideos.length}):</h4>
|
||||
<div class="video-list">
|
||||
${preselectedVideos.map(video => `
|
||||
<div class="playlist-creation-video-item">
|
||||
<span class="video-name">${video.name}</span>
|
||||
<button class="btn-remove-from-creation" data-path="${video.path}">✕</button>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : `
|
||||
<div class="empty-playlist-note">
|
||||
<p>Creating an empty playlist. You can add videos later using the ➕ button.</p>
|
||||
</div>
|
||||
`}
|
||||
|
||||
<div class="playlist-creation-actions">
|
||||
<button class="btn-create-playlist">Create Playlist</button>
|
||||
<button class="btn-create-and-switch">Create & Switch to Playlist</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add modal styles
|
||||
modal.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10002;
|
||||
backdrop-filter: blur(5px);
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Focus on input and select text
|
||||
const input = modal.querySelector('#new-playlist-name');
|
||||
setTimeout(() => {
|
||||
input.focus();
|
||||
input.select();
|
||||
}, 100);
|
||||
|
||||
// Setup event handlers
|
||||
this.setupCreatePlaylistModalEvents(modal, preselectedVideos);
|
||||
}
|
||||
|
||||
setupCreatePlaylistModalEvents(modal, preselectedVideos) {
|
||||
const input = modal.querySelector('#new-playlist-name');
|
||||
const createBtn = modal.querySelector('.btn-create-playlist');
|
||||
const createSwitchBtn = modal.querySelector('.btn-create-and-switch');
|
||||
const closeBtn = modal.querySelector('.playlist-modal-close');
|
||||
|
||||
let currentVideos = [...preselectedVideos];
|
||||
|
||||
// Close modal handlers
|
||||
const closeModal = () => {
|
||||
document.body.removeChild(modal);
|
||||
};
|
||||
|
||||
closeBtn.addEventListener('click', closeModal);
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) closeModal();
|
||||
});
|
||||
|
||||
// Remove video from creation list
|
||||
const removeButtons = modal.querySelectorAll('.btn-remove-from-creation');
|
||||
removeButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const path = button.dataset.path;
|
||||
currentVideos = currentVideos.filter(video => video.path !== path);
|
||||
button.closest('.playlist-creation-video-item').remove();
|
||||
|
||||
// Update header count
|
||||
const videoCountSpan = modal.querySelector('.preselected-videos h4');
|
||||
if (videoCountSpan) {
|
||||
videoCountSpan.textContent = `Selected Videos (${currentVideos.length}):`;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Create playlist handlers
|
||||
const createPlaylist = async (switchToPlaylist = false) => {
|
||||
const name = input.value.trim();
|
||||
if (!name) {
|
||||
input.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const playlistData = {
|
||||
name: name,
|
||||
created: new Date().toISOString(),
|
||||
videos: currentVideos.map(video => ({
|
||||
name: video.name,
|
||||
path: video.path,
|
||||
duration: video.duration,
|
||||
size: video.size,
|
||||
type: video.type
|
||||
})),
|
||||
count: currentVideos.length
|
||||
};
|
||||
|
||||
// Save to localStorage
|
||||
const savedPlaylists = this.getSavedPlaylists();
|
||||
|
||||
// Check for duplicate names
|
||||
const existingIndex = savedPlaylists.findIndex(p => p.name === name);
|
||||
if (existingIndex >= 0) {
|
||||
if (!confirm(`A playlist named "${name}" already exists. Replace it?`)) {
|
||||
return;
|
||||
}
|
||||
savedPlaylists[existingIndex] = playlistData;
|
||||
} else {
|
||||
savedPlaylists.push(playlistData);
|
||||
}
|
||||
|
||||
localStorage.setItem('pornCinema_savedPlaylists', JSON.stringify(savedPlaylists));
|
||||
|
||||
this.showLibraryNotification(`Created playlist: ${name} (${currentVideos.length} videos)`);
|
||||
|
||||
// Switch to playlist if requested
|
||||
if (switchToPlaylist && this.pornCinema) {
|
||||
this.pornCinema.loadSavedPlaylist(playlistData);
|
||||
this.showLibraryNotification(`Switched to playlist: ${name}`);
|
||||
}
|
||||
|
||||
// Clear selection
|
||||
this.selectedVideos.clear();
|
||||
this.isSelectMode = false;
|
||||
this.selectModeBtn.classList.remove('active');
|
||||
this.selectModeBtn.textContent = '☑️ Select';
|
||||
this.updatePlaylistControls();
|
||||
this.displayLibrary();
|
||||
|
||||
console.log('📝 ✅ Playlist created:', name);
|
||||
closeModal();
|
||||
|
||||
} catch (error) {
|
||||
console.error('📝 ❌ Error creating playlist:', error);
|
||||
this.showLibraryNotification('Error creating playlist');
|
||||
}
|
||||
};
|
||||
|
||||
createBtn.addEventListener('click', () => createPlaylist(false));
|
||||
createSwitchBtn.addEventListener('click', () => createPlaylist(true));
|
||||
|
||||
// Enter key to create
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
createPlaylist(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getSavedPlaylists() {
|
||||
try {
|
||||
const saved = localStorage.getItem('pornCinema_savedPlaylists');
|
||||
return saved ? JSON.parse(saved) : [];
|
||||
} catch (error) {
|
||||
console.error('📝 ❌ Error getting saved playlists:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
getExistingPlaylistCount() {
|
||||
return this.getSavedPlaylists().length;
|
||||
}
|
||||
|
||||
showLibraryNotification(message) {
|
||||
// Create a simple notification system
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'library-notification';
|
||||
notification.textContent = message;
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
right: 20px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 10px 15px;
|
||||
border-radius: 5px;
|
||||
z-index: 10001;
|
||||
font-size: 14px;
|
||||
backdrop-filter: blur(5px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
animation: slideInRight 0.3s ease-out, slideOutRight 0.3s ease-in 2.7s;
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Remove after 3 seconds
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
|
@ -377,6 +377,439 @@ body.cinema-mode {
|
|||
}
|
||||
}
|
||||
|
||||
/* ===== PLAYLIST MODAL STYLES ===== */
|
||||
.playlist-modal-content {
|
||||
background: linear-gradient(135deg, #2a2a3a, #1e1e2e);
|
||||
border-radius: 12px;
|
||||
max-width: 500px;
|
||||
width: 90vw;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
border: 1px solid #404050;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.playlist-modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #404050;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.playlist-modal-header h3 {
|
||||
margin: 0;
|
||||
color: #ff6b9d;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.playlist-modal-close {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
color: #ffffff;
|
||||
border-radius: 50%;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.playlist-modal-close:hover {
|
||||
background: rgba(220, 53, 69, 0.3);
|
||||
border-color: rgba(220, 53, 69, 0.5);
|
||||
}
|
||||
|
||||
.playlist-modal-body {
|
||||
padding: 20px;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Save playlist form styles */
|
||||
.save-playlist-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.save-playlist-form label {
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.save-playlist-form input {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid #404050;
|
||||
border-radius: 6px;
|
||||
color: #ffffff;
|
||||
padding: 12px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.save-playlist-form input:focus {
|
||||
outline: none;
|
||||
border-color: #ff6b9d;
|
||||
box-shadow: 0 0 0 2px rgba(255, 107, 157, 0.2);
|
||||
}
|
||||
|
||||
.save-playlist-form input::placeholder {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.playlist-info {
|
||||
color: #999;
|
||||
font-size: 0.9rem;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.save-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-save-playlist,
|
||||
.btn-save-and-export {
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.btn-save-playlist {
|
||||
background: rgba(139, 99, 214, 0.15);
|
||||
border-color: rgba(139, 99, 214, 0.3);
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.btn-save-playlist:hover {
|
||||
background: rgba(139, 99, 214, 0.25);
|
||||
border-color: rgba(139, 99, 214, 0.5);
|
||||
}
|
||||
|
||||
.btn-save-and-export {
|
||||
background: rgba(40, 167, 69, 0.15);
|
||||
border-color: rgba(40, 167, 69, 0.3);
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.btn-save-and-export:hover {
|
||||
background: rgba(40, 167, 69, 0.25);
|
||||
border-color: rgba(40, 167, 69, 0.5);
|
||||
}
|
||||
|
||||
.saved-playlists {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.saved-playlist-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.saved-playlist-item:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.saved-playlist-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.saved-playlist-name {
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.saved-playlist-meta {
|
||||
color: #999;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.saved-playlist-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.btn-load-playlist,
|
||||
.btn-export-playlist,
|
||||
.btn-delete-playlist {
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
transition: all 0.2s ease;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.btn-load-playlist {
|
||||
background: rgba(139, 99, 214, 0.15);
|
||||
border-color: rgba(139, 99, 214, 0.3);
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.btn-load-playlist:hover {
|
||||
background: rgba(139, 99, 214, 0.25);
|
||||
border-color: rgba(139, 99, 214, 0.5);
|
||||
}
|
||||
|
||||
.btn-export-playlist {
|
||||
background: rgba(40, 167, 69, 0.15);
|
||||
border-color: rgba(40, 167, 69, 0.3);
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.btn-export-playlist:hover {
|
||||
background: rgba(40, 167, 69, 0.25);
|
||||
border-color: rgba(40, 167, 69, 0.5);
|
||||
}
|
||||
|
||||
.btn-delete-playlist {
|
||||
background: rgba(220, 53, 69, 0.15);
|
||||
border-color: rgba(220, 53, 69, 0.3);
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.btn-delete-playlist:hover {
|
||||
background: rgba(220, 53, 69, 0.25);
|
||||
border-color: rgba(220, 53, 69, 0.5);
|
||||
}
|
||||
|
||||
.playlist-modal-separator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.playlist-modal-separator::before,
|
||||
.playlist-modal-separator::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: #404050;
|
||||
}
|
||||
|
||||
.playlist-modal-separator span {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.file-upload-area {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-upload-playlist {
|
||||
background: rgba(40, 167, 69, 0.15);
|
||||
border: 1px solid rgba(40, 167, 69, 0.3);
|
||||
color: #e0e0e0;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-upload-playlist:hover {
|
||||
background: rgba(40, 167, 69, 0.25);
|
||||
border-color: rgba(40, 167, 69, 0.5);
|
||||
}
|
||||
|
||||
/* Responsive modal styles */
|
||||
@media (max-width: 768px) {
|
||||
.playlist-modal-content {
|
||||
width: 95vw;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
.saved-playlist-item {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.saved-playlist-actions {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.save-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== PLAYLIST CREATION MODAL ===== */
|
||||
.create-playlist-modal {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.create-playlist-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.create-playlist-form label {
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.create-playlist-form input {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid #404050;
|
||||
border-radius: 6px;
|
||||
color: #ffffff;
|
||||
padding: 12px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.create-playlist-form input:focus {
|
||||
outline: none;
|
||||
border-color: #ff6b9d;
|
||||
box-shadow: 0 0 0 2px rgba(255, 107, 157, 0.2);
|
||||
}
|
||||
|
||||
.preselected-videos {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.preselected-videos h4 {
|
||||
margin: 0 0 12px 0;
|
||||
color: #28a745;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.video-list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.playlist-creation-video-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.playlist-creation-video-item .video-name {
|
||||
flex: 1;
|
||||
color: #ffffff;
|
||||
font-size: 0.9rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.btn-remove-from-creation {
|
||||
background: rgba(220, 53, 69, 0.15);
|
||||
border: 1px solid rgba(220, 53, 69, 0.3);
|
||||
color: #e0e0e0;
|
||||
border-radius: 50%;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-remove-from-creation:hover {
|
||||
background: rgba(220, 53, 69, 0.25);
|
||||
border-color: rgba(220, 53, 69, 0.5);
|
||||
}
|
||||
|
||||
.empty-playlist-note {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.playlist-creation-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-create-playlist,
|
||||
.btn-create-and-switch {
|
||||
padding: 12px 24px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.btn-create-playlist {
|
||||
background: rgba(139, 99, 214, 0.15);
|
||||
border-color: rgba(139, 99, 214, 0.3);
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.btn-create-playlist:hover {
|
||||
background: rgba(139, 99, 214, 0.25);
|
||||
border-color: rgba(139, 99, 214, 0.5);
|
||||
}
|
||||
|
||||
.btn-create-and-switch {
|
||||
background: rgba(40, 167, 69, 0.15);
|
||||
border-color: rgba(40, 167, 69, 0.3);
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.btn-create-and-switch:hover {
|
||||
background: rgba(40, 167, 69, 0.25);
|
||||
border-color: rgba(40, 167, 69, 0.5);
|
||||
}
|
||||
|
||||
/* Search panel */
|
||||
.search-content {
|
||||
flex: 1;
|
||||
|
|
@ -776,6 +1209,23 @@ body.cinema-mode {
|
|||
gap: 8px;
|
||||
}
|
||||
|
||||
.playlist-management {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.playlist-management .btn-mini.has-selection {
|
||||
background: rgba(40, 167, 69, 0.2);
|
||||
border-color: rgba(40, 167, 69, 0.4);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.playlist-management .btn-mini.active {
|
||||
background: rgba(255, 107, 157, 0.2);
|
||||
border-color: rgba(255, 107, 157, 0.4);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.view-toggle {
|
||||
display: flex;
|
||||
border: 1px solid #444;
|
||||
|
|
@ -873,6 +1323,7 @@ body.cinema-mode {
|
|||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.video-card:hover {
|
||||
|
|
@ -886,6 +1337,42 @@ body.cinema-mode {
|
|||
background: rgba(255, 107, 157, 0.1);
|
||||
}
|
||||
|
||||
.video-card.multi-selected {
|
||||
border-color: #28a745;
|
||||
background: rgba(40, 167, 69, 0.15);
|
||||
}
|
||||
|
||||
.video-selection-checkbox {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: transparent;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.video-selection-checkbox.checked {
|
||||
background: #28a745;
|
||||
border-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.video-selection-checkbox:hover {
|
||||
border-color: rgba(255, 255, 255, 0.6);
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.video-thumbnail {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
|
|
@ -969,6 +1456,7 @@ body.cinema-mode {
|
|||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid transparent;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.video-list-item:hover {
|
||||
|
|
@ -980,6 +1468,18 @@ body.cinema-mode {
|
|||
background: rgba(255, 107, 157, 0.1);
|
||||
}
|
||||
|
||||
.video-list-item.multi-selected {
|
||||
border-color: #28a745;
|
||||
background: rgba(40, 167, 69, 0.15);
|
||||
}
|
||||
|
||||
.video-list-item .video-selection-checkbox {
|
||||
position: relative;
|
||||
top: auto;
|
||||
left: auto;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.list-thumbnail {
|
||||
width: 80px;
|
||||
height: 50px;
|
||||
|
|
|
|||
Loading…
Reference in New Issue