This commit is contained in:
Kamil
2024-11-22 12:29:29 +00:00
parent 4be8622bcd
commit 435f903b94
62 changed files with 3690 additions and 168 deletions

View File

@@ -0,0 +1,12 @@
{% if item.can_add %}
<button class="btn btn-success" hx-post="/addplaylist" hx-include="this" hx-swap="outerHTML" hx-target="this"
data-bs-toggle="tooltip" title="Add to my Jellyfin"
hx-vals='{"item_id": "{{ item.id }}", "item_name": "{{ item.name }}"}'>
<i class="fa-solid fa-circle-plus"> </i>
</button>
{% elif item.can_remove %}
<button class="btn btn-warning" hx-delete="{{ url_for('delete_playlist', playlist_id=item['jellyfin_id']) }}"
hx-include="this" hx-swap="outerHTML" hx-target="this" data-bs-toggle="tooltip" title="Remove from Jellyfin">
<i class="fa-solid fa-circle-minus"> </i>
</button>
{% endif %}

View File

@@ -0,0 +1,37 @@
<div id="empty"></div>
<div class="search-results">
{% if results %}
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Artist(s)</th>
<th>Path</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{% for track in results %}
<tr>
<td>{{ track.Name }}</td>
<td>{{ ', '.join(track.Artists) }}</td>
<td>{{ track.Path}}</td>
<td>
<button class="btn btn-sm btn-primary" onclick="playJellyfinTrack(this, '{{ track.Id }}')">
<i class="fas fa-play"></i>
</button>
</td>
<td>
<button hx-swap="beforebegin" class="btn btn-sm btn-success" hx-post="/associate_track" hx-vals='{"jellyfin_id": "{{ track.Id }}","spotify_id": "{{ spotify_id}}"}'>
Associate Track
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No results found.</p>
{% endif %}
</div>

View File

@@ -0,0 +1,14 @@
<div class="d-flex align-items-center row sticky-top py-3 mb-3 bg-dark" style="top: 0; z-index: 1000;">
<div class="col-6">
<img src="{{ playlist_cover }}" class="figure-img">
</div>
<div class="col-6">
<div class="playlist-info">
<h1>{{ playlist_name }}</h1>
<p>{{ playlist_description }}</p>
<p>{{ track_count }} songs, {{ total_duration }}</p>
<p>Last Updated: {{ last_updated}} | Last Change: {{ last_changed}}</p>
{% include 'partials/_add_remove_button.html' %}
</div>
</div>
</div>

View File

@@ -0,0 +1,31 @@
{% if playlists %}
<h1 class="mb-4">Playlists</h1>
<div class="row row-cols-1 row-cols-md-4 g-4">
{% for item in playlists %}
{% include 'partials/_spotify_item.html' %}
{% endfor %}
</div>
{% endif %}
{% if albums %}
<h1 class="mb-4">Albums</h1>
<div class="row row-cols-1 row-cols-md-4 g-4">
{% for item in albums %}
{% include 'partials/_spotify_item.html' %}
{% endfor %}
</div>
{% endif %}
{% if artists %}
<h1 class="mb-4">Artists</h1>
<div class="row row-cols-1 row-cols-md-4 g-4">
{% for item in artists %}
{% include 'partials/_spotify_item.html' %}
{% endfor %}
</div>
{% endif %}

View File

@@ -0,0 +1,49 @@
<div class="col">
<div class="card h-100 d-flex flex-column position-relative">
<!-- Badge: Only show if status is available (i.e., playlist has been requested) -->
{% if item.status %}
<span style="z-index: 99;" class="badge position-absolute top-0 end-0 m-2
{% if item.status == 'green' %} bg-success
{% elif item.status == 'yellow' %} bg-warning text-dark
{% else %} bg-danger {% endif %}" data-bs-toggle="tooltip" title="{% if item.track_count > 0 %}
{{ item.tracks_available }} Track Available / {{ item.tracks_linked}} Tracks linked/ {{ item.track_count}} Total
{%endif%}
">
{% if item.track_count > 0 %}
{{ item.tracks_available }} / {{ item.tracks_linked}} / {{ item.track_count}}
{% else %}
not Available
{% endif %}
</span>
{% endif %}
<!-- Card Image -->
<div style="position: relative;">
<img src="{{ item.image }}" class="card-img-top" alt="{{ item.name }}">
</div>
<!-- Card Body -->
<div class="card-body d-flex flex-column justify-content-between">
<div>
<h5 class="card-title">{{ item.name }}</h5>
<p class="card-text">{{ item.description }}</p>
</div>
<div class="mt-auto pt-3">
{% if item.type == 'category'%}
<a href="{{ item.url }}" class="btn btn-primary" data-bs-toggle="tooltip" title="View Playlists">
<i class="fa-solid fa-eye"></i>
</a>
{%else%}
<a href="/playlist/view/{{ item.id }}" class="btn btn-primary" data-bs-toggle="tooltip"
title="View Playlist details">
<i class="fa-solid fa-eye"></i>
</a>
{%endif%}
{% include 'partials/_add_remove_button.html' %}
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,24 @@
{% for item in items %}
{% include 'partials/_spotify_item.html' %}
{% endfor %}
{% if next_offset < total_items %}
<div hx-get="{{ endpoint }}?offset={{ next_offset }}{{ additional_query }}"
hx-trigger="revealed"
hx-swap="beforeend"
hx-indicator=".loading-indicator"
hx-target="#items-container"
class="loading-indicator text-center">
Loading more items...
</div>
{% endif %}
<script>
// Show the loading indicator only when it is active
document.querySelectorAll('.loading-indicator').forEach(indicator => {
indicator.addEventListener('htmx:afterRequest', () => {
indicator.style.display = 'none'; // Hide the indicator after the request completes
});
});
</script>

View File

@@ -0,0 +1,22 @@
{% for task_name, task in tasks.items() %}
<tr id="task-row-{{ task_name }}">
<td>{{ task_name }}</td>
<td>{{ task.state }}</td>
<td>
{% if task.info.percent %}
{{ task.info.percent }}%
{% else %}
N/A
{% endif %}
</td>
<td>
<div>
<button hx-post="/run_task/{{ task_name }}" hx-target="#task-row-{{ task_name }}" hx-swap="outerHTML"
class="btn btn-primary">
Run Task
</button>
</div>
</td>
</tr>
{% endfor %}

View File

@@ -0,0 +1,18 @@
<div class="toast align-items-center text-white {{ 'bg-success' if success else 'bg-danger' }} border-0" role="alert"
aria-live="assertive" aria-atomic="true" style="position: fixed; bottom: 20px; right: 20px; z-index: 1000;">
<div class="d-flex">
<div class="toast-body">
{{ message }}
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"
aria-label="Close"></button>
</div>
</div>
<script>
var toastElList = [].slice.call(document.querySelectorAll('.toast'))
var toastList = toastElList.map(function (toastEl) {
return new bootstrap.Toast(toastEl)
})
toastList.forEach(toast => toast.show());
</script>

View File

@@ -0,0 +1,94 @@
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Title</th>
<th scope="col">Artist</th>
<th scope="col">Duration</th>
<th scope="col">Spotify</th>
<th scope="col">Preview</th>
<th scope="col">Status</th>
<th scope="col">Jellyfin</th>
</tr>
</thead>
<tbody>
{% for track in tracks %}
<tr>
<th scope="row">{{ loop.index }}</th>
<td>{{ track.title }}</td>
<td>{{ track.artist }}</td>
<td>{{ track.duration }}</td>
<td>
<a href="{{ track.url }}" target="_blank" class="text-success" data-bs-toggle="tooltip" title="Open in Spotify">
<i class="fab fa-spotify fa-lg"></i>
</a>
</td>
<td>
{% if track.preview_url %}
<button class="btn btn-sm btn-primary" onclick="playPreview(this, '{{ track.preview_url }}')" data-bs-toggle="tooltip" title="Play Preview">
<i class="fas fa-play"></i>
</button>
{% else %}
<span data-bs-toggle="tooltip" title="No Preview Available">
<button class="btn btn-sm" disabled><i class="fas fa-ban"></i></button>
</span>
{% endif %}
</td>
<td>
{% if not track.downloaded %}
<button class="btn btn-sm btn-danger"
data-bs-toggle="tooltip" title="{{ track.download_status if track.download_status else 'Not downloaded'}}">
<i class="fa-solid fa-triangle-exclamation"></i>
</button>
{% else %}
<button class="btn btn-sm btn-success" data-bs-toggle="tooltip" title="Track downloaded">
<i class="fa-solid fa-circle-check"></i>
</button>
{% endif %}
</td>
<td>
{% if track.jellyfin_id %}
<button class="btn btn-sm btn-success" onclick="playJellyfinTrack(this, '{{ track.jellyfin_id }}')" data-bs-toggle="tooltip" title="Play from Jellyfin">
<i class="fas fa-play"></i>
</button>
{% elif track.downloaded %}
<span data-bs-toggle="tooltip" title="Track Downloaded, but not in Jellyfin or could not be associated automatically. You can try to do the association manually">
{% set title = track.title | replace("'","") %}
<button class="btn btn-sm btn-warning" onclick="openSearchModal('{{ title }}','{{track.spotify_id}}')">
<i class="fas fa-triangle-exclamation"></i>
</button>
</span>
{% else %}
<span data-bs-toggle="tooltip" title="Not Available">
<button class="btn btn-sm" disabled><i class="fas fa-ban"></i></button>
</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="modal fade" id="searchModal" tabindex="-1" aria-labelledby="searchModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="searchModalLabel">Search Jellyfin for Track</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- htmx-enabled form -->
<form id="search-form" hx-get="/search_jellyfin" hx-target="#search-results" hx-trigger="submit">
<div class="input-group mb-3">
<input type="text" class="form-control" id="search-query" name="search_query" placeholder="Search for a track...">
<input type="hidden" class="form-control" id="spotify-id" name="spotify_id" >
<button class="btn btn-primary" type="submit">Search</button>
</div>
</form>
<div id="search-results">
<!-- Search results will be inserted here by htmx -->
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,6 @@
{% if unlinked_track_count > 0 %}
<span class="badge rounded-pill bg-danger ms-2 mb-2">
{{unlinked_track_count}}
<span class="visually-hidden">Unlinked Tracks</span>
</span>
{% endif %}

View File

@@ -0,0 +1,17 @@
<div id="alerts" hx-swap-oob="true">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="toast align-items-center text-bg-{{ 'success' if category == 'success' else 'danger' }}" role="alert"
aria-live="assertive" aria-atomic="true" style="position: fixed; bottom: 20px; right: 20px; z-index: 1000;">
<div class="d-flex">
<div class="toast-body">
{{ message }}
</div>
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>