+
Create Playlist
+
+
+
Import from CSV
+
Only Spotify Is Supported for now. please use Exportify to export your playlist into a csv. Also Make sure its headers are in english.
+
+
+
This Feature Isnt Perfect And is Prone To Errors! Please check Your Playlist After To Remove Weird Songs That Were Added By The System.
+
-
-
- Public Playlist
- Visible to anyone with the link
-
-
-
-
-
+
+
+ Public Playlist
+ Visible to anyone with the link
+
+
+
+
+
-
-
Share
-
- Cancel
- Save
-
+
+ Share
+ Cancel
+ Save
+
+
+
+
+
+
+
+
Add to Playlist
+
+
+
+
+ Cancel
+
+
+
+
+
+
+
+
+
+
Space Play / Pause
+
→ Seek forward 10s
+
← Seek backward 10s
+
Shift + → Next track
+
Shift + ← Previous track
+
↑ Volume up
+
↓ Volume down
+
M Mute / Unmute
+
S Toggle shuffle
+
R Toggle repeat
+
Q Open queue
+
L Toggle lyrics
+
/ Focus search
+
Esc Close modals
+
+
+
+
+
+
+
+
+
+
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 :(
+
+
+ OK
+
+
+
+
+
+
+
+
Sleep Timer
+
+
5 minutes
+
15 minutes
+
30 minutes
+
1 hour
+
2 hours
+
+
+ Set
+
+
+
+ Cancel
+
diff --git a/js/app.js b/js/app.js
index 1dbba2f..bae0c7a 100644
--- a/js/app.js
+++ b/js/app.js
@@ -420,7 +420,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (publicToggle) publicToggle.checked = false;
if (shareBtn) shareBtn.style.display = 'none';
- modal.style.display = 'flex';
+ modal.classList.add('active');
document.getElementById('playlist-name-input').focus();
}
@@ -464,7 +464,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (window.location.hash === `#userplaylist/${editingId}`) {
ui.renderPlaylistPage(editingId, 'user');
}
- modal.style.display = 'none';
+ modal.classList.remove('active');
delete modal.dataset.editingId;
}
});
@@ -536,14 +536,14 @@ document.addEventListener('DOMContentLoaded', async () => {
await db.performTransaction('user_playlists', 'readwrite', (store) => store.put(playlist));
syncManager.syncUserPlaylist(playlist, 'create');
ui.renderLibraryPage();
- modal.style.display = 'none';
+ modal.classList.remove('active');
});
}
}
}
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')) {
@@ -573,7 +573,7 @@ document.addEventListener('DOMContentLoaded', async () => {
modal.dataset.editingId = playlistId;
document.getElementById('csv-import-section').style.display = 'none';
- modal.style.display = 'flex';
+ modal.classList.add('active');
document.getElementById('playlist-name-input').focus();
}
});
@@ -612,7 +612,7 @@ document.addEventListener('DOMContentLoaded', async () => {
modal.dataset.editingId = playlistId;
document.getElementById('csv-import-section').style.display = 'none';
- modal.style.display = 'flex';
+ modal.classList.add('active');
document.getElementById('playlist-name-input').focus();
}
});
@@ -969,37 +969,24 @@ function showInstallPrompt(deferredPrompt) {
}
function showMissingTracksNotification(missingTracks) {
- const modal = document.createElement('div');
- modal.className = 'missing-tracks-modal-overlay';
- modal.innerHTML = `
-
-
-
-
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 :(
-
-
Missing Tracks:
-
- ${missingTracks.map(track => `${track} `).join('')}
-
-
-
-
- OK
-
-
- `;
- document.body.appendChild(modal);
+ const modal = document.getElementById('missing-tracks-modal');
+ const listUl = document.getElementById('missing-tracks-list-ul');
+
+ listUl.innerHTML = missingTracks.map(track => `
${track} `).join('');
+
+ const closeModal = () => modal.classList.remove('active');
- const closeModal = () => modal.remove();
-
- modal.addEventListener('click', (e) => {
- if (e.target === modal || e.target.classList.contains('close-missing-tracks') || e.target.id === 'close-missing-tracks-btn') {
+ // 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)
+ const handleClose = (e) => {
+ if (e.target === modal || e.target.closest('.close-missing-tracks') || e.target.id === 'close-missing-tracks-btn' || e.target.classList.contains('modal-overlay')) {
closeModal();
+ modal.removeEventListener('click', handleClose);
}
- });
+ };
+
+ modal.addEventListener('click', handleClose);
+ modal.classList.add('active');
}
async function parseCSV(csvText, api, onProgress) {
@@ -1112,80 +1099,36 @@ async function parseCSV(csvText, api, onProgress) {
}
function showKeyboardShortcuts() {
- const modal = document.createElement('div');
- modal.className = 'shortcuts-modal-overlay';
- modal.innerHTML = `
-
-
-
-
- Space
- Play / Pause
-
-
- →
- Seek forward 10s
-
-
- ←
- Seek backward 10s
-
-
- Shift + →
- Next track
-
-
- Shift + ←
- Previous track
-
-
- ↑
- Volume up
-
-
- ↓
- Volume down
-
-
- M
- Mute / Unmute
-
-
- S
- Toggle shuffle
-
-
- R
- Toggle repeat
-
-
- Q
- Open queue
-
-
- L
- Toggle lyrics
-
-
- /
- Focus search
-
-
- Esc
- Close modals
-
-
-
- `;
- document.body.appendChild(modal);
- modal.addEventListener('click', (e) => {
- if (e.target === modal || e.target.classList.contains('close-shortcuts')) {
- modal.remove();
+ const modal = document.getElementById('shortcuts-modal');
+
+
+
+ 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');
+
}
diff --git a/js/events.js b/js/events.js
index be71b44..58d3733 100644
--- a/js/events.js
+++ b/js/events.js
@@ -615,39 +615,43 @@ export async function handleTrackAction(action, item, player, api, lyricsManager
return;
}
- const modal = document.createElement('div');
- modal.className = 'playlist-select-modal';
- modal.innerHTML = `
-
-
-
Add to Playlist
-
- ${playlists.map(p => `
${p.name}
`).join('')}
-
-
- Cancel
-
-
-
- `;
- document.body.appendChild(modal);
+ const modal = document.getElementById('playlist-select-modal');
+ const list = document.getElementById('playlist-select-list');
+ const cancelBtn = document.getElementById('playlist-select-cancel');
+ const overlay = modal.querySelector('.modal-overlay');
- modal.addEventListener('click', async (e) => {
- if (e.target.id === 'cancel-add-playlist') {
- modal.remove();
- return;
- }
+ list.innerHTML = playlists.map(p => `
+
${p.name}
+ `).join('');
- 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) {
const playlistId = option.dataset.id;
await db.addTrackToPlaylist(playlistId, item);
const updatedPlaylist = await db.getPlaylist(playlistId);
syncManager.syncUserPlaylist(updatedPlaylist, 'update');
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) {
- if (document.querySelector('.sleep-timer-modal')) return;
+ const modal = document.getElementById('sleep-timer-modal');
+ if (!modal) return;
- const modal = document.createElement('div');
- modal.className = 'sleep-timer-modal';
- modal.innerHTML = `
-
-
-
Sleep Timer
-
-
5 minutes
-
15 minutes
-
30 minutes
-
1 hour
-
2 hours
-
-
- Set
-
-
-
- Cancel
-
-
-
- `;
- 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 closeModal = () => {
+ modal.classList.remove('active');
+ cleanup();
+ };
+ const handleOptionClick = (e) => {
const timerOption = e.target.closest('.timer-option');
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) {
player.setSleepTimer(minutes);
showNotification(`Sleep timer set for ${minutes} minute${minutes === 1 ? '' : 's'}`);
- modal.remove();
+ closeModal();
}
}
+ };
- if (e.target.id === 'custom-timer-btn') {
- const customInput = document.getElementById('custom-minutes');
- const minutes = parseInt(customInput.value);
- 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 handleCancel = (e) => {
+ if (e.target.id === 'cancel-sleep-timer' || e.target.classList.contains('modal-overlay')) {
+ closeModal();
}
- });
+ };
+
+ 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) {
diff --git a/styles.css b/styles.css
index cb74af1..e456eb6 100644
--- a/styles.css
+++ b/styles.css
@@ -2408,41 +2408,6 @@ input:checked + .slider::before {
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 {
background: transparent;
border: none;
@@ -2943,6 +2908,92 @@ img:not([src]), img[src=''] {
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 {
opacity: 1;
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 {
display: flex;
justify-content: space-between;