mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
fix: add proper local Jellyfin mapping modal for Map to Local button
Some checks are pending
CI / build-and-test (push) Waiting to run
Some checks are pending
CI / build-and-test (push) Waiting to run
Map to Local now opens a Jellyfin search modal instead of the external provider modal.
This commit is contained in:
@@ -1174,7 +1174,7 @@
|
|||||||
<div class="modal-content" style="max-width: 600px;">
|
<div class="modal-content" style="max-width: 600px;">
|
||||||
<h3>Map Track to External Provider</h3>
|
<h3>Map Track to External Provider</h3>
|
||||||
<p style="color: var(--text-secondary); margin-bottom: 16px;">
|
<p style="color: var(--text-secondary); margin-bottom: 16px;">
|
||||||
Map this track to an external provider (SquidWTF, Deezer, or Qobuz). For local Jellyfin tracks, use the Spotify Import plugin instead.
|
Map this track to an external provider (SquidWTF, Deezer, or Qobuz). For local Jellyfin tracks, use the Jellyfin mapping modal instead.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Track Info -->
|
<!-- Track Info -->
|
||||||
@@ -1216,6 +1216,43 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Local Jellyfin Track Mapping Modal -->
|
||||||
|
<div class="modal" id="local-map-modal">
|
||||||
|
<div class="modal-content" style="max-width: 700px;">
|
||||||
|
<h3>Map Track to Local Jellyfin Track</h3>
|
||||||
|
<p style="color: var(--text-secondary); margin-bottom: 16px;">
|
||||||
|
Search your Jellyfin library and select a local track to map to this Spotify track.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Track Info -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Spotify Track (Position <span id="local-map-position"></span>)</label>
|
||||||
|
<div style="background: var(--bg-primary); padding: 12px; border-radius: 8px; margin-bottom: 16px;">
|
||||||
|
<strong id="local-map-spotify-title"></strong><br>
|
||||||
|
<span style="color: var(--text-secondary);" id="local-map-spotify-artist"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Search Section -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Search Jellyfin Library</label>
|
||||||
|
<input type="text" id="local-map-search" placeholder="Search for track name or artist...">
|
||||||
|
<button onclick="searchJellyfinTracks()" style="margin-top: 8px; width: 100%;">🔍 Search</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Search Results -->
|
||||||
|
<div id="local-map-results" style="max-height: 300px; overflow-y: auto; margin-top: 16px;"></div>
|
||||||
|
|
||||||
|
<input type="hidden" id="local-map-playlist-name">
|
||||||
|
<input type="hidden" id="local-map-spotify-id">
|
||||||
|
<input type="hidden" id="local-map-jellyfin-id">
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button onclick="closeModal('local-map-modal')">Cancel</button>
|
||||||
|
<button class="primary" onclick="saveLocalMapping()" id="local-map-save-btn" disabled>Save Mapping</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Link Playlist Modal -->
|
<!-- Link Playlist Modal -->
|
||||||
<div class="modal" id="link-playlist-modal">
|
<div class="modal" id="link-playlist-modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@@ -2997,8 +3034,27 @@
|
|||||||
saveBtn.disabled = !externalId;
|
saveBtn.disabled = !externalId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open manual mapping modal (external only)
|
// Open local Jellyfin mapping modal
|
||||||
function openManualMap(playlistName, position, title, artist, spotifyId) {
|
function openManualMap(playlistName, position, title, artist, spotifyId) {
|
||||||
|
document.getElementById('local-map-playlist-name').value = playlistName;
|
||||||
|
document.getElementById('local-map-position').textContent = position + 1;
|
||||||
|
document.getElementById('local-map-spotify-title').textContent = title;
|
||||||
|
document.getElementById('local-map-spotify-artist').textContent = artist;
|
||||||
|
document.getElementById('local-map-spotify-id').value = spotifyId;
|
||||||
|
|
||||||
|
// Pre-fill search with track info
|
||||||
|
document.getElementById('local-map-search').value = `${title} ${artist}`;
|
||||||
|
|
||||||
|
// Reset fields
|
||||||
|
document.getElementById('local-map-results').innerHTML = '';
|
||||||
|
document.getElementById('local-map-jellyfin-id').value = '';
|
||||||
|
document.getElementById('local-map-save-btn').disabled = true;
|
||||||
|
|
||||||
|
openModal('local-map-modal');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open external mapping modal
|
||||||
|
function openExternalMap(playlistName, position, title, artist, spotifyId) {
|
||||||
document.getElementById('map-playlist-name').value = playlistName;
|
document.getElementById('map-playlist-name').value = playlistName;
|
||||||
document.getElementById('map-position').textContent = position + 1;
|
document.getElementById('map-position').textContent = position + 1;
|
||||||
document.getElementById('map-spotify-title').textContent = title;
|
document.getElementById('map-spotify-title').textContent = title;
|
||||||
@@ -3013,12 +3069,123 @@
|
|||||||
openModal('manual-map-modal');
|
openModal('manual-map-modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alias for backward compatibility
|
// Search Jellyfin tracks for local mapping
|
||||||
function openExternalMap(playlistName, position, title, artist, spotifyId) {
|
async function searchJellyfinTracks() {
|
||||||
openManualMap(playlistName, position, title, artist, spotifyId);
|
const query = document.getElementById('local-map-search').value.trim();
|
||||||
|
if (!query) {
|
||||||
|
showToast('Please enter a search query', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultsDiv = document.getElementById('local-map-results');
|
||||||
|
resultsDiv.innerHTML = '<p style="text-align:center;padding:20px;">Searching...</p>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/admin/jellyfin/search?query=' + encodeURIComponent(query));
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
resultsDiv.innerHTML = '<p style="text-align:center;color:var(--error);padding:20px;">Search failed</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.tracks || data.tracks.length === 0) {
|
||||||
|
resultsDiv.innerHTML = '<p style="text-align:center;color:var(--text-secondary);padding:20px;">No tracks found</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultsDiv.innerHTML = data.tracks.map(track => `
|
||||||
|
<div style="padding: 12px; border: 1px solid var(--border); border-radius: 8px; margin-bottom: 8px; cursor: pointer; transition: background 0.2s;"
|
||||||
|
onclick="selectJellyfinTrack('${escapeJs(track.id)}', '${escapeJs(track.name)}', '${escapeJs(track.artist)}')"
|
||||||
|
onmouseover="this.style.background='var(--bg-primary)'"
|
||||||
|
onmouseout="this.style.background='transparent'">
|
||||||
|
<strong>${escapeHtml(track.name)}</strong><br>
|
||||||
|
<span style="color: var(--text-secondary); font-size: 0.9em;">${escapeHtml(track.artist)}</span>
|
||||||
|
${track.album ? '<br><span style="color: var(--text-secondary); font-size: 0.85em;">' + escapeHtml(track.album) + '</span>' : ''}
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Search error:', error);
|
||||||
|
resultsDiv.innerHTML = '<p style="text-align:center;color:var(--error);padding:20px;">Search failed</p>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save manual mapping (external only)
|
// Select a Jellyfin track for mapping
|
||||||
|
function selectJellyfinTrack(jellyfinId, name, artist) {
|
||||||
|
document.getElementById('local-map-jellyfin-id').value = jellyfinId;
|
||||||
|
document.getElementById('local-map-save-btn').disabled = false;
|
||||||
|
|
||||||
|
// Highlight selected track
|
||||||
|
document.querySelectorAll('#local-map-results > div').forEach(div => {
|
||||||
|
div.style.background = 'transparent';
|
||||||
|
div.style.border = '1px solid var(--border)';
|
||||||
|
});
|
||||||
|
event.target.closest('div').style.background = 'var(--primary)';
|
||||||
|
event.target.closest('div').style.border = '1px solid var(--primary)';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save local Jellyfin mapping
|
||||||
|
async function saveLocalMapping() {
|
||||||
|
const playlistName = document.getElementById('local-map-playlist-name').value;
|
||||||
|
const spotifyId = document.getElementById('local-map-spotify-id').value;
|
||||||
|
const jellyfinId = document.getElementById('local-map-jellyfin-id').value;
|
||||||
|
|
||||||
|
if (!jellyfinId) {
|
||||||
|
showToast('Please select a Jellyfin track', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
spotifyId,
|
||||||
|
jellyfinId
|
||||||
|
};
|
||||||
|
|
||||||
|
// Show loading state
|
||||||
|
const saveBtn = document.getElementById('local-map-save-btn');
|
||||||
|
const originalText = saveBtn.textContent;
|
||||||
|
saveBtn.textContent = 'Saving...';
|
||||||
|
saveBtn.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
||||||
|
|
||||||
|
const res = await fetch(`/api/admin/playlists/${encodeURIComponent(playlistName)}/map`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(requestBody),
|
||||||
|
signal: controller.signal
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
showToast('Track mapped successfully!', 'success');
|
||||||
|
closeModal('local-map-modal');
|
||||||
|
|
||||||
|
// Refresh the tracks view if it's open
|
||||||
|
const tracksModal = document.getElementById('tracks-modal');
|
||||||
|
if (tracksModal.style.display === 'flex') {
|
||||||
|
await viewTracks(playlistName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const data = await res.json();
|
||||||
|
showToast(data.error || 'Failed to save mapping', 'error');
|
||||||
|
saveBtn.textContent = originalText;
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
showToast('Request timed out. The mapping may still be processing.', 'warning');
|
||||||
|
} else {
|
||||||
|
showToast('Failed to save mapping', 'error');
|
||||||
|
}
|
||||||
|
saveBtn.textContent = originalText;
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save manual mapping (external only) - kept for backward compatibility
|
||||||
async function saveManualMapping() {
|
async function saveManualMapping() {
|
||||||
const playlistName = document.getElementById('map-playlist-name').value;
|
const playlistName = document.getElementById('map-playlist-name').value;
|
||||||
const spotifyId = document.getElementById('map-spotify-id').value;
|
const spotifyId = document.getElementById('map-spotify-id').value;
|
||||||
|
|||||||
Reference in New Issue
Block a user