refactor: simplify and standardize modal system
This commit is contained in:
parent
7516df9278
commit
13d5f07b6c
4 changed files with 303 additions and 264 deletions
131
index.html
131
index.html
|
|
@ -68,38 +68,115 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="playlist-modal" class="modal" style="display: none;">
|
<div id="playlist-modal" class="modal">
|
||||||
<div class="modal-overlay" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000;">
|
<div class="modal-overlay"></div>
|
||||||
<div class="modal-content" style="background: var(--card); padding: 2rem; border-radius: var(--radius); max-width: 400px; width: 90%;">
|
<div class="modal-content">
|
||||||
<h3 id="playlist-modal-title">Create Playlist</h3>
|
<h3 id="playlist-modal-title">Create Playlist</h3>
|
||||||
<input type="text" id="playlist-name-input" placeholder="Playlist name" style="width: 100%; margin: 1rem 0; padding: 0.5rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--background); color: var(--foreground);">
|
<input type="text" id="playlist-name-input" class="template-input" placeholder="Playlist name" style="margin: 1rem 0;">
|
||||||
<div id="csv-import-section" style="display: none; margin: 1rem 0; padding: 1rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--background-secondary);">
|
<div id="csv-import-section" style="display: none; margin: 1rem 0; padding: 1rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--background-secondary);">
|
||||||
<p style="margin-bottom: 0.5rem; font-size: 0.9rem;">Import from CSV</p>
|
<p style="margin-bottom: 0.5rem; font-size: 0.9rem;">Import from CSV</p>
|
||||||
<p style="font-size: 0.8rem; margin: 0;">Only Spotify Is Supported for now. please use <a href="https://exportify.app/" style="text-decoration: underline;">Exportify</a> to export your playlist into a csv. Also Make sure its headers are in english.</p>
|
<p style="font-size: 0.8rem; margin: 0;">Only Spotify Is Supported for now. please use <a href="https://exportify.app/" style="text-decoration: underline;">Exportify</a> to export your playlist into a csv. Also Make sure its headers are in english.</p>
|
||||||
<br>
|
<br>
|
||||||
<input type="file" id="csv-file-input" class="btn-secondary" accept=".csv" style="width: 100%; margin-bottom: 0.5rem;">
|
<input type="file" id="csv-file-input" class="btn-secondary" accept=".csv" style="width: 100%; margin-bottom: 0.5rem;">
|
||||||
<p style="font-size: 0.8rem; margin: 0;">This Feature Isnt Perfect And is Prone To Errors! Please check Your Playlist After To Remove Weird Songs That Were Added By The System.</p>
|
<p style="font-size: 0.8rem; margin: 0;">This Feature Isnt Perfect And is Prone To Errors! Please check Your Playlist After To Remove Weird Songs That Were Added By The System.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="setting-item" style="margin-bottom: 1rem; padding: 0; border: none; background: transparent;">
|
<div class="setting-item" style="margin-bottom: 1rem; padding: 0; border: none; background: transparent;">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<span class="label">Public Playlist</span>
|
<span class="label">Public Playlist</span>
|
||||||
<span class="description">Visible to anyone with the link</span>
|
<span class="description">Visible to anyone with the link</span>
|
||||||
</div>
|
|
||||||
<label class="toggle-switch">
|
|
||||||
<input type="checkbox" id="playlist-public-toggle">
|
|
||||||
<span class="slider"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox" id="playlist-public-toggle">
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="modal-actions" style="display: flex; gap: 0.5rem; justify-content: space-between; align-items: center;">
|
<div class="modal-actions">
|
||||||
<button id="playlist-share-btn" class="btn-secondary" style="display: none;">Share</button>
|
<button id="playlist-share-btn" class="btn-secondary" style="display: none;">Share</button>
|
||||||
<div style="display: flex; gap: 0.5rem; margin-left: auto;">
|
<button id="playlist-modal-cancel" class="btn-secondary">Cancel</button>
|
||||||
<button id="playlist-modal-cancel" class="btn-secondary">Cancel</button>
|
<button id="playlist-modal-save" class="btn-primary">Save</button>
|
||||||
<button id="playlist-modal-save" class="btn-primary">Save</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="playlist-select-modal" class="modal">
|
||||||
|
<div class="modal-overlay"></div>
|
||||||
|
<div class="modal-content">
|
||||||
|
<h3>Add to Playlist</h3>
|
||||||
|
<div id="playlist-select-list" class="modal-list">
|
||||||
|
<!-- Options will be injected here -->
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button id="playlist-select-cancel" class="btn-secondary">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="shortcuts-modal" class="modal">
|
||||||
|
<div class="modal-overlay"></div>
|
||||||
|
<div class="modal-content medium">
|
||||||
|
<div class="shortcuts-header">
|
||||||
|
<h3>Keyboard Shortcuts</h3>
|
||||||
|
<button class="close-shortcuts">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="shortcuts-content">
|
||||||
|
<div class="shortcut-item"><kbd>Space</kbd><span>Play / Pause</span></div>
|
||||||
|
<div class="shortcut-item"><kbd>→</kbd><span>Seek forward 10s</span></div>
|
||||||
|
<div class="shortcut-item"><kbd>←</kbd><span>Seek backward 10s</span></div>
|
||||||
|
<div class="shortcut-item"><kbd>Shift</kbd> + <kbd>→</kbd><span>Next track</span></div>
|
||||||
|
<div class="shortcut-item"><kbd>Shift</kbd> + <kbd>←</kbd><span>Previous track</span></div>
|
||||||
|
<div class="shortcut-item"><kbd>↑</kbd><span>Volume up</span></div>
|
||||||
|
<div class="shortcut-item"><kbd>↓</kbd><span>Volume down</span></div>
|
||||||
|
<div class="shortcut-item"><kbd>M</kbd><span>Mute / Unmute</span></div>
|
||||||
|
<div class="shortcut-item"><kbd>S</kbd><span>Toggle shuffle</span></div>
|
||||||
|
<div class="shortcut-item"><kbd>R</kbd><span>Toggle repeat</span></div>
|
||||||
|
<div class="shortcut-item"><kbd>Q</kbd><span>Open queue</span></div>
|
||||||
|
<div class="shortcut-item"><kbd>L</kbd><span>Toggle lyrics</span></div>
|
||||||
|
<div class="shortcut-item"><kbd>/</kbd><span>Focus search</span></div>
|
||||||
|
<div class="shortcut-item"><kbd>Esc</kbd><span>Close modals</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="missing-tracks-modal" class="modal">
|
||||||
|
<div class="modal-overlay"></div>
|
||||||
|
<div class="modal-content wide">
|
||||||
|
<div class="missing-tracks-header">
|
||||||
|
<h3>Note</h3>
|
||||||
|
<button class="close-missing-tracks">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="missing-tracks-content">
|
||||||
|
<p>Unfortunately some songs weren't able to be added. This could be an issue with our import system, try searching for the song and adding it. But it could also be due to Monochrome not having it sadly :(</p>
|
||||||
|
<div class="missing-tracks-list">
|
||||||
|
<h4>Missing Tracks:</h4>
|
||||||
|
<ul id="missing-tracks-list-ul"></ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="missing-tracks-actions">
|
||||||
|
<button class="btn-secondary" id="close-missing-tracks-btn">OK</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="sleep-timer-modal" class="modal">
|
||||||
|
<div class="modal-overlay"></div>
|
||||||
|
<div class="modal-content" style="max-width: 300px;">
|
||||||
|
<h3 style="text-align: center; margin-bottom: 1.5rem;">Sleep Timer</h3>
|
||||||
|
<div class="timer-options">
|
||||||
|
<button class="timer-option btn-secondary" data-minutes="5">5 minutes</button>
|
||||||
|
<button class="timer-option btn-secondary" data-minutes="15">15 minutes</button>
|
||||||
|
<button class="timer-option btn-secondary" data-minutes="30">30 minutes</button>
|
||||||
|
<button class="timer-option btn-secondary" data-minutes="60">1 hour</button>
|
||||||
|
<button class="timer-option btn-secondary" data-minutes="120">2 hours</button>
|
||||||
|
<div style="display: flex; gap: 0.5rem; margin-top: 0.5rem;">
|
||||||
|
<input type="number" id="custom-minutes" class="template-input" placeholder="Custom" min="1" max="480">
|
||||||
|
<button class="timer-option btn-primary" id="custom-timer-btn" style="padding: 0.5rem 1rem;">Set</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions" style="justify-content: center;">
|
||||||
|
<button id="cancel-sleep-timer" class="btn-secondary">Cancel</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
157
js/app.js
157
js/app.js
|
|
@ -420,7 +420,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
if (publicToggle) publicToggle.checked = false;
|
if (publicToggle) publicToggle.checked = false;
|
||||||
if (shareBtn) shareBtn.style.display = 'none';
|
if (shareBtn) shareBtn.style.display = 'none';
|
||||||
|
|
||||||
modal.style.display = 'flex';
|
modal.classList.add('active');
|
||||||
document.getElementById('playlist-name-input').focus();
|
document.getElementById('playlist-name-input').focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -464,7 +464,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
if (window.location.hash === `#userplaylist/${editingId}`) {
|
if (window.location.hash === `#userplaylist/${editingId}`) {
|
||||||
ui.renderPlaylistPage(editingId, 'user');
|
ui.renderPlaylistPage(editingId, 'user');
|
||||||
}
|
}
|
||||||
modal.style.display = 'none';
|
modal.classList.remove('active');
|
||||||
delete modal.dataset.editingId;
|
delete modal.dataset.editingId;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -536,14 +536,14 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
await db.performTransaction('user_playlists', 'readwrite', (store) => store.put(playlist));
|
await db.performTransaction('user_playlists', 'readwrite', (store) => store.put(playlist));
|
||||||
syncManager.syncUserPlaylist(playlist, 'create');
|
syncManager.syncUserPlaylist(playlist, 'create');
|
||||||
ui.renderLibraryPage();
|
ui.renderLibraryPage();
|
||||||
modal.style.display = 'none';
|
modal.classList.remove('active');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.target.closest('#playlist-modal-cancel')) {
|
if (e.target.closest('#playlist-modal-cancel')) {
|
||||||
document.getElementById('playlist-modal').style.display = 'none';
|
document.getElementById('playlist-modal').classList.remove('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.target.closest('.edit-playlist-btn')) {
|
if (e.target.closest('.edit-playlist-btn')) {
|
||||||
|
|
@ -573,7 +573,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
|
||||||
modal.dataset.editingId = playlistId;
|
modal.dataset.editingId = playlistId;
|
||||||
document.getElementById('csv-import-section').style.display = 'none';
|
document.getElementById('csv-import-section').style.display = 'none';
|
||||||
modal.style.display = 'flex';
|
modal.classList.add('active');
|
||||||
document.getElementById('playlist-name-input').focus();
|
document.getElementById('playlist-name-input').focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -612,7 +612,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
|
||||||
modal.dataset.editingId = playlistId;
|
modal.dataset.editingId = playlistId;
|
||||||
document.getElementById('csv-import-section').style.display = 'none';
|
document.getElementById('csv-import-section').style.display = 'none';
|
||||||
modal.style.display = 'flex';
|
modal.classList.add('active');
|
||||||
document.getElementById('playlist-name-input').focus();
|
document.getElementById('playlist-name-input').focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -969,37 +969,24 @@ function showInstallPrompt(deferredPrompt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMissingTracksNotification(missingTracks) {
|
function showMissingTracksNotification(missingTracks) {
|
||||||
const modal = document.createElement('div');
|
const modal = document.getElementById('missing-tracks-modal');
|
||||||
modal.className = 'missing-tracks-modal-overlay';
|
const listUl = document.getElementById('missing-tracks-list-ul');
|
||||||
modal.innerHTML = `
|
|
||||||
<div class="missing-tracks-modal">
|
listUl.innerHTML = missingTracks.map(track => `<li>${track}</li>`).join('');
|
||||||
<div class="missing-tracks-header">
|
|
||||||
<h3>Note</h3>
|
const closeModal = () => modal.classList.remove('active');
|
||||||
<button class="close-missing-tracks">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="missing-tracks-content">
|
|
||||||
<p>Unfortunately some songs weren't able to be added. This could be an issue with our import system, try searching for the song and adding it. But it could also be due to Monochrome not having it sadly :(</p>
|
|
||||||
<div class="missing-tracks-list">
|
|
||||||
<h4>Missing Tracks:</h4>
|
|
||||||
<ul>
|
|
||||||
${missingTracks.map(track => `<li>${track}</li>`).join('')}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="missing-tracks-actions">
|
|
||||||
<button class="btn-secondary" id="close-missing-tracks-btn">OK</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
document.body.appendChild(modal);
|
|
||||||
|
|
||||||
const closeModal = () => modal.remove();
|
// Remove old listeners if any (though usually these functions are called once per instance,
|
||||||
|
// but since we reuse the same modal element we should be careful or use a one-time listener)
|
||||||
modal.addEventListener('click', (e) => {
|
const handleClose = (e) => {
|
||||||
if (e.target === modal || e.target.classList.contains('close-missing-tracks') || e.target.id === 'close-missing-tracks-btn') {
|
if (e.target === modal || e.target.closest('.close-missing-tracks') || e.target.id === 'close-missing-tracks-btn' || e.target.classList.contains('modal-overlay')) {
|
||||||
closeModal();
|
closeModal();
|
||||||
|
modal.removeEventListener('click', handleClose);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
modal.addEventListener('click', handleClose);
|
||||||
|
modal.classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function parseCSV(csvText, api, onProgress) {
|
async function parseCSV(csvText, api, onProgress) {
|
||||||
|
|
@ -1112,80 +1099,36 @@ async function parseCSV(csvText, api, onProgress) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function showKeyboardShortcuts() {
|
function showKeyboardShortcuts() {
|
||||||
const modal = document.createElement('div');
|
|
||||||
modal.className = 'shortcuts-modal-overlay';
|
|
||||||
modal.innerHTML = `
|
|
||||||
<div class="shortcuts-modal">
|
|
||||||
<div class="shortcuts-header">
|
|
||||||
<h3>Keyboard Shortcuts</h3>
|
|
||||||
<button class="close-shortcuts">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="shortcuts-content">
|
|
||||||
<div class="shortcut-item">
|
|
||||||
<kbd>Space</kbd>
|
|
||||||
<span>Play / Pause</span>
|
|
||||||
</div>
|
|
||||||
<div class="shortcut-item">
|
|
||||||
<kbd>→</kbd>
|
|
||||||
<span>Seek forward 10s</span>
|
|
||||||
</div>
|
|
||||||
<div class="shortcut-item">
|
|
||||||
<kbd>←</kbd>
|
|
||||||
<span>Seek backward 10s</span>
|
|
||||||
</div>
|
|
||||||
<div class="shortcut-item">
|
|
||||||
<kbd>Shift</kbd> + <kbd>→</kbd>
|
|
||||||
<span>Next track</span>
|
|
||||||
</div>
|
|
||||||
<div class="shortcut-item">
|
|
||||||
<kbd>Shift</kbd> + <kbd>←</kbd>
|
|
||||||
<span>Previous track</span>
|
|
||||||
</div>
|
|
||||||
<div class="shortcut-item">
|
|
||||||
<kbd>↑</kbd>
|
|
||||||
<span>Volume up</span>
|
|
||||||
</div>
|
|
||||||
<div class="shortcut-item">
|
|
||||||
<kbd>↓</kbd>
|
|
||||||
<span>Volume down</span>
|
|
||||||
</div>
|
|
||||||
<div class="shortcut-item">
|
|
||||||
<kbd>M</kbd>
|
|
||||||
<span>Mute / Unmute</span>
|
|
||||||
</div>
|
|
||||||
<div class="shortcut-item">
|
|
||||||
<kbd>S</kbd>
|
|
||||||
<span>Toggle shuffle</span>
|
|
||||||
</div>
|
|
||||||
<div class="shortcut-item">
|
|
||||||
<kbd>R</kbd>
|
|
||||||
<span>Toggle repeat</span>
|
|
||||||
</div>
|
|
||||||
<div class="shortcut-item">
|
|
||||||
<kbd>Q</kbd>
|
|
||||||
<span>Open queue</span>
|
|
||||||
</div>
|
|
||||||
<div class="shortcut-item">
|
|
||||||
<kbd>L</kbd>
|
|
||||||
<span>Toggle lyrics</span>
|
|
||||||
</div>
|
|
||||||
<div class="shortcut-item">
|
|
||||||
<kbd>/</kbd>
|
|
||||||
<span>Focus search</span>
|
|
||||||
</div>
|
|
||||||
<div class="shortcut-item">
|
|
||||||
<kbd>Esc</kbd>
|
|
||||||
<span>Close modals</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
document.body.appendChild(modal);
|
|
||||||
|
|
||||||
modal.addEventListener('click', (e) => {
|
const modal = document.getElementById('shortcuts-modal');
|
||||||
if (e.target === modal || e.target.classList.contains('close-shortcuts')) {
|
|
||||||
modal.remove();
|
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
|
||||||
|
modal.classList.remove('active');
|
||||||
|
|
||||||
|
modal.removeEventListener('click', handleClose);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleClose = (e) => {
|
||||||
|
|
||||||
|
if (e.target === modal || e.target.classList.contains('close-shortcuts') || e.target.classList.contains('modal-overlay')) {
|
||||||
|
|
||||||
|
closeModal();
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
modal.addEventListener('click', handleClose);
|
||||||
|
|
||||||
|
modal.classList.add('active');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
131
js/events.js
131
js/events.js
|
|
@ -615,39 +615,43 @@ export async function handleTrackAction(action, item, player, api, lyricsManager
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const modal = document.createElement('div');
|
const modal = document.getElementById('playlist-select-modal');
|
||||||
modal.className = 'playlist-select-modal';
|
const list = document.getElementById('playlist-select-list');
|
||||||
modal.innerHTML = `
|
const cancelBtn = document.getElementById('playlist-select-cancel');
|
||||||
<div class="modal-overlay" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; display: flex; align-items: center; justify-content: center;">
|
const overlay = modal.querySelector('.modal-overlay');
|
||||||
<div class="modal-content" style="background: var(--card); padding: 2rem; border-radius: var(--radius); max-width: 400px; width: 90%;">
|
|
||||||
<h3>Add to Playlist</h3>
|
|
||||||
<div id="playlist-list" style="margin: 1rem 0; max-height: 200px; overflow-y: auto;">
|
|
||||||
${playlists.map(p => `<div class="playlist-option" data-id="${p.id}" style="padding: 0.5rem; cursor: pointer; border-bottom: 1px solid var(--border);">${p.name}</div>`).join('')}
|
|
||||||
</div>
|
|
||||||
<div class="modal-actions" style="display: flex; gap: 0.5rem; justify-content: flex-end;">
|
|
||||||
<button id="cancel-add-playlist" class="btn-secondary">Cancel</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
document.body.appendChild(modal);
|
|
||||||
|
|
||||||
modal.addEventListener('click', async (e) => {
|
list.innerHTML = playlists.map(p => `
|
||||||
if (e.target.id === 'cancel-add-playlist') {
|
<div class="modal-option" data-id="${p.id}">${p.name}</div>
|
||||||
modal.remove();
|
`).join('');
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const option = e.target.closest('.playlist-option');
|
const closeModal = () => {
|
||||||
|
modal.classList.remove('active');
|
||||||
|
cleanup();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOptionClick = async (e) => {
|
||||||
|
const option = e.target.closest('.modal-option');
|
||||||
if (option) {
|
if (option) {
|
||||||
const playlistId = option.dataset.id;
|
const playlistId = option.dataset.id;
|
||||||
await db.addTrackToPlaylist(playlistId, item);
|
await db.addTrackToPlaylist(playlistId, item);
|
||||||
const updatedPlaylist = await db.getPlaylist(playlistId);
|
const updatedPlaylist = await db.getPlaylist(playlistId);
|
||||||
syncManager.syncUserPlaylist(updatedPlaylist, 'update');
|
syncManager.syncUserPlaylist(updatedPlaylist, 'update');
|
||||||
showNotification(`Added to playlist: ${option.textContent}`);
|
showNotification(`Added to playlist: ${option.textContent}`);
|
||||||
modal.remove();
|
closeModal();
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
cancelBtn.removeEventListener('click', closeModal);
|
||||||
|
overlay.removeEventListener('click', closeModal);
|
||||||
|
list.removeEventListener('click', handleOptionClick);
|
||||||
|
};
|
||||||
|
|
||||||
|
cancelBtn.addEventListener('click', closeModal);
|
||||||
|
overlay.addEventListener('click', closeModal);
|
||||||
|
list.addEventListener('click', handleOptionClick);
|
||||||
|
|
||||||
|
modal.classList.add('active');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -900,61 +904,52 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSleepTimerModal(player) {
|
function showSleepTimerModal(player) {
|
||||||
if (document.querySelector('.sleep-timer-modal')) return;
|
const modal = document.getElementById('sleep-timer-modal');
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
const modal = document.createElement('div');
|
const closeModal = () => {
|
||||||
modal.className = 'sleep-timer-modal';
|
modal.classList.remove('active');
|
||||||
modal.innerHTML = `
|
cleanup();
|
||||||
<div class="modal-overlay" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; display: flex; align-items: center; justify-content: center;">
|
};
|
||||||
<div class="modal-content" style="background: var(--card); padding: 2rem; border-radius: var(--radius); max-width: 300px; width: 90%;">
|
|
||||||
<h3 style="text-align: center; margin-bottom: 1.5rem;">Sleep Timer</h3>
|
|
||||||
<div class="timer-options" style="display: flex; flex-direction: column; gap: 0.5rem;">
|
|
||||||
<button class="timer-option btn-secondary" data-minutes="5">5 minutes</button>
|
|
||||||
<button class="timer-option btn-secondary" data-minutes="15">15 minutes</button>
|
|
||||||
<button class="timer-option btn-secondary" data-minutes="30">30 minutes</button>
|
|
||||||
<button class="timer-option btn-secondary" data-minutes="60">1 hour</button>
|
|
||||||
<button class="timer-option btn-secondary" data-minutes="120">2 hours</button>
|
|
||||||
<div style="display: flex; gap: 0.5rem; margin-top: 0.5rem;">
|
|
||||||
<input type="number" id="custom-minutes" placeholder="Custom" min="1" max="480" style="flex: 1; padding: 0.5rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--background); color: var(--foreground);">
|
|
||||||
<button class="timer-option btn-primary" id="custom-timer-btn" style="padding: 0.5rem 1rem;">Set</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-actions" style="display: flex; gap: 0.5rem; justify-content: center; margin-top: 1.5rem;">
|
|
||||||
<button id="cancel-sleep-timer" class="btn-secondary">Cancel</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
document.body.appendChild(modal);
|
|
||||||
|
|
||||||
modal.addEventListener('click', (e) => {
|
|
||||||
if (e.target.id === 'cancel-sleep-timer' || e.target.classList.contains('modal-overlay')) {
|
|
||||||
modal.remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const handleOptionClick = (e) => {
|
||||||
const timerOption = e.target.closest('.timer-option');
|
const timerOption = e.target.closest('.timer-option');
|
||||||
if (timerOption) {
|
if (timerOption) {
|
||||||
const minutes = parseInt(timerOption.dataset.minutes);
|
let minutes;
|
||||||
|
if (timerOption.id === 'custom-timer-btn') {
|
||||||
|
const customInput = document.getElementById('custom-minutes');
|
||||||
|
minutes = parseInt(customInput.value);
|
||||||
|
if (!minutes || minutes < 1) {
|
||||||
|
showNotification('Please enter a valid number of minutes');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
minutes = parseInt(timerOption.dataset.minutes);
|
||||||
|
}
|
||||||
|
|
||||||
if (minutes) {
|
if (minutes) {
|
||||||
player.setSleepTimer(minutes);
|
player.setSleepTimer(minutes);
|
||||||
showNotification(`Sleep timer set for ${minutes} minute${minutes === 1 ? '' : 's'}`);
|
showNotification(`Sleep timer set for ${minutes} minute${minutes === 1 ? '' : 's'}`);
|
||||||
modal.remove();
|
closeModal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (e.target.id === 'custom-timer-btn') {
|
const handleCancel = (e) => {
|
||||||
const customInput = document.getElementById('custom-minutes');
|
if (e.target.id === 'cancel-sleep-timer' || e.target.classList.contains('modal-overlay')) {
|
||||||
const minutes = parseInt(customInput.value);
|
closeModal();
|
||||||
if (minutes && minutes > 0 && minutes <= 480) {
|
|
||||||
player.setSleepTimer(minutes);
|
|
||||||
showNotification(`Sleep timer set for ${minutes} minute${minutes === 1 ? '' : 's'}`);
|
|
||||||
modal.remove();
|
|
||||||
} else {
|
|
||||||
showNotification('Please enter a valid number of minutes (1-480)');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
modal.removeEventListener('click', handleOptionClick);
|
||||||
|
modal.removeEventListener('click', handleCancel);
|
||||||
|
};
|
||||||
|
|
||||||
|
modal.addEventListener('click', handleOptionClick);
|
||||||
|
modal.addEventListener('click', handleCancel);
|
||||||
|
|
||||||
|
modal.classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
function positionMenu(menu, x, y, anchorRect = null) {
|
function positionMenu(menu, x, y, anchorRect = null) {
|
||||||
|
|
|
||||||
148
styles.css
148
styles.css
|
|
@ -2408,41 +2408,6 @@ input:checked + .slider::before {
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shortcuts-modal-overlay {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.7);
|
|
||||||
backdrop-filter: blur(4px);
|
|
||||||
z-index: 10000;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
animation: fadeIn 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcuts-modal {
|
|
||||||
background: var(--card);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
width: 90%;
|
|
||||||
max-width: 500px;
|
|
||||||
max-height: 80vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
box-shadow: var(--shadow-xl);
|
|
||||||
animation: scaleIn 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcuts-header {
|
|
||||||
padding: 1rem;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcuts-header h3 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-shortcuts {
|
.close-shortcuts {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -2943,6 +2908,92 @@ img:not([src]), img[src=''] {
|
||||||
background: var(--primary);
|
background: var(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Modals */
|
||||||
|
.modal {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 10000;
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: var(--card);
|
||||||
|
padding: 2rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
max-width: 400px;
|
||||||
|
width: 90%;
|
||||||
|
box-shadow: var(--shadow-xl);
|
||||||
|
animation: scaleIn 0.2s ease;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content.wide {
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content.medium {
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-list {
|
||||||
|
margin: 1rem 0;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-option {
|
||||||
|
padding: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-option:hover {
|
||||||
|
background: var(--secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-option:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-option {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scaleIn {
|
||||||
|
from { transform: scale(0.95); opacity: 0; }
|
||||||
|
to { transform: scale(1); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
#playlist-modal {
|
#playlist-modal {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
animation-name: fadeInOpacity;
|
animation-name: fadeInOpacity;
|
||||||
|
|
@ -3058,33 +3109,6 @@ img:not([src]), img[src=''] {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* OH NO SOME SONGS WERENT FOUND FUCK ME FUCK ME */
|
|
||||||
.missing-tracks-modal-overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.7);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 10000;
|
|
||||||
animation: fadeIn 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.missing-tracks-modal {
|
|
||||||
background: var(--card);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
max-width: 600px;
|
|
||||||
width: 90%;
|
|
||||||
max-height: 85vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
box-shadow: var(--shadow-xl);
|
|
||||||
animation: scaleIn 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.missing-tracks-header {
|
.missing-tracks-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue