feat: playlist art uploads?
This commit is contained in:
parent
23c39ffa6d
commit
18fc409e56
3 changed files with 268 additions and 15 deletions
69
index.html
69
index.html
|
|
@ -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
180
js/app.js
|
|
@ -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');
|
||||
|
|
|
|||
34
js/events.js
34
js/events.js
|
|
@ -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];
|
||||
|
|
|
|||
Loading…
Reference in a new issue