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;">
|
||||
<h3>Map Track to External Provider</h3>
|
||||
<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>
|
||||
|
||||
<!-- Track Info -->
|
||||
@@ -1216,6 +1216,43 @@
|
||||
</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 -->
|
||||
<div class="modal" id="link-playlist-modal">
|
||||
<div class="modal-content">
|
||||
@@ -2997,8 +3034,27 @@
|
||||
saveBtn.disabled = !externalId;
|
||||
}
|
||||
|
||||
// Open manual mapping modal (external only)
|
||||
// Open local Jellyfin mapping modal
|
||||
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-position').textContent = position + 1;
|
||||
document.getElementById('map-spotify-title').textContent = title;
|
||||
@@ -3013,12 +3069,123 @@
|
||||
openModal('manual-map-modal');
|
||||
}
|
||||
|
||||
// Alias for backward compatibility
|
||||
function openExternalMap(playlistName, position, title, artist, spotifyId) {
|
||||
openManualMap(playlistName, position, title, artist, spotifyId);
|
||||
// Search Jellyfin tracks for local mapping
|
||||
async function searchJellyfinTracks() {
|
||||
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() {
|
||||
const playlistName = document.getElementById('map-playlist-name').value;
|
||||
const spotifyId = document.getElementById('map-spotify-id').value;
|
||||
|
||||
Reference in New Issue
Block a user