Compare commits

...

7 Commits

27 changed files with 390 additions and 176 deletions

View File

@@ -1,4 +1,14 @@
{ {
"controllers": [], "controllers": {
"@symfony/ux-live-component": {
"live": {
"enabled": true,
"fetch": "eager",
"autoimport": {
"@symfony/ux-live-component/dist/live.min.css": true
}
}
}
},
"entrypoints": [] "entrypoints": []
} }

View File

@@ -21,7 +21,7 @@ export default class extends Controller {
} }
static outlets = ['movie-results', 'tv-results'] static outlets = ['movie-results', 'tv-results']
static targets = ['resolution', 'codec', 'language', 'provider', 'season'] static targets = ['resolution', 'codec', 'language', 'provider', 'season', 'selectAll']
static values = { static values = {
'media-type': String, 'media-type': String,
'episodes': Array, 'episodes': Array,
@@ -29,7 +29,8 @@ export default class extends Controller {
connect() { connect() {
if (this.mediaTypeValue === "tvshows") { if (this.mediaTypeValue === "tvshows") {
this.activeFilter['season'] = 1;} this.activeFilter['season'] = 1;
}
} }
async movieResultsOutletConnected(outlet) { async movieResultsOutletConnected(outlet) {
@@ -148,7 +149,7 @@ export default class extends Controller {
} else if (true === firstIncluded) { } else if (true === firstIncluded) {
count = 1; count = 1;
selectedCount = selectedCount + 1; selectedCount = selectedCount + 1;
// option.selectInput.checked = true; option.querySelector('input[type="checkbox"]').checked = true;
firstIncluded = false; firstIncluded = false;
} else { } else {
count = count + 1; count = count + 1;
@@ -158,8 +159,18 @@ export default class extends Controller {
resultList.countTarget.innerText = count; resultList.countTarget.innerText = count;
} }
}); });
} }
await results.forEach((list) => filterOperation(list, currentSeason)); await results.forEach((list) => filterOperation(list, currentSeason));
} }
uncheckSelectAllBtn() {
console.log('hurr');
this.selectAllTarget.checked = false;
}
selectAllEpisodes() {
this.tvResultsOutlets.forEach((episode) => episode.selectEpisodeForDownload());
}
} }

View File

@@ -15,7 +15,7 @@ export default class extends Controller {
active: Boolean, active: Boolean,
}; };
static targets = ['list', 'count'] static targets = ['list', 'count', 'episodeSelector']
static outlets = ['loading-icon'] static outlets = ['loading-icon']
options = [] options = []
@@ -32,7 +32,12 @@ export default class extends Controller {
.then(response => { .then(response => {
this.element.innerHTML = response; this.element.innerHTML = response;
this.options = this.element.querySelectorAll('tbody tr'); this.options = this.element.querySelectorAll('tbody tr');
this.options.forEach((option) => option.querySelector('.download-btn').dataset['title'] = this.titleValue); if (this.options.length > 0) {
this.options.forEach((option) => option.querySelector('.download-btn').dataset['title'] = this.titleValue);
this.options[0].querySelector('input[type="checkbox"]').checked = true;
} else {
this.episodeSelectorTarget.disabled = true;
}
this.optionsLoaded = true; this.optionsLoaded = true;
this.loadingIconOutlet.increaseCount(); this.loadingIconOutlet.increaseCount();
}); });
@@ -49,6 +54,7 @@ export default class extends Controller {
setInActive() { setInActive() {
this.activeValue = false; this.activeValue = false;
this.episodeSelectorTarget.checked = false;
this.element.classList.add('hidden'); this.element.classList.add('hidden');
} }
@@ -56,6 +62,12 @@ export default class extends Controller {
return this.activeValue; return this.activeValue;
} }
selectEpisodeForDownload() {
if (true === this.isActive() && this.episodeSelectorTarget.disabled === false) {
this.episodeSelectorTarget.checked = !this.episodeSelectorTarget.checked;
}
}
toggleList() { toggleList() {
this.listTarget.classList.toggle('hidden'); this.listTarget.classList.toggle('hidden');
} }

View File

@@ -29,6 +29,7 @@
"symfony/stimulus-bundle": "^2.24", "symfony/stimulus-bundle": "^2.24",
"symfony/twig-bundle": "7.2.*", "symfony/twig-bundle": "7.2.*",
"symfony/ux-icons": "^2.24", "symfony/ux-icons": "^2.24",
"symfony/ux-live-component": "^2.24",
"symfony/ux-twig-component": "^2.24", "symfony/ux-twig-component": "^2.24",
"symfony/yaml": "7.2.*", "symfony/yaml": "7.2.*",
"symfonycasts/tailwind-bundle": "^0.10.0", "symfonycasts/tailwind-bundle": "^0.10.0",

96
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "0448ecb537f5d169d81a56ba2b3c2cc6", "content-hash": "09d927397449bd08c7c2b96e35d2bd60",
"packages": [ "packages": [
{ {
"name": "1tomany/data-uri", "name": "1tomany/data-uri",
@@ -6976,6 +6976,100 @@
], ],
"time": "2025-04-04T17:32:18+00:00" "time": "2025-04-04T17:32:18+00:00"
}, },
{
"name": "symfony/ux-live-component",
"version": "v2.24.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/ux-live-component.git",
"reference": "ee1a8e5d01f5b3f2f8e6856941fa8c944677e41c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/ux-live-component/zipball/ee1a8e5d01f5b3f2f8e6856941fa8c944677e41c",
"reference": "ee1a8e5d01f5b3f2f8e6856941fa8c944677e41c",
"shasum": ""
},
"require": {
"php": ">=8.1",
"symfony/deprecation-contracts": "^2.5|^3.0",
"symfony/property-access": "^5.4.5|^6.0|^7.0",
"symfony/property-info": "^5.4|^6.0|^7.0",
"symfony/stimulus-bundle": "^2.9",
"symfony/ux-twig-component": "^2.8",
"twig/twig": "^3.8.0"
},
"conflict": {
"symfony/config": "<5.4.0"
},
"require-dev": {
"doctrine/annotations": "^1.0",
"doctrine/collections": "^1.6.8|^2.0",
"doctrine/doctrine-bundle": "^2.4.3",
"doctrine/orm": "^2.9.4",
"doctrine/persistence": "^2.5.2|^3.0",
"phpdocumentor/reflection-docblock": "5.x-dev",
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
"symfony/expression-language": "^5.4|^6.0|^7.0",
"symfony/form": "^5.4|^6.0|^7.0",
"symfony/framework-bundle": "^5.4|^6.0|^7.0",
"symfony/options-resolver": "^5.4|^6.0|^7.0",
"symfony/phpunit-bridge": "^6.1|^7.0",
"symfony/security-bundle": "^5.4|^6.0|^7.0",
"symfony/serializer": "^5.4|^6.0|^7.0",
"symfony/twig-bundle": "^5.4|^6.0|^7.0",
"symfony/validator": "^5.4|^6.0|^7.0",
"zenstruck/browser": "^1.2.0",
"zenstruck/foundry": "^2.0"
},
"type": "symfony-bundle",
"extra": {
"thanks": {
"url": "https://github.com/symfony/ux",
"name": "symfony/ux"
}
},
"autoload": {
"psr-4": {
"Symfony\\UX\\LiveComponent\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Live components for Symfony",
"homepage": "https://symfony.com",
"keywords": [
"components",
"symfony-ux",
"twig"
],
"support": {
"source": "https://github.com/symfony/ux-live-component/tree/v2.24.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-03-12T08:41:47+00:00"
},
{ {
"name": "symfony/ux-twig-component", "name": "symfony/ux-twig-component",
"version": "v2.24.0", "version": "v2.24.0",

View File

@@ -13,4 +13,5 @@ return [
Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true], Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Symfony\UX\LiveComponent\LiveComponentBundle::class => ['all' => true],
]; ];

View File

@@ -0,0 +1,5 @@
live_component:
resource: '@LiveComponentBundle/config/routes.php'
prefix: '/_components'
# adjust prefix to add localization to your components
#prefix: '/{_locale}/_components'

View File

@@ -22,4 +22,7 @@ return [
'@symfony/stimulus-bundle' => [ '@symfony/stimulus-bundle' => [
'path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js', 'path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js',
], ],
'@symfony/ux-live-component' => [
'path' => './vendor/symfony/ux-live-component/assets/dist/live_controller.js',
],
]; ];

View File

@@ -2,17 +2,26 @@
namespace App\Controller; namespace App\Controller;
use App\Download\Framework\Repository\DownloadRepository;
use App\Tmdb\Tmdb;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
final class IndexController extends AbstractController final class IndexController extends AbstractController
{ {
public function __construct(
private readonly DownloadRepository $downloadRepository,
private readonly Tmdb $tmdb,
) {}
#[Route('/', name: 'app_index')] #[Route('/', name: 'app_index')]
public function index(): Response public function index(): Response
{ {
return $this->render('index/index.html.twig', [ return $this->render('index/index.html.twig', [
'controller_name' => 'IndexController', 'active_downloads' => $this->downloadRepository->getActivePaginated(),
'recent_downloads' => $this->downloadRepository->latest(5),
'popular_movies' => $this->tmdb->popularMovies(1, 6),
]); ]);
} }
} }

View File

@@ -36,11 +36,12 @@ class DownloadRepository extends ServiceEntityRepository
return new \Doctrine\ORM\Tools\Pagination\Paginator($query); return new \Doctrine\ORM\Tools\Pagination\Paginator($query);
} }
public function getActivePaginated(int $pageNumber = 1, int $perPage = 10) public function getActivePaginated(int $pageNumber = 1, int $perPage = 5)
{ {
$firstResult = ($pageNumber - 1) * $perPage; $firstResult = ($pageNumber - 1) * $perPage;
$query = $this->createQueryBuilder('d') $query = $this->createQueryBuilder('d')
->andWhere('d.status IN (:statuses)') ->andWhere('d.status IN (:statuses)')
->orderBy('d.id', 'DESC')
->setParameter('statuses', ['New', 'In Progress']) ->setParameter('statuses', ['New', 'In Progress'])
->setFirstResult($firstResult) ->setFirstResult($firstResult)
->setMaxResults($perPage) ->setMaxResults($perPage)
@@ -115,4 +116,15 @@ class DownloadRepository extends ServiceEntityRepository
return $query->getResult(); return $query->getResult();
} }
public function latest(int $limit = 1)
{
return $this->createQueryBuilder('d')
->andWhere('d.status IN (:statuses)')
->setParameter('statuses', ['Complete'])
->setMaxResults($limit)
->orderBy('d.id', 'DESC')
->getQuery()
->getResult();
}
} }

View File

@@ -4,9 +4,9 @@ namespace App\Search\Action\Command;
use OneToMany\RichBundle\Contract\CommandInterface; use OneToMany\RichBundle\Contract\CommandInterface;
/** @implements CommandInterface<GetMediaInfoCommand> */
class GetMediaInfoCommand implements CommandInterface class GetMediaInfoCommand implements CommandInterface
{ {
/** @implements CommandInterface<GetMediaInfoCommand> */
public function __construct( public function __construct(
public string $tmdbId, public string $tmdbId,
public string $mediaType, public string $mediaType,

View File

@@ -4,12 +4,9 @@ namespace App\Search\Action\Command;
use OneToMany\RichBundle\Contract\CommandInterface; use OneToMany\RichBundle\Contract\CommandInterface;
/** @implements CommandInterface<SearchCommand> */
class SearchCommand implements CommandInterface class SearchCommand implements CommandInterface
{ {
/**
* @param string $term
* @implements CommandInterface<SearchCommand>
*/
public function __construct( public function __construct(
public string $term public string $term
) {} ) {}

View File

@@ -2,12 +2,14 @@
namespace App\Search\Action\Handler; namespace App\Search\Action\Handler;
use App\Search\Action\Command\GetMediaInfoCommand;
use App\Search\Action\Result\GetMediaInfoResult; use App\Search\Action\Result\GetMediaInfoResult;
use App\Tmdb\Tmdb; use App\Tmdb\Tmdb;
use OneToMany\RichBundle\Contract\CommandInterface; use OneToMany\RichBundle\Contract\CommandInterface;
use OneToMany\RichBundle\Contract\HandlerInterface; use OneToMany\RichBundle\Contract\HandlerInterface;
use OneToMany\RichBundle\Contract\ResultInterface; use OneToMany\RichBundle\Contract\ResultInterface;
/** @implements HandlerInterface<GetMediaInfoCommand, GetMediaInfoResult> */
class GetMediaInfoHandler implements HandlerInterface class GetMediaInfoHandler implements HandlerInterface
{ {
public function __construct( public function __construct(

View File

@@ -8,13 +8,13 @@ use OneToMany\RichBundle\Contract\CommandInterface;
use OneToMany\RichBundle\Contract\HandlerInterface; use OneToMany\RichBundle\Contract\HandlerInterface;
use OneToMany\RichBundle\Contract\ResultInterface; use OneToMany\RichBundle\Contract\ResultInterface;
/*** @implements HandlerInterface<SearchResult> */
class SearchHandler implements HandlerInterface class SearchHandler implements HandlerInterface
{ {
public function __construct( public function __construct(
private Tmdb $tmdb, private Tmdb $tmdb,
) {} ) {}
/*** @implements HandlerInterface<SearchResult> */
public function handle(CommandInterface $command): ResultInterface public function handle(CommandInterface $command): ResultInterface
{ {
return new SearchResult( return new SearchResult(

View File

@@ -8,6 +8,7 @@ use OneToMany\RichBundle\Attribute\SourceRoute;
use OneToMany\RichBundle\Contract\CommandInterface; use OneToMany\RichBundle\Contract\CommandInterface;
use OneToMany\RichBundle\Contract\InputInterface; use OneToMany\RichBundle\Contract\InputInterface;
/** @implements InputInterface<GetMediaInfoInput> */
class GetMediaInfoInput implements InputInterface class GetMediaInfoInput implements InputInterface
{ {
public function __construct( public function __construct(

View File

@@ -8,9 +8,7 @@ use OneToMany\RichBundle\Attribute\SourceRequest;
use OneToMany\RichBundle\Contract\CommandInterface; use OneToMany\RichBundle\Contract\CommandInterface;
use OneToMany\RichBundle\Contract\InputInterface; use OneToMany\RichBundle\Contract\InputInterface;
/** /** @implements InputInterface<SearchCommand> */
* @implements InputInterface<SearchCommand>
*/
class SearchInput implements InputInterface class SearchInput implements InputInterface
{ {
public function __construct( public function __construct(

View File

@@ -5,9 +5,9 @@ namespace App\Search\Action\Result;
use App\Tmdb\TmdbResult; use App\Tmdb\TmdbResult;
use OneToMany\RichBundle\Contract\ResultInterface; use OneToMany\RichBundle\Contract\ResultInterface;
/** @implements ResultInterface<GetMediaInfoResult> */
class GetMediaInfoResult implements ResultInterface class GetMediaInfoResult implements ResultInterface
{ {
/** @implements ResultInterface<GetMediaInfoResult> */
public function __construct( public function __construct(
public TmdbResult $media, public TmdbResult $media,
) {} ) {}

View File

@@ -4,6 +4,7 @@ namespace App\Search\Action\Result;
use OneToMany\RichBundle\Contract\ResultInterface; use OneToMany\RichBundle\Contract\ResultInterface;
/** @implements ResultInterface<SearchResult> */
class SearchResult implements ResultInterface class SearchResult implements ResultInterface
{ {
public function __construct( public function __construct(

View File

@@ -94,12 +94,18 @@ class Tmdb
$this->tvRepository = new TvRepository($this->client); $this->tvRepository = new TvRepository($this->client);
} }
public function popularMovies(int $page = 1) public function popularMovies(int $page = 1, ?int $limit = null)
{ {
$movies = $this->movieRepository->getPopular(['page' => $page]); $movies = $this->movieRepository->getPopular(['page' => $page]);
foreach ($movies as $movie) { $movies = $movies->map(function ($movie) use ($movies) {
$this->parseResult($movie); return $this->parseResult($movies[$movie], "movie");
});
$movies = array_values($movies->toArray());
if (null !== $limit) {
$movies = array_slice($movies, 0, $limit);
} }
return $movies; return $movies;

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Twig\Components;
use App\Download\Framework\Repository\DownloadRepository;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\DefaultActionTrait;
#[AsLiveComponent]
final class ActiveDownloadList
{
use DefaultActionTrait;
public function __construct(
private DownloadRepository $downloadRepository,
) {}
#[LiveAction]
public function getDownloads()
{
return $this->downloadRepository->getActivePaginated();
}
}

View File

@@ -199,6 +199,18 @@
"assets/icons/symfony.svg" "assets/icons/symfony.svg"
] ]
}, },
"symfony/ux-live-component": {
"version": "2.24",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "2.6",
"ref": "73e69baf18f47740d6f58688c5464b10cdacae06"
},
"files": [
"config/routes/ux_live_component.yaml"
]
},
"symfony/ux-twig-component": { "symfony/ux-twig-component": {
"version": "2.24", "version": "2.24",
"recipe": { "recipe": {

View File

@@ -0,0 +1,39 @@
<div{{ attributes }} class="min-w-48" data-poll="getDownloads">
<table class="divide-y divide-gray-200 dark:divide-gray-50 dark:bg-gray-50 table-fixed">
<thead>
<tr class="dark:bg-gray-50">
<th scope="col"
class="px-6 py-3 text-start text-xs font-medium text-stone-500 uppercase dark:text-stone-800 min-w-[55ch] max-w-[55ch] truncate">
Title
</th>
<th scope="col"
class="px-6 py-3 text-start text-xs font-medium text-gray-500 uppercase dark:text-stone-800">
Progress
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-50">
{% if this.getDownloads()|length > 0 %}
{% for download in this.getDownloads() %}
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800 min-w-[45ch] max-w-[45ch] truncate">
{{ download.title }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50">
<span class="p-1.5 bg-purple-600 rounded-full">
<span class="w-4 inline-block text-center text-gray-50">{{ download.progress }}</span>
</span>
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td class="px-6 py-4 whitespace-nowrap text-xs uppercase text-center col-span-2 font-medium text-gray-800 dark:text-stone-800" colspan="2">
No active downloads
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>

View File

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

View File

@@ -1,64 +1,17 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Dashboard &mdash - Torsearch{% endblock %} {% block title %}Dashboard &mdash; Torsearch{% endblock %}
{% block body %} {% block body %}
<div class="p-4 flex flex-col grow gap-4"> <div class="p-4 flex flex-col grow gap-4">
<h2 class="mb-2 text-3xl font-bold text-gray-50">Dashboard</h2> <h2 class="mb-2 text-3xl font-bold text-gray-50">Dashboard</h2>
<div class="flex flex-row gap-4"> <div class="flex flex-row gap-4">
<twig:Card title="Active Downloads" class="w-full"> <twig:Card title="Active Downloads" class="w-full">
<table class="divide-y divide-gray-200 dark:divide-gray-50 dark:bg-gray-50"> <twig:ActiveDownloadList />
<thead>
<tr class="dark:bg-gray-50">
<th scope="col"
class="px-6 py-3 text-start text-xs font-medium text-stone-500 uppercase dark:text-stone-800">
Title
</th>
<th scope="col"
class="px-6 py-3 text-start text-xs font-medium text-gray-500 uppercase dark:text-stone-800">
Progress
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-50">
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800">
The Wolf of Wallstreet
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50">
<span class="p-1.5 bg-purple-600 rounded-full">
<span class="text-gray-50">11</span>
</span>
</td>
</tr>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800">
Inception
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50">
<span class="p-1.5 bg-purple-600 rounded-full">
<span class="text-gray-50">36</span>
</span>
</td>
</tr>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800">
Hop
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50">
<span class="p-1.5 bg-purple-600 rounded-full">
<span class="text-gray-50">0</span>
</span>
</td>
</tr>
</tbody>
</table>
</twig:Card> </twig:Card>
<twig:Card title="Recent Downloads" class="w-full"> <twig:Card title="Recent Downloads" class="w-full">
<table class="divide-y divide-gray-200 dark:divide-gray-50 dark:bg-gray-50"> <table class="divide-y divide-gray-200 dark:divide-gray-50 dark:bg-gray-50 table-fixed">
<thead> <thead>
<tr class="dark:bg-gray-50"> <tr class="dark:bg-gray-50">
<th scope="col" <th scope="col"
@@ -66,56 +19,51 @@
Title Title
</th> </th>
<th scope="col" <th scope="col"
class="px-6 py-3 text-start text-xs font-medium text-end text-gray-500 uppercase dark:text-stone-800"> class="px-6 py-3 text-start text-xs font-medium text-gray-500 uppercase dark:text-stone-800">
Status Status
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-50"> <tbody class="divide-y divide-gray-200 dark:divide-gray-50">
<tr> {% if recent_downloads|length > 0 %}
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800"> {% for download in recent_downloads %}
The Family Plan <tr>
</td> <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800">
<td class="px-6 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50"> {{ download.title }}
<span class="p-1 bg-green-600 rounded-lg"> </td>
<span class="text-gray-50">Complete</span> <td class="px-6 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50">
</span> <span class="p-1 bg-green-600 rounded-lg">
</td> <span class="text-gray-50">Complete</span>
</tr> </span>
</td>
<tr> </tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800"> {% endfor %}
It <tr class="bg-blue-400">
</td> <td class="px-6 py-3 whitespace-nowrap text-xs uppercase text-center col-span-2 font-medium text-rose-400 dark:text-stone-800" colspan="2">
<td class="px-6 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50"> <a href="#">View all downloads</a>
<span class="p-1 bg-green-600 rounded-lg"> </td>
<span class="text-gray-50">Complete</span> </tr>
</span> {% else %}
</td> <tr>
</tr> <td class="px-6 py-4 whitespace-nowrap text-xs uppercase text-center col-span-2 font-medium text-gray-800 dark:text-stone-800" colspan="2">
No recent downloads
<tr> </td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800"> </tr>
Silicon Cowboys {% endif %}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50">
<span class="p-1 bg-green-600 rounded-lg">
<span class="text-gray-50">Complete</span>
</span>
</td>
</tr>
</tbody> </tbody>
</table> </table>
</twig:Card> </twig:Card>
</div> </div>
<div class=""> <div class="">
<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">
<twig:Poster imdbId="" title="A Working Man" description="" image="https://image.tmdb.org/t/p/w500/xUkUZ8eOnrOnnJAfusZUqKYZiDu.jpg" year="" /> {% for movie in popular_movies %}
<twig:Poster imdbId="" title="In the Lost Lands" description="" image="https://image.tmdb.org/t/p/w500/iHf6bXPghWB6gT8kFkL1zo00x6X.jpg" year="" /> <twig:Poster imdbId=""
<twig:Poster imdbId="" title="A Minecraft Movie" description="" image="https://image.tmdb.org/t/p/w500/yFHHfHcUgGAxziP1C3lLt0q2T4s.jpg" year="" /> tmdbId="{{ movie.tmdbId }}"
<twig:Poster imdbId="" title="G20" description="" image="https://image.tmdb.org/t/p/w500/wv6oWAleCJZUk5htrGg413t3GCy.jpg" year="" /> title="{{ movie.title }}"
<twig:Poster imdbId="" title="Novocaine" description="" image="https://image.tmdb.org/t/p/w500/xmMHGz9dVRaMY6rRAlEX4W0Wdhm.jpg" year="" /> description="{{ movie.description }}"
<twig:Poster imdbId="" title="Gunslingers" description="" image="https://image.tmdb.org/t/p/w500/O7REXWPANWXvX2jhQydHjAq2DV.jpg" year="" /> image="{{ movie.poster }}"
year="{{ movie.year }}" />
{% endfor %}
</twig:Card> </twig:Card>
</div> </div>
</div> </div>

View File

@@ -1,63 +1,81 @@
<div id="filter" class="w-full p-4 flex flex-row gap-4 bg-stone-500 text-md text-gray-500 dark:text-gray-50 rounded-lg" <div id="filter" class="flex flex-col gap-4"
{{ stimulus_controller('result_filter') }} {{ stimulus_controller('result_filter') }}
{{ stimulus_action('result_filter', 'filter', 'change') }} {{ stimulus_action('result_filter', 'filter', 'change') }}
data-result-filter-media-type-value="{{ results.media.mediaType }}" data-result-filter-media-type-value="{{ results.media.mediaType }}"
data-result-filter-movie-results-outlet=".results" data-result-filter-movie-results-outlet=".results"
data-result-filter-tv-results-outlet=".results" data-result-filter-tv-results-outlet=".results"
> >
<label for="resolution"> <div class="w-full p-4 flex flex-row gap-4 bg-stone-500 text-md text-gray-500 dark:text-gray-50 rounded-lg">
Resolution <label for="resolution">
<select id="resolution" data-result-filter-target="resolution" class="px-1 py-0.5 bg-stone-100 text-gray-800 rounded-sm"> Resolution
<option {{ filter.resolution == "n/a" ? "selected" }} <select id="resolution" data-result-filter-target="resolution" class="px-1 py-0.5 bg-stone-100 text-gray-800 rounded-md">
value="">n/a</option> <option {{ filter.resolution == "n/a" ? "selected" }}
<option {{ filter.resolution == "720p" ? "selected" }} value="">n/a</option>
value="720p">720p</option> <option {{ filter.resolution == "720p" ? "selected" }}
<option {{ filter.resolution == "1080p" ? "selected" }} value="720p">720p</option>
value="1080p">1080p</option> <option {{ filter.resolution == "1080p" ? "selected" }}
<option {{ filter.resolution == "2160p" ? "selected" }} value="1080p">1080p</option>
value="2160p">2160p</option> <option {{ filter.resolution == "2160p" ? "selected" }}
</select> value="2160p">2160p</option>
</label>
<label for="codec">
Codec
<select id="codec" data-result-filter-target="codec" class="px-1 py-0.5 bg-stone-100 text-sm text-gray-800 rounded-sm">
<option {{ filter.codec == "n/a" ? "selected" }}
value="">n/a</option>
<option {{ filter.codec == "-" ? "selected" }}
value="-">-</option>
<option {{ filter.codec == "h264" ? "selected" }}
value="h264">h264</option>
<option {{ filter.codec == "h265" ? "selected" }}
value="h265">h265/HEVC</option>
</select>
</label>
<label for="language">
Language
<select id="language" data-result-filter-target="language" class="px-1 py-0.5 bg-stone-100 text-gray-800 rounded-sm">
<option selected value="{{ filter.language }}">{{ filter.language }}</option>
</select>
</label>
<label for="provider">
Provider
<select id="provider" data-result-filter-target="provider" class="px-1 py-0.5 bg-stone-100 text-gray-800 rounded-sm">
<option selected value="">n/a</option>
</select>
</label>
{% if results.media.mediaType == "tvshows" %}
<label for="season">
Season
<select id="season" name="season" value="1" data-result-filter-target="season" class="px-1 py-0.5 bg-stone-100 text-gray-800 rounded-sm">
<option selected value="1">1</option>
{% for season in range(2, results.media.episodes|length) %}
<option value="{{ season }}">{{ season }}</option>
{% endfor %}
</select> </select>
</label> </label>
{# <label for="episodeNumber">#} <label for="codec">
{# Episode#} Codec
{# <select id="episodeNumber" name="episodeNumber" data-result-filter-target="episode" class="px-1 py-0.5 bg-stone-100 text-gray-800 rounded-sm">#} <select id="codec" data-result-filter-target="codec" class="px-1 py-0.5 bg-stone-100 text-sm text-gray-800 rounded-md">
{# <option selected value="">n/a</option>#} <option {{ filter.codec == "n/a" ? "selected" }}
{# </select>#} value="">n/a</option>
{# </label>#} <option {{ filter.codec == "-" ? "selected" }}
value="-">-</option>
<option {{ filter.codec == "h264" ? "selected" }}
value="h264">h264</option>
<option {{ filter.codec == "h265" ? "selected" }}
value="h265">h265/HEVC</option>
</select>
</label>
<label for="language">
Language
<select id="language" data-result-filter-target="language" class="px-1 py-0.5 bg-stone-100 text-gray-800 rounded-md">
<option selected value="{{ filter.language }}">{{ filter.language }}</option>
</select>
</label>
<label for="provider">
Provider
<select id="provider" data-result-filter-target="provider" class="px-1 py-0.5 bg-stone-100 text-gray-800 rounded-md">
<option selected value="">n/a</option>
</select>
</label>
{% if results.media.mediaType == "tvshows" %}
<label for="season">
Season
<select id="season" name="season" value="1" data-result-filter-target="season" class="px-1 py-0.5 bg-stone-100 text-gray-800 rounded-md"
{{ stimulus_action('result_filter', 'uncheckSelectAllBtn', 'change') }}>
<option selected value="1">1</option>
{% for season in range(2, results.media.episodes|length) %}
<option value="{{ season }}">{{ season }}</option>
{% endfor %}
</select>
</label>
{# <label for="episodeNumber">#}
{# Episode#}
{# <select id="episodeNumber" name="episodeNumber" data-result-filter-target="episode" class="px-1 py-0.5 bg-stone-100 text-gray-800 rounded-sm">#}
{# <option selected value="">n/a</option>#}
{# </select>#}
{# </label>#}
{% endif %}
<span {{ stimulus_controller('loading_icon', {total: (results.media.mediaType == "tvshows") ? results.media.episodes[1]|length : 1, count: 0}) }}
class="loading-icon"
>
<twig:ux:icon name="codex:loader" height="20" width="20" data-loading-icon-target="icon" class="text-end" />
</span>
</div>
{% if results.media.mediaType == "tvshows" %}
<div class="flex flex-row gap-2 justify-end px-8">
<button class="px-1.5 py-1 bg-green-600 rounded-md text-sm">Download Selected</button>
<input type="checkbox" name="selectAll" id="selectAll"
{{ stimulus_target('result_filter', 'selectAll') }}
{{ stimulus_action('result_filter', 'selectAllEpisodes', 'change') }}
/>
</div>
{% endif %} {% endif %}
</div> </div>

View File

@@ -17,11 +17,6 @@
{{ results.media.description }} {{ results.media.description }}
</p> </p>
</div> </div>
<span {{ stimulus_controller('loading_icon', {total: (results.media.mediaType == "tvshows") ? results.media.episodes[1]|length : 1, count: 0}) }}
class="loading-icon"
>
<twig:ux:icon name="codex:loader" height="20" width="20" data-loading-icon-target="icon" />
</span>
</div> </div>
{{ include('search/partial/filter.html.twig') }} {{ include('search/partial/filter.html.twig') }}

View File

@@ -10,11 +10,16 @@
><span {{ stimulus_target('tv-results', 'count') }}>{{ results.results|length }}</span> results</small> ><span {{ stimulus_target('tv-results', 'count') }}>{{ results.results|length }}</span> results</small>
</span> </span>
</div> </div>
<div class="flex flex-col items-end hover:cursor-pointer" <div class="flex flex-col gap-4 justify-between">
{{ stimulus_action('tv-results', 'toggleList', 'click') }}> <input type="checkbox"
<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 32 32"> {{ stimulus_target('tv-results', 'episodeSelector') }}
<path fill="currentColor" d="m16 10l10 10l-1.4 1.4l-8.6-8.6l-8.6 8.6L6 20z"/> />
</svg> <div class="flex flex-col items-end hover:cursor-pointer"
{{ stimulus_action('tv-results', 'toggleList', 'click') }}>
<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 32 32">
<path fill="currentColor" d="m16 10l10 10l-1.4 1.4l-8.6-8.6l-8.6 8.6L6 20z"/>
</svg>
</div>
</div> </div>
</div> </div>
<div class="inline-block overflow-hidden rounded-lg"> <div class="inline-block overflow-hidden rounded-lg">