Compare commits

..

1 Commits

Author SHA1 Message Date
5ca0cd651d wip: toggling visibility of newly added download 2025-06-04 22:12:06 -05:00
16 changed files with 71 additions and 66 deletions

View File

@@ -7,7 +7,13 @@ import { getComponent } from '@symfony/ux-live-component';
/* stimulusFetch: 'lazy' */ /* stimulusFetch: 'lazy' */
export default class extends Controller { export default class extends Controller {
static targets = ['download'] static targets = ['download', 'downloadRow', 'viewAllBtn']
static values = {
isWidget: Boolean,
perPage: Number,
}
component = null;
async initialize() { async initialize() {
this.component = await getComponent(this.element); this.component = await getComponent(this.element);
@@ -22,9 +28,38 @@ export default class extends Controller {
// this.fooTarget.addEventListener('click', this._fooBar) // this.fooTarget.addEventListener('click', this._fooBar)
} }
downloadRowTargetConnected(target) {
if (Boolean(target.getAttribute('isBroadcasted')) === true) {
this.viewAllBtnTarget.parentElement.append(this.viewAllBtnTarget);
if (this.downloadRowTargets.length > this.perPageValue) {
target.classList.add('hidden');
this.viewAllBtnTarget.classList.remove('hidden');
} else {
this.viewAllBtnTarget.classList.add('hidden');
}
}
}
downloadRowTargetDisconnected(target) {
this.viewAllBtnTarget.parentElement.append(this.viewAllBtnTarget);
let i = 1;
this.downloadRowTargets.forEach((downloadRow) => {
console.log(downloadRow)
if (i <= this.perPageValue) {
downloadRow.classList.remove('hidden');
} else {
downloadRow.classList.add('hidden');
}
})
if (this.downloadRowTargets.length > this.perPage) {
this.viewAllBtnTarget.classList.remove('hidden');
}
}
downloadTargetConnected(target) { downloadTargetConnected(target) {
let downloads = this.element.querySelectorAll('tbody tr'); let downloads = this.element.querySelectorAll('tbody tr');
if (downloads.length > 5) { if (downloads.length > this.perPageValue) {
target.classList.add('hidden'); target.classList.add('hidden');
} }
} }

View File

@@ -3,6 +3,8 @@
frankenphp { frankenphp {
{$FRANKENPHP_CONFIG} {$FRANKENPHP_CONFIG}
} }
} }

View File

@@ -33,12 +33,11 @@ final class SearchController extends AbstractController
]); ]);
} }
#[Route('/result/{mediaType}/{imdbId}', name: 'app_search_result')] #[Route('/result/{mediaType}/{tmdbId}', name: 'app_search_result')]
public function result( public function result(
GetMediaInfoInput $input, GetMediaInfoInput $input,
): Response { ): Response {
$result = $this->getMediaInfoHandler->handle($input->toCommand()); $result = $this->getMediaInfoHandler->handle($input->toCommand());
$this->warmDownloadOptionCache($result->media); $this->warmDownloadOptionCache($result->media);
return $this->render('search/result.html.twig', [ return $this->render('search/result.html.twig', [

View File

@@ -3,11 +3,8 @@
namespace App\Download\Framework\Entity; namespace App\Download\Framework\Entity;
use App\Download\Framework\Repository\DownloadRepository; use App\Download\Framework\Repository\DownloadRepository;
use App\Torrentio\Result\ResultFactory;
use App\Torrentio\Result\TorrentioResult;
use App\User\Framework\Entity\User; use App\User\Framework\Entity\User;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Nihilarr\PTN;
use Symfony\UX\Turbo\Attribute\Broadcast; use Symfony\UX\Turbo\Attribute\Broadcast;
#[ORM\Entity(repositoryClass: DownloadRepository::class)] #[ORM\Entity(repositoryClass: DownloadRepository::class)]
@@ -165,16 +162,4 @@ class Download
return $this; return $this;
} }
public function getPtn(): object
{
$ptn = (object) (new PTN())->parse($this->filename);
if ($this->mediaType === "tvshows") {
$ptn->season = str_pad($ptn->season, 2, "0", STR_PAD_LEFT);
$ptn->episode = str_pad($ptn->episode, 2, "0", STR_PAD_LEFT);
}
return $ptn;
}
} }

View File

@@ -8,7 +8,7 @@ use OneToMany\RichBundle\Contract\CommandInterface;
class GetMediaInfoCommand implements CommandInterface class GetMediaInfoCommand implements CommandInterface
{ {
public function __construct( public function __construct(
public string $imdbId, public string $tmdbId,
public string $mediaType, public string $mediaType,
) {} ) {}
} }

View File

@@ -18,7 +18,7 @@ class GetMediaInfoHandler implements HandlerInterface
public function handle(CommandInterface $command): ResultInterface public function handle(CommandInterface $command): ResultInterface
{ {
$media = $this->tmdb->mediaDetails($command->imdbId, $command->mediaType); $media = $this->tmdb->mediaDetails($command->tmdbId, $command->mediaType);
return new GetMediaInfoResult($media); return new GetMediaInfoResult($media);
} }

View File

@@ -12,8 +12,8 @@ use OneToMany\RichBundle\Contract\InputInterface;
class GetMediaInfoInput implements InputInterface class GetMediaInfoInput implements InputInterface
{ {
public function __construct( public function __construct(
#[SourceRoute('imdbId')] #[SourceRoute('tmdbId')]
public string $imdbId, public string $tmdbId,
#[SourceRoute('mediaType')] #[SourceRoute('mediaType')]
public string $mediaType, public string $mediaType,
@@ -21,6 +21,6 @@ class GetMediaInfoInput implements InputInterface
public function toCommand(): CommandInterface public function toCommand(): CommandInterface
{ {
return new GetMediaInfoCommand($this->imdbId, $this->mediaType); return new GetMediaInfoCommand($this->tmdbId, $this->mediaType);
} }
} }

View File

@@ -133,12 +133,9 @@ class Tmdb
if (!$result instanceof Movie && !$result instanceof Tv) { if (!$result instanceof Movie && !$result instanceof Tv) {
continue; continue;
} }
$results[] = $this->parseResult($result); $results[] = $this->parseResult($result);
} }
$results = array_filter($results, fn ($result) => null !== $result->imdbId);
return $results; return $results;
} }
@@ -146,16 +143,7 @@ class Tmdb
{ {
$finder = new Find($this->client); $finder = new Find($this->client);
$result = $finder->findBy($id, ['external_source' => 'imdb_id']); $result = $finder->findBy($id, ['external_source' => 'imdb_id']);
return $this->parseResult($result);
if (count($result['movie_results']) > 0) {
return $result['movie_results'][0]['id'];
} elseif (count($result['tv_results']) > 0) {
return $result['tv_results'][0]['id'];
} elseif (count($result['tv_episode_results']) > 0) {
return $result['tv_episode_results'][0]['show_id'];
}
throw new \Exception("No results found for $id");
} }
public function movieDetails(string $id) public function movieDetails(string $id)
@@ -195,8 +183,6 @@ class Tmdb
public function mediaDetails(string $id, string $type) public function mediaDetails(string $id, string $type)
{ {
$id = $this->find($id);
if ($type === "movies") { if ($type === "movies") {
return $this->movieDetails($id); return $this->movieDetails($id);
} else { } else {

View File

@@ -19,7 +19,7 @@ class GetMovieOptionsHandler implements HandlerInterface
public function handle(CommandInterface $command): ResultInterface public function handle(CommandInterface $command): ResultInterface
{ {
return new GetMovieOptionsResult( return new GetMovieOptionsResult(
media: $this->tmdb->mediaDetails($command->imdbId, 'movies'), media: $this->tmdb->mediaDetails($command->tmdbId, 'movies'),
results: $this->torrentio->search($command->imdbId, 'movies'), results: $this->torrentio->search($command->imdbId, 'movies'),
); );
} }

View File

@@ -11,4 +11,6 @@ final class DownloadListRow
public Download $download; public Download $download;
public bool $isWidget = true; public bool $isWidget = true;
public bool $isBroadcasted = false;
} }

View File

@@ -5,7 +5,7 @@
<turbo-stream action="append" target="active_downloads"> <turbo-stream action="append" target="active_downloads">
<template> <template>
<twig:DownloadListRow download="{{ entity }}" /> <twig:DownloadListRow download="{{ entity }}" isBroadcasted="true" />
</template> </template>
</turbo-stream> </turbo-stream>
{% endblock %} {% endblock %}
@@ -35,7 +35,7 @@
<turbo-stream action="prepend" target="complete_downloads"> <turbo-stream action="prepend" target="complete_downloads">
<template> <template>
<twig:DownloadListRow download="{{ entity }}" /> <twig:DownloadListRow download="{{ entity }}" :isBroadcasted="true" />
</template> </template>
</turbo-stream> </turbo-stream>
{% endif %} {% endif %}

View File

@@ -1,5 +1,11 @@
<div{{ attributes.defaults(stimulus_controller('download_list')) }} class="min-w-48" > <div{{ attributes.defaults(stimulus_controller('download_list', {isWidget: this.isWidget, perPage: this.perPage})) }} class="min-w-48" >
{% set table_body_id = (type == "complete") ? "complete_downloads" : "active_downloads" %} {% set table_body_id = (type == "complete") ? "complete_downloads" : "active_downloads" %}
{% if this.isWidget == true and this.downloads.items|length > this.perPage %}
{% set show_view_all = true %}
{% else %}
{% set show_view_all = false %}
{% endif %}
<table id="downloads" class="divide-y divide-gray-200 bg-gray-50 overflow-hidden rounded-lg table-auto w-full" {{ turbo_stream_listen('App\\Download\\Framework\\Entity\\Download') }}> <table id="downloads" class="divide-y divide-gray-200 bg-gray-50 overflow-hidden rounded-lg table-auto w-full" {{ turbo_stream_listen('App\\Download\\Framework\\Entity\\Download') }}>
<thead> <thead>
<tr class="bg-orange-500 bg-filter bg-blur-lg bg-opacity-80 text-gray-950"> <tr class="bg-orange-500 bg-filter bg-blur-lg bg-opacity-80 text-gray-950">
@@ -31,15 +37,13 @@
<tbody id="{{ table_body_id }}" class="divide-y divide-gray-200 dark:divide-gray-50"> <tbody id="{{ table_body_id }}" class="divide-y divide-gray-200 dark:divide-gray-50">
{% if this.downloads.items|length > 0 %} {% if this.downloads.items|length > 0 %}
{% for download in this.downloads.items %} {% for download in this.downloads.items %}
<twig:DownloadListRow download="{{ download }}" isWidget="{{ this.isWidget }}" /> <twig:DownloadListRow download="{{ download }}" isWidget="{{ this.isWidget }}" perPage="{{ this.perPage }}" />
{% endfor %} {% endfor %}
{% if this.isWidget == true and this.downloads.items|length > this.perPage %} <tr id="download_view_all" class="{{ show_view_all == false ?? "hidden" }}" {{ stimulus_target('download_list', 'viewAllBtn')}} >
<tr id="download_view_all"> <td class="py-2 whitespace-nowrap bg-orange-300 uppercase text-xs font-medium text-center text-black truncate" colspan="100%">
<td class="py-2 whitespace-nowrap bg-orange-300 uppercase text-xs font-medium text-center text-black truncate" colspan="100%"> <a href="{{ path('app_downloads') }}">View All Downloads</a>
<a href="{{ path('app_downloads') }}">View All Downloads</a> </td>
</td> </tr>
</tr>
{% endif %}
{% else %} {% else %}
<tr id="{{ table_body_id }}_no_downloads"> <tr id="{{ table_body_id }}_no_downloads">
<td class="px-6 py-4 whitespace-nowrap text-xs uppercase text-center font-medium text-gray-800 dark:text-stone-800" colspan="100%"> <td class="px-6 py-4 whitespace-nowrap text-xs uppercase text-center font-medium text-gray-800 dark:text-stone-800" colspan="100%">

View File

@@ -1,14 +1,6 @@
<tr{{ attributes }} id="ad_download_{{ download.id }}"> <tr{{ attributes }} id="ad_download_{{ download.id }}" {{ stimulus_target('download_list', 'downloadRow') }} isBroadcasted="{{ isBroadcasted ?? 'false' }}">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800 truncate"> <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800 truncate">
<a href="{{ path('app_search_result', {imdbId: download.imdbId, mediaType: download.mediaType}) }}" {{ download.title }}
class="hover:underline mr-1"
>
{{ download.title }}
</a>
{% if download.mediaType == "tvshows" %}
&mdash; <span class="ml-1">(S{{ download.ptn.season }}E{{ download.ptn.episode }})</span>
{% endif %}
</td> </td>
{% if isWidget == false %} {% if isWidget == false %}

View File

@@ -1,13 +1,13 @@
<div{{ attributes }}> <div{{ attributes }}>
<a href="{{ path('app_search_result', { <a href="{{ path('app_search_result', {
mediaType: mediaType, mediaType: mediaType,
imdbId: imdbId tmdbId: tmdbId
}) }}"> }) }}">
<img src="{{ image }}" class="w-40 rounded-md" /> <img src="{{ image }}" class="w-40 rounded-md" />
</a> </a>
<a href="{{ path('app_search_result', { <a href="{{ path('app_search_result', {
mediaType: mediaType, mediaType: mediaType,
imdbId: imdbId tmdbId: tmdbId
}) }}"> }) }}">
<h3 class="text-center text-gray-50 max-w-[16ch] text-extrabold">{{ title }}</h3> <h3 class="text-center text-gray-50 max-w-[16ch] text-extrabold">{{ title }}</h3>
</a> </a>

View File

@@ -17,7 +17,7 @@
</p> </p>
</div> </div>
<a class="h-9 rounded-md py-1 px-2 bg-green-600 text-gray-50" <a class="h-9 rounded-md py-1 px-2 bg-green-600 text-gray-50"
href="{{ path('app_search_result', {mediaType: mediaType, imdbId: imdbId}) }}" href="{{ path('app_search_result', {mediaType: mediaType, tmdbId: tmdbId}) }}"
>choose</a> >choose</a>
</div> </div>
</div> </div>

View File

@@ -22,7 +22,7 @@
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<twig:Card title="Popular Movies" contentClass="flex flex-row justify-between w-full"> <twig:Card title="Popular Movies" contentClass="flex flex-row justify-between w-full">
{% for movie in popular_movies %} {% for movie in popular_movies %}
<twig:Poster imdbId="{{ movie.imdbId }}" <twig:Poster imdbId=""
tmdbId="{{ movie.tmdbId }}" tmdbId="{{ movie.tmdbId }}"
title="{{ movie.title }}" title="{{ movie.title }}"
description="{{ movie.description }}" description="{{ movie.description }}"
@@ -34,7 +34,7 @@
</twig:Card> </twig:Card>
<twig:Card title="Popular TV Shows" contentClass="flex flex-row justify-between w-full"> <twig:Card title="Popular TV Shows" contentClass="flex flex-row justify-between w-full">
{% for movie in popular_tvshows %} {% for movie in popular_tvshows %}
<twig:Poster imdbId="{{ movie.imdbId }}" <twig:Poster imdbId=""
tmdbId="{{ movie.tmdbId }}" tmdbId="{{ movie.tmdbId }}"
title="{{ movie.title }}" title="{{ movie.title }}"
description="{{ movie.description }}" description="{{ movie.description }}"