feat: new Discover section shows watch providers for results
Some checks failed
SonarQube Scan / SonarQube Trigger (pull_request) Failing after 24s
SonarQube Scan / SonarQube Trigger (push) Failing after 36s

This commit is contained in:
Brock H Caldwell
2025-11-11 23:08:20 -06:00
parent c2474942a1
commit 2effa0fb07
23 changed files with 616 additions and 80 deletions

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
{{ pwa() }}
<title>{% block title %}Welcome!{% endblock %}</title>
<title>{% block title %}Torsearch{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
{% block stylesheets %}
<link rel="stylesheet" href="{{ asset('styles/app.css') }}">

View File

@@ -29,6 +29,15 @@
</a>
</li>
<li>
<a href="{{ path('app.discover') }}"
class="block rounded-lg
bg-orange-500 hover:bg-opacity-80 bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-60
px-4 py-2 text-sm font-medium text-gray-50">
Discover
</a>
</li>
<li>
<a href="{{ path('app_user_preferences') }}"
class="block rounded-lg px-4 py-2 text-sm font-medium text-gray-50 hover:bg-gray-100 hover:text-stone-700">

View File

@@ -1,14 +1,21 @@
<div{{ attributes }}>
{% if image != null and image != "https://image.tmdb.org/t/p/w500" %}
<a href="{{ path('app_search_result', {
mediaType: mediaType,
imdbId: imdbId
}) }}">
<img src="{{ preload(image) }}" class="w-full rounded-md" />
</a>
{% else %}
<div class="w-full md:w-32 h-[144px] rounded-lg bg-gray-700 flex items-center justify-center">
<twig:ux:icon width="16" name="hugeicons:loading-01" />
</div>
{% endif %}
<a href="{{ path('app_search_result', {
mediaType: mediaType,
imdbId: imdbId
}) }}">
<img src="{{ preload(image) }}" class="w-full md:w-40 rounded-md" />
</a>
<a href="{{ path('app_search_result', {
mediaType: mediaType,
imdbId: imdbId
}) }}">
<h3 class="text-center text-white md:text-md md:text-base md:max-w-[16ch]">{{ title }}</h3>
<h3 class="mt-2 text-center text-white md:text-md md:text-base md:max-w-[16ch]">{{ title }}</h3>
</a>
</div>

View File

@@ -0,0 +1,30 @@
<div{{ attributes.defaults(stimulus_controller('discover_media_results')) }} class="flex flex-col">
<div class="grid grid-cols-1 md:grid-cols-6 gap-4">
{% for i in range(0, media|length - 1) %}
{% if i > 5 and tease is true %}
{% set class_list = "hidden" %}
{% else %}
{% set class_list = "" %}
{% endif %}
{% set poster = media[i] %}
<twig:Poster data-discover-media-results-target="poster"
imdbId="{{ poster.imdbId }}"
tmdbId="{{ poster.tmdbId }}"
title="{{ poster.title }}"
description="{{ poster.description }}"
image="{{ poster.poster }}"
year="{{ poster.year }}"
mediaType="movies"
class="pb-2 w-full rounded-lg {{ class_list }}"
/>
{% endfor %}
</div>
{% if tease == true %}
<div class="inline-flex self-end text-white">
<button data-discover-media-results-target="moreBtn" data-action="click->discover-media-results#moreResults" href="#" class="underline">More</button>
<a data-discover-media-results-target="moreLink" href="{{ url('app.discover.browse', {mediaType: mediaType, page: 2, genreId: genreId}) }}" class="underline hidden">More &gt;</a>
</div>
{% endif %}
</div>

View File

@@ -1,7 +1,7 @@
<div{{ attributes }}>
<div class="p-4 flex flex-col md:flex-row gap-6 bg-orange-500 bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-60 rounded-md">
{% if poster != null and poster != "https://image.tmdb.org/t/p/w500" %}
<img class="w-full md:w-24 rounded-lg" src="{{ poster }}" />
<img class="w-full md:w-24 rounded-lg" src="{{ preload(poster) }}" />
{% else %}
<div class="w-full md:w-32 h-[144px] rounded-lg bg-gray-700 flex items-center justify-center">
<twig:ux:icon width="16" name="hugeicons:loading-01" />

View File

@@ -0,0 +1,17 @@
{% extends 'base.html.twig' %}
{% block title %}Discover {{ media_type|capitalize }} &mdash; {{ parent() }}{% endblock %}
{% block h2 %}Discover {{ media_type|capitalize }}{% endblock %}
{% block body %}
<div class="p-4 flex flex-col gap-4">
{% for genreTitle, genreId in genres %}
<twig:Turbo:Frame id="genre_{{ media_type }}_{{ genreId }}" src="{{ path('api.tmdb.genre', {
mediaType: media_type,
genreId: genreId,
block: 'genre_results',
target: 'genre_' ~ media_type~ '_' ~ genreId
}) }}">
</twig:Turbo:Frame>
{% endfor %}
</div>
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends 'base.html.twig' %}
{% block title %}Discover {{ genre }} {{ media_type|capitalize }} &mdash; {{ parent() }}{% endblock %}
{% block h2 %}Discover {{ genre }} {{ media_type|capitalize }}{% endblock %}
{% block body %}
<div class="p-4 flex flex-col gap-4">
<twig:Card title="{{ genre }}" class="w-full">
<twig:PosterContainer tease="'false'" genreId="{{ genre_id }}" mediaType="{{ media_type }}" media="{{ media }}"></twig:PosterContainer>
</twig:Card>
</div>
{% endblock %}

View File

@@ -0,0 +1,25 @@
{% block watch_providers %}
{% if result.providers %}
<turbo-stream action="replace" targets="#{{ target }}">
<template>
<div class="flex flex-row justify-start items-end gap-1 mt-2">
{% for provider in result.providers %}
<a href="#">
<img class="w-10 h-10 rounded-lg" src="{{ provider.logo }}" alt="{{ provider.name }}" title="{{ provider.name }}" />
</a>
{% endfor %}
</div>
</template>
</turbo-stream>
{% endif %}
{% endblock %}
{% block genre_results %}
<turbo-stream action="replace" targets="#{{ target }}">
<template>
<twig:Card title="{{ result.result.genre }}" class="w-full">
<twig:PosterContainer genreId="{{ result.result.genre_id }}" mediaType="{{ result.result.media_type }}" media="{{ result.result.media }}"></twig:PosterContainer>
</twig:Card>
</template>
</turbo-stream>
{% endblock %}

View File

@@ -0,0 +1,17 @@
{% extends 'base.html.twig' %}
{% block title %}Discover &mdash; {{ parent() }}{% endblock %}
{% block h2 %}Discover New Media{% endblock %}
{% block body %}
<div class="p-4 flex flex-col gap-4">
<twig:Card title="Popular Movies" class="w-full">
<twig:PosterContainer mediaType="movies" media="{{ movies }}" />
</twig:Card>
<twig:Card title="Popular Shows" class="w-full">
<twig:PosterContainer mediaType="tvshows" media="{{ shows }}" />
</twig:Card>
</div>
{% endblock %}

View File

@@ -76,48 +76,57 @@
{% if results.media.genres != null %}
<div id="genres" class="text-gray-50 my-4">
{% for genre in results.media.genres %}
<small class="px-2 py-1 border border-orange-500 rounded-full">{{ genre }}</small>
<a href="{{ url('app.discover.browse_genre', {mediaType: results.media.mediaType, genreId: genre.id}) }}" class="px-2 py-1 border border-orange-500 rounded-full text-sm">{{ genre }}</a>
{% endfor %}
</div>
{% endif %}
</div>
{% if results.media.mediaType == "tvshows" %}
<div class="flex flex-row justify-start items-end grow text-xs">
<span class="py-1 px-1.5 mr-1 grow-0 font-bold text-xs bg-orange-500 rounded-lg text-white">
<span>{{ results.media.numberSeasons }}</span> season(s)
</span>
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-sky-700 rounded-lg text-white" title='"{{ results.media.title }}" first aired on {{ results.media.premiereDate|date(null, 'UTC') }}.'>
{{ results.media.premiereDate|date(null, 'UTC') }}
</span>
</div>
{% endif %}
<div class="flex flex-col gap-2">
{% if results.media.mediaType == "tvshows" %}
<div class="flex flex-row justify-start items-end grow text-xs">
<span class="py-1 px-1.5 mr-1 grow-0 font-bold text-xs bg-orange-500 rounded-lg text-white">
<span>{{ results.media.numberSeasons }}</span> season(s)
</span>
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-sky-700 rounded-lg text-white" title='"{{ results.media.title }}" first aired on {{ results.media.premiereDate|date(null, 'UTC') }}.'>
{{ results.media.premiereDate|date(null, 'UTC') }}
</span>
</div>
{% endif %}
{% if "movies" == results.media.mediaType %}
<div class="flex flex-row justify-start items-end grow text-xs">
<span class="results-count-badge py-1 px-1.5 mr-1 grow-0 font-bold text-xs bg-green-600 rounded-lg hover:cursor-pointer hover:bg-green-700 text-white">
<span class="results-count-number" id="movie_results_count">-</span> results
</span>
{% if "movies" == results.media.mediaType %}
<div class="flex flex-row justify-start items-end grow text-xs">
<span class="results-count-badge py-1 px-1.5 mr-1 grow-0 font-bold text-xs bg-green-600 rounded-lg hover:cursor-pointer hover:bg-green-700 text-white">
<span class="results-count-number" id="movie_results_count">-</span> results
</span>
<twig:Turbo:Frame id="meb_{{ results.media.imdbId }}" src="{{ path('api.library.search', {
title: results.media.title,
block: 'media_exists_badge',
target: "meb_" ~ results.media.imdbId
<twig:Turbo:Frame id="meb_{{ results.media.imdbId }}" src="{{ path('api.library.search', {
title: results.media.title,
block: 'media_exists_badge',
target: "meb_" ~ results.media.imdbId
}) }}">
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-rose-600 rounded-lg text-white" title="Movie has not been downloaded yet.">
missing
</span>
</twig:Turbo:Frame>
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-sky-700 rounded-lg text-white" title="Release date {{ results.media.episodeAirDate }}">
{{ results.media.premiereDate|date('n/j/Y', 'UTC') }}
</span>
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-orange-500 rounded-lg text-white" title="This movie has a runtime of {{ results.media.runtime }} minutes.">
{{ results.media.runtime }} minutes
</span>
</div>
{% endif %}
<twig:Turbo:Frame id="watch_providers_frame" src="{{ path('api.tmdb.watch_providers', {
mediaType: results.media.mediaType,
tmdbId: results.media.tmdbId,
block: 'watch_providers',
target: 'watch_providers_frame'
}) }}">
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-rose-600 rounded-lg text-white" title="Movie has not been downloaded yet.">
missing
</span>
</twig:Turbo:Frame>
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-sky-700 rounded-lg text-white" title="Release date {{ results.media.episodeAirDate }}">
{{ results.media.premiereDate|date('n/j/Y', 'UTC') }}
</span>
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-orange-500 rounded-lg text-white" title="This movie has a runtime of {{ results.media.runtime }} minutes.">
{{ results.media.runtime }} minutes
</span>
</div>
{% endif %}
</div>
</div>