eerrrrrr
All checks were successful
CI - Build Tonehaus Docker image / tonehaus-ci-build (push) Successful in 1m57s

This commit is contained in:
2025-11-27 23:42:17 +00:00
parent 054e970df9
commit 1c98a634c3
50 changed files with 1666 additions and 593 deletions

View File

@@ -1,46 +1,100 @@
{% extends 'base.html.twig' %}
{% block title %}Album Search{% endblock %}
{% block body %}
<h1 class="h4 mb-3">Search Albums</h1>
<form class="row g-2 mb-2" action="{{ path('album_search') }}" method="get">
{% set query_value = query|default('') %}
{% set album_value = album|default('') %}
{% set artist_value = artist|default('') %}
{% set year_from_value = year_from|default('') %}
{% set year_to_value = year_to|default('') %}
{% set source_value = source|default('all') %}
{% set has_search = (query_value is not empty) or (album_value is not empty) or (artist_value is not empty) or (year_from_value is not empty) or (year_to_value is not empty) or (source_value != 'all') %}
{% set advanced_open = (album_value is not empty) or (artist_value is not empty) or (year_from_value is not empty) or (year_to_value is not empty) or (source_value != 'all') %}
{% set landing_view = not has_search %}
{% if landing_view %}
<style>
.landing-search-form {
max-width: 640px;
margin: 0 auto;
}
.landing-search-input {
border-radius: 999px;
padding: 1rem 1.5rem;
font-size: 1.1rem;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}
</style>
{% endif %}
<h1 class="{{ landing_view ? 'display-6 text-center mb-4' : 'h4 mb-3' }}">Search Albums</h1>
<form class="{{ landing_view ? 'landing-search-form mb-4' : 'row g-2 mb-2 align-items-center' }}" action="{{ path('album_search') }}" method="get">
{% if landing_view %}
<div>
<input class="form-control form-control-lg landing-search-input" type="search" name="q" value="{{ query_value }}" placeholder="Search albums, artists..." autocomplete="off" />
</div>
<div class="d-flex justify-content-center gap-3 mt-3">
<button class="btn btn-success px-4" type="submit">Search</button>
<button class="btn btn-outline-secondary px-4" type="button" data-bs-toggle="collapse" data-bs-target="#advancedSearch" aria-expanded="{{ advanced_open ? 'true' : 'false' }}" aria-controls="advancedSearch">Advanced</button>
</div>
<div class="mt-3">
<div class="collapse {{ advanced_open ? 'show' : '' }}" id="advancedSearch">
<div class="row g-2">
<div class="col-sm-3 order-sm-4">
<select class="form-select" name="source">
<option value="all" {{ source_value == 'all' ? 'selected' : '' }}>All sources</option>
<option value="spotify" {{ source_value == 'spotify' ? 'selected' : '' }}>Spotify</option>
<option value="user" {{ source_value == 'user' ? 'selected' : '' }}>User-created</option>
</select>
</div>
<div class="col-sm-4">
<input class="form-control" type="text" name="album" value="{{ album_value }}" placeholder="Album title" />
</div>
<div class="col-sm-4">
<input class="form-control" type="text" name="artist" value="{{ artist_value }}" placeholder="Artist" />
</div>
<div class="col-sm-2">
<input class="form-control" type="number" name="year_from" value="{{ year_from_value }}" placeholder="Year from" min="1900" max="2100" />
</div>
<div class="col-sm-2">
<input class="form-control" type="number" name="year_to" value="{{ year_to_value }}" placeholder="Year to" min="1900" max="2100" />
</div>
</div>
</div>
</div>
{% else %}
<div class="col-sm">
<input class="form-control" type="search" name="q" value="{{ query }}" placeholder="Search.." autocomplete="off" />
<input class="form-control" type="search" name="q" value="{{ query_value }}" placeholder="Search.." autocomplete="off" />
</div>
<div class="col-auto">
<div class="col-auto d-flex gap-2">
<button class="btn btn-success" type="submit">Search</button>
<button class="btn btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#advancedSearch" aria-expanded="{{ advanced_open ? 'true' : 'false' }}" aria-controls="advancedSearch">Advanced</button>
</div>
<div class="col-12">
<a class="link-secondary" data-bs-toggle="collapse" href="#advancedSearch" role="button" aria-expanded="false" aria-controls="advancedSearch">Advanced search</a>
</div>
<div class="collapse col-12" id="advancedSearch">
<div class="collapse col-12 {{ advanced_open ? 'show' : '' }}" id="advancedSearch">
<div class="row g-2 mt-1">
<div class="col-sm-3 order-sm-4">
<select class="form-select" name="source">
<option value="all" {{ (source is defined and source == 'all') or source is not defined ? 'selected' : '' }}>All sources</option>
<option value="spotify" {{ (source is defined and source == 'spotify') ? 'selected' : '' }}>Spotify</option>
<option value="user" {{ (source is defined and source == 'user') ? 'selected' : '' }}>User-created</option>
<option value="all" {{ source_value == 'all' ? 'selected' : '' }}>All sources</option>
<option value="spotify" {{ source_value == 'spotify' ? 'selected' : '' }}>Spotify</option>
<option value="user" {{ source_value == 'user' ? 'selected' : '' }}>User-created</option>
</select>
</div>
<div class="col-sm-4">
<input class="form-control" type="text" name="album" value="{{ album }}" placeholder="Album title" />
<input class="form-control" type="text" name="album" value="{{ album_value }}" placeholder="Album title" />
</div>
<div class="col-sm-4">
<input class="form-control" type="text" name="artist" value="{{ artist }}" placeholder="Artist" />
<input class="form-control" type="text" name="artist" value="{{ artist_value }}" placeholder="Artist" />
</div>
<div class="col-sm-2">
<input class="form-control" type="number" name="year_from" value="{{ year_from }}" placeholder="Year from" min="1900" max="2100" />
<input class="form-control" type="number" name="year_from" value="{{ year_from_value }}" placeholder="Year from" min="1900" max="2100" />
</div>
<div class="col-sm-2">
<input class="form-control" type="number" name="year_to" value="{{ year_to }}" placeholder="Year to" min="1900" max="2100" />
<input class="form-control" type="number" name="year_to" value="{{ year_to_value }}" placeholder="Year to" min="1900" max="2100" />
</div>
</div>
</div>
</div>
{% endif %}
</form>
{% if query is empty and (album is empty) and (artist is empty) and (year_from is empty) and (year_to is empty) %}
<p class="text-secondary">Tip: Use the Advanced search to filter by album, artist, or year range.</p>
{% endif %}
{% if albums is defined and albums|length > 0 %}
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-4 g-3">
{% for album in albums %}
@@ -63,12 +117,9 @@
<a class="btn btn-outline-success btn-sm" href="{{ album.external_urls.spotify }}" target="_blank" rel="noopener">Open in Spotify</a>
{% endif %}
<a class="btn btn-success btn-sm" href="{{ path('album_show', {id: album.id}) }}">Reviews</a>
{% if album.source is defined and album.source == 'user' %}
<span class="badge text-bg-primary ms-2">User album</span>
{% endif %}
{% if app.user and (album.source is not defined or album.source != 'user') %}
{% if savedIds is defined and (album.id in savedIds) %}
<span class="badge text-bg-secondary ms-2">Saved</span>
{# Saved indicator intentionally omitted to reduce noise #}
{% else %}
<form class="d-inline ms-2" method="post" action="{{ path('album_save', {id: album.id}) }}">
<input type="hidden" name="_token" value="{{ csrf_token('save-album-' ~ album.id) }}">

View File

@@ -43,12 +43,26 @@
</div>
<div class="vstack gap-3 mb-4">
{% for r in reviews %}
{% set avatar = r.author.profileImagePath ?? null %}
<div class="card">
<div class="card-body">
<h6 class="card-title mb-1">{{ r.title }} <span class="text-secondary">(Rating {{ r.rating }}/10)</span></h6>
<div class="text-secondary mb-2">by {{ r.author.displayName ?? r.author.userIdentifier }}{{ r.createdAt|date('Y-m-d H:i') }}</div>
<p class="card-text">{{ r.content|u.truncate(300, '…', false) }}</p>
<a class="btn btn-link p-0" href="{{ path('review_show', {id: r.id}) }}">Read more</a>
<div class="d-flex gap-3">
<div>
{% if avatar %}
<img src="{{ avatar }}" alt="Avatar for {{ r.author.displayName ?? r.author.userIdentifier }}" class="rounded-circle border" width="56" height="56" style="object-fit: cover;">
{% else %}
<div class="rounded-circle bg-secondary text-white d-flex align-items-center justify-content-center" style="width:56px;height:56px;">
{{ (r.author.displayName ?? r.author.userIdentifier)|slice(0,1)|upper }}
</div>
{% endif %}
</div>
<div class="flex-grow-1">
<h6 class="card-title mb-1">{{ r.title }} <span class="text-secondary">(Rating {{ r.rating }}/10)</span></h6>
<div class="text-secondary mb-2">by {{ r.author.displayName ?? r.author.userIdentifier }}{{ r.createdAt|date('Y-m-d H:i') }}</div>
<p class="card-text">{{ r.content|u.truncate(300, '…', false) }}</p>
<a class="btn btn-link p-0" href="{{ path('review_show', {id: r.id}) }}">Read more</a>
</div>
</div>
</div>
</div>
{% else %}