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"
|
placeholder="Playlist name"
|
||||||
style="margin: 1rem 0"
|
style="margin: 1rem 0"
|
||||||
/>
|
/>
|
||||||
<input
|
<div
|
||||||
type="url"
|
id="playlist-cover-wrapper"
|
||||||
id="playlist-cover-input"
|
style="margin: 0.5rem 0; display: flex; gap: 0.5rem; align-items: stretch"
|
||||||
class="template-input"
|
>
|
||||||
placeholder="Cover image URL (optional)"
|
<input
|
||||||
style="margin: 0.5rem 0"
|
type="url"
|
||||||
/>
|
id="playlist-cover-input"
|
||||||
<p style="font-size: 0.8rem; margin: 0">
|
class="template-input"
|
||||||
Upload your cover to
|
placeholder="Cover image URL"
|
||||||
<a href="https://catbox.moe" style="text-decoration: underline">catbox.moe</a> then paste the link
|
style="flex: 1; margin: 0; display: none"
|
||||||
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)
|
<input type="file" id="playlist-cover-file-input" accept="image/*" style="display: none" />
|
||||||
</p>
|
<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
|
<textarea
|
||||||
id="playlist-description-input"
|
id="playlist-description-input"
|
||||||
class="template-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 () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
// Initialize analytics
|
// Initialize analytics
|
||||||
initAnalytics();
|
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', () => {
|
document.getElementById('nav-back')?.addEventListener('click', () => {
|
||||||
window.history.back();
|
window.history.back();
|
||||||
});
|
});
|
||||||
|
|
@ -750,6 +867,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
document.getElementById('playlist-modal-title').textContent = 'Create Playlist';
|
document.getElementById('playlist-modal-title').textContent = 'Create Playlist';
|
||||||
document.getElementById('playlist-name-input').value = '';
|
document.getElementById('playlist-name-input').value = '';
|
||||||
document.getElementById('playlist-cover-input').value = '';
|
document.getElementById('playlist-cover-input').value = '';
|
||||||
|
document.getElementById('playlist-cover-file-input').value = '';
|
||||||
document.getElementById('playlist-description-input').value = '';
|
document.getElementById('playlist-description-input').value = '';
|
||||||
modal.dataset.editingId = '';
|
modal.dataset.editingId = '';
|
||||||
document.getElementById('import-section').style.display = 'block';
|
document.getElementById('import-section').style.display = 'block';
|
||||||
|
|
@ -775,6 +893,22 @@ 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';
|
||||||
|
|
||||||
|
// 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');
|
modal.classList.add('active');
|
||||||
document.getElementById('playlist-name-input').focus();
|
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;
|
modal.dataset.editingId = playlistId;
|
||||||
document.getElementById('import-section').style.display = 'none';
|
document.getElementById('import-section').style.display = 'none';
|
||||||
modal.classList.add('active');
|
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;
|
modal.dataset.editingId = playlistId;
|
||||||
document.getElementById('import-section').style.display = 'none';
|
document.getElementById('import-section').style.display = 'none';
|
||||||
modal.classList.add('active');
|
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-modal-title').textContent = 'Create Playlist';
|
||||||
document.getElementById('playlist-name-input').value = '';
|
document.getElementById('playlist-name-input').value = '';
|
||||||
document.getElementById('playlist-cover-input').value = '';
|
document.getElementById('playlist-cover-input').value = '';
|
||||||
|
document.getElementById('playlist-cover-file-input').value = '';
|
||||||
document.getElementById('playlist-description-input').value = '';
|
document.getElementById('playlist-description-input').value = '';
|
||||||
createModal.dataset.editingId = '';
|
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
|
// Pass track
|
||||||
createModal._pendingTracks = [track];
|
createModal._pendingTracks = [track];
|
||||||
|
|
@ -1096,9 +1111,24 @@ export async function handleTrackAction(
|
||||||
document.getElementById('playlist-modal-title').textContent = 'Create Playlist';
|
document.getElementById('playlist-modal-title').textContent = 'Create Playlist';
|
||||||
document.getElementById('playlist-name-input').value = '';
|
document.getElementById('playlist-name-input').value = '';
|
||||||
document.getElementById('playlist-cover-input').value = '';
|
document.getElementById('playlist-cover-input').value = '';
|
||||||
|
document.getElementById('playlist-cover-file-input').value = '';
|
||||||
document.getElementById('playlist-description-input').value = '';
|
document.getElementById('playlist-description-input').value = '';
|
||||||
createModal.dataset.editingId = '';
|
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
|
// Pass track
|
||||||
createModal._pendingTracks = [item];
|
createModal._pendingTracks = [item];
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue