feat: playlist art uploads?

This commit is contained in:
IsraelGPT 2026-02-16 13:40:36 +00:00
parent 23c39ffa6d
commit 18fc409e56
3 changed files with 268 additions and 15 deletions

View file

@ -405,19 +405,62 @@
placeholder="Playlist name"
style="margin: 1rem 0"
/>
<input
type="url"
id="playlist-cover-input"
class="template-input"
placeholder="Cover image URL (optional)"
style="margin: 0.5rem 0"
/>
<p style="font-size: 0.8rem; margin: 0">
Upload your cover to
<a href="https://catbox.moe" style="text-decoration: underline">catbox.moe</a> then paste the link
here. (or any other service that allows directly linking to a file, but we recommend catbox due to
their long-term storage of your files)
</p>
<div
id="playlist-cover-wrapper"
style="margin: 0.5rem 0; display: flex; gap: 0.5rem; align-items: stretch"
>
<input
type="url"
id="playlist-cover-input"
class="template-input"
placeholder="Cover image URL"
style="flex: 1; margin: 0; display: none"
/>
<input type="file" id="playlist-cover-file-input" accept="image/*" style="display: none" />
<button
type="button"
id="playlist-cover-upload-btn"
class="template-btn"
style="
flex: 1;
padding: 0.5rem;
font-size: 0.85rem;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="17 8 12 3 7 8" />
<line x1="12" y1="3" x2="12" y2="15" />
</svg>
<span id="playlist-cover-btn-text">Upload</span>
</button>
<button
type="button"
id="playlist-cover-toggle-url-btn"
class="template-btn"
style="padding: 0.5rem; font-size: 0.85rem; white-space: nowrap"
title="Switch to URL input"
>
or URL
</button>
</div>
<div
id="playlist-cover-upload-status"
style="display: none; margin: -0.25rem 0 0.5rem 0; font-size: 0.75rem; opacity: 0.8"
>
<span id="playlist-cover-upload-text"></span>
</div>
<textarea
id="playlist-description-input"
class="template-input"

180
js/app.js
View file

@ -267,6 +267,58 @@ async function disablePwaForAuthGate() {
}
}
async function uploadCoverImage(file) {
const API_BASE = 'https://temp.imgur.gg/api/upload';
try {
const payload = {
files: [
{
fileName: file.name,
fileType: file.type,
fileSize: file.size,
},
],
};
const metadataResp = await fetch(API_BASE, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
if (!metadataResp.ok) {
throw new Error(`Metadata request failed: ${metadataResp.status}`);
}
const metadata = await metadataResp.json();
if (!metadata.success || !metadata.files || !metadata.files[0]) {
throw new Error('Failed to get upload URL');
}
const fileInfo = metadata.files[0];
const uploadUrl = fileInfo.uploadUrl;
const uploadResp = await fetch(uploadUrl, {
method: 'PUT',
body: file,
});
if (!uploadResp.ok) {
throw new Error(`Upload failed: ${uploadResp.status}`);
}
const publicUrl = `https://i.imgur.gg/${fileInfo.fileId}-${fileInfo.fileName}`;
return publicUrl;
} catch (error) {
console.error('Cover upload error:', error);
throw error;
}
}
document.addEventListener('DOMContentLoaded', async () => {
// Initialize analytics
initAnalytics();
@ -526,6 +578,71 @@ document.addEventListener('DOMContentLoaded', async () => {
});
});
// Cover image upload functionality
const coverUploadBtn = document.getElementById('playlist-cover-upload-btn');
const coverFileInput = document.getElementById('playlist-cover-file-input');
const coverToggleUrlBtn = document.getElementById('playlist-cover-toggle-url-btn');
const coverUrlInput = document.getElementById('playlist-cover-input');
const coverUploadStatus = document.getElementById('playlist-cover-upload-status');
const coverUploadText = document.getElementById('playlist-cover-upload-text');
let useUrlInput = false;
coverUploadBtn?.addEventListener('click', () => {
if (useUrlInput) return;
coverFileInput?.click();
});
coverFileInput?.addEventListener('change', async (e) => {
const file = e.target.files?.[0];
if (!file) return;
// Validate file type
if (!file.type.startsWith('image/')) {
alert('Please select an image file');
return;
}
// Show uploading status
coverUploadStatus.style.display = 'block';
coverUploadText.textContent = 'Uploading...';
coverUploadBtn.disabled = true;
try {
const publicUrl = await uploadCoverImage(file);
coverUrlInput.value = publicUrl;
coverUploadText.textContent = 'Done!';
coverUploadText.style.color = 'var(--success)';
setTimeout(() => {
coverUploadStatus.style.display = 'none';
}, 2000);
} catch (error) {
coverUploadText.textContent = 'Failed - try URL';
coverUploadText.style.color = 'var(--error)';
console.error('Upload failed:', error);
} finally {
coverUploadBtn.disabled = false;
}
});
coverToggleUrlBtn?.addEventListener('click', () => {
useUrlInput = !useUrlInput;
if (useUrlInput) {
coverUploadBtn.style.flex = '0 0 auto';
coverUploadBtn.style.display = 'none';
coverUrlInput.style.display = 'block';
coverToggleUrlBtn.textContent = 'Upload';
coverToggleUrlBtn.title = 'Switch to file upload';
} else {
coverUploadBtn.style.flex = '1';
coverUploadBtn.style.display = 'flex';
coverUrlInput.style.display = 'none';
coverToggleUrlBtn.textContent = 'or URL';
coverToggleUrlBtn.title = 'Switch to URL input';
}
});
document.getElementById('nav-back')?.addEventListener('click', () => {
window.history.back();
});
@ -750,6 +867,7 @@ document.addEventListener('DOMContentLoaded', async () => {
document.getElementById('playlist-modal-title').textContent = 'Create Playlist';
document.getElementById('playlist-name-input').value = '';
document.getElementById('playlist-cover-input').value = '';
document.getElementById('playlist-cover-file-input').value = '';
document.getElementById('playlist-description-input').value = '';
modal.dataset.editingId = '';
document.getElementById('import-section').style.display = 'block';
@ -775,6 +893,22 @@ document.addEventListener('DOMContentLoaded', async () => {
if (publicToggle) publicToggle.checked = false;
if (shareBtn) shareBtn.style.display = 'none';
// Reset cover upload state
const coverUploadBtn = document.getElementById('playlist-cover-upload-btn');
const coverUrlInput = document.getElementById('playlist-cover-input');
const coverUploadStatus = document.getElementById('playlist-cover-upload-status');
const coverToggleUrlBtn = document.getElementById('playlist-cover-toggle-url-btn');
if (coverUploadBtn) {
coverUploadBtn.style.flex = '1';
coverUploadBtn.style.display = 'flex';
}
if (coverUrlInput) coverUrlInput.style.display = 'none';
if (coverUploadStatus) coverUploadStatus.style.display = 'none';
if (coverToggleUrlBtn) {
coverToggleUrlBtn.textContent = 'or URL';
coverToggleUrlBtn.title = 'Switch to URL input';
}
modal.classList.add('active');
document.getElementById('playlist-name-input').focus();
}
@ -1272,6 +1406,29 @@ document.addEventListener('DOMContentLoaded', async () => {
};
}
// Set cover upload state - show URL input if there's an existing cover
const coverUploadBtn = document.getElementById('playlist-cover-upload-btn');
const coverUrlInput = document.getElementById('playlist-cover-input');
const coverToggleUrlBtn = document.getElementById('playlist-cover-toggle-url-btn');
if (playlist.cover) {
if (coverUploadBtn) coverUploadBtn.style.display = 'none';
if (coverUrlInput) coverUrlInput.style.display = 'block';
if (coverToggleUrlBtn) {
coverToggleUrlBtn.textContent = 'Upload';
coverToggleUrlBtn.title = 'Switch to file upload';
}
} else {
if (coverUploadBtn) {
coverUploadBtn.style.flex = '1';
coverUploadBtn.style.display = 'flex';
}
if (coverUrlInput) coverUrlInput.style.display = 'none';
if (coverToggleUrlBtn) {
coverToggleUrlBtn.textContent = 'or URL';
coverToggleUrlBtn.title = 'Switch to URL input';
}
}
modal.dataset.editingId = playlistId;
document.getElementById('import-section').style.display = 'none';
modal.classList.add('active');
@ -1313,6 +1470,29 @@ document.addEventListener('DOMContentLoaded', async () => {
};
}
// Set cover upload state - show URL input if there's an existing cover
const coverUploadBtn = document.getElementById('playlist-cover-upload-btn');
const coverUrlInput = document.getElementById('playlist-cover-input');
const coverToggleUrlBtn = document.getElementById('playlist-cover-toggle-url-btn');
if (playlist.cover) {
if (coverUploadBtn) coverUploadBtn.style.display = 'none';
if (coverUrlInput) coverUrlInput.style.display = 'block';
if (coverToggleUrlBtn) {
coverToggleUrlBtn.textContent = 'Upload';
coverToggleUrlBtn.title = 'Switch to file upload';
}
} else {
if (coverUploadBtn) {
coverUploadBtn.style.flex = '1';
coverUploadBtn.style.display = 'flex';
}
if (coverUrlInput) coverUrlInput.style.display = 'none';
if (coverToggleUrlBtn) {
coverToggleUrlBtn.textContent = 'or URL';
coverToggleUrlBtn.title = 'Switch to URL input';
}
}
modal.dataset.editingId = playlistId;
document.getElementById('import-section').style.display = 'none';
modal.classList.add('active');

View file

@ -696,9 +696,24 @@ export async function showAddToPlaylistModal(track) {
document.getElementById('playlist-modal-title').textContent = 'Create Playlist';
document.getElementById('playlist-name-input').value = '';
document.getElementById('playlist-cover-input').value = '';
document.getElementById('playlist-cover-file-input').value = '';
document.getElementById('playlist-description-input').value = '';
createModal.dataset.editingId = '';
document.getElementById('csv-import-section').style.display = 'none';
document.getElementById('import-section').style.display = 'none';
// Reset cover upload state
const coverUploadBtn = document.getElementById('playlist-cover-upload-btn');
const coverUrlInput = document.getElementById('playlist-cover-input');
const coverToggleUrlBtn = document.getElementById('playlist-cover-toggle-url-btn');
if (coverUploadBtn) {
coverUploadBtn.style.flex = '1';
coverUploadBtn.style.display = 'flex';
}
if (coverUrlInput) coverUrlInput.style.display = 'none';
if (coverToggleUrlBtn) {
coverToggleUrlBtn.textContent = 'or URL';
coverToggleUrlBtn.title = 'Switch to URL input';
}
// Pass track
createModal._pendingTracks = [track];
@ -1096,9 +1111,24 @@ export async function handleTrackAction(
document.getElementById('playlist-modal-title').textContent = 'Create Playlist';
document.getElementById('playlist-name-input').value = '';
document.getElementById('playlist-cover-input').value = '';
document.getElementById('playlist-cover-file-input').value = '';
document.getElementById('playlist-description-input').value = '';
createModal.dataset.editingId = '';
document.getElementById('csv-import-section').style.display = 'none';
document.getElementById('import-section').style.display = 'none';
// Reset cover upload state
const coverUploadBtn = document.getElementById('playlist-cover-upload-btn');
const coverUrlInput = document.getElementById('playlist-cover-input');
const coverToggleUrlBtn = document.getElementById('playlist-cover-toggle-url-btn');
if (coverUploadBtn) {
coverUploadBtn.style.flex = '1';
coverUploadBtn.style.display = 'flex';
}
if (coverUrlInput) coverUrlInput.style.display = 'none';
if (coverToggleUrlBtn) {
coverToggleUrlBtn.textContent = 'or URL';
coverToggleUrlBtn.title = 'Switch to URL input';
}
// Pass track
createModal._pendingTracks = [item];