Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 242dd6fe99 | |||
| a2b20b14ea | |||
| 7ed117d19b | |||
| d4077d036e | |||
| de88a36545 | |||
| cb51483a00 | |||
| 9714cf1749 |
30
assets/controllers/clear_cache_controller.js
Normal file
30
assets/controllers/clear_cache_controller.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { Controller } from '@hotwired/stimulus';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The following line makes this controller "lazy": it won't be downloaded until needed
|
||||||
|
* See https://symfony.com/bundles/StimulusBundle/current/index.html#lazy-stimulus-controllers
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* stimulusFetch: 'lazy' */
|
||||||
|
export default class extends Controller {
|
||||||
|
initialize() {}
|
||||||
|
|
||||||
|
connect() {}
|
||||||
|
|
||||||
|
disconnect() {}
|
||||||
|
|
||||||
|
async clearAll() {
|
||||||
|
let response = await fetch('/api/torrentio/cache', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
type: 'torrentio',
|
||||||
|
mediaType: 'tvshows',
|
||||||
|
})
|
||||||
|
});
|
||||||
|
response = await response.json()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -90,7 +90,13 @@ export default class extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleList() {
|
toggleList() {
|
||||||
this.listTarget.classList.toggle('options-table');
|
// if (!this.isOpen) {
|
||||||
|
// this.toggleButtonTarget.classList.add('rotate-180');
|
||||||
|
// this.toggleButtonTarget.classList.remove('-rotate-180');
|
||||||
|
// } else {
|
||||||
|
// this.toggleButtonTarget.classList.add('-rotate-180');
|
||||||
|
// this.toggleButtonTarget.classList.remove('rotate-180');
|
||||||
|
// }
|
||||||
this.listTarget.classList.toggle('hidden');
|
this.listTarget.classList.toggle('hidden');
|
||||||
this.toggleButtonTarget.classList.toggle('rotate-90');
|
this.toggleButtonTarget.classList.toggle('rotate-90');
|
||||||
this.toggleButtonTarget.classList.toggle('-rotate-90');
|
this.toggleButtonTarget.classList.toggle('-rotate-90');
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 0 0"><path fill="currentColor" fill-rule="evenodd" d="M0 3.75A.75.75 0 0 1 .75 3h14.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 3.75M0 8a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8m.75 3.5a.75.75 0 0 0 0 1.5h14.5a.75.75 0 0 0 0-1.5z" clip-rule="evenodd"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 333 B |
@@ -25,6 +25,13 @@
|
|||||||
.alert-warning {
|
.alert-warning {
|
||||||
@apply bg-yellow-500/70 hover:bg-yellow-600 border-yellow-400 text-black
|
@apply bg-yellow-500/70 hover:bg-yellow-600 border-yellow-400 text-black
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.primary-btn {
|
||||||
|
@apply px-1 py-1 rounded-md bg-orange-500 self-end text-white w-16 ml-2 hover:bg-orange-600
|
||||||
|
}
|
||||||
|
.secondary-btn {
|
||||||
|
@apply px-1 py-1 rounded-md self-end w-16 hover:bg-stone-100
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Prevent scrolling while dialog is open */
|
/* Prevent scrolling while dialog is open */
|
||||||
@@ -63,17 +70,3 @@ dialog[data-dialog-target="dialog"][open] {
|
|||||||
dialog[data-dialog-target="dialog"][closing] {
|
dialog[data-dialog-target="dialog"][closing] {
|
||||||
animation: fade-out 200ms forwards;
|
animation: fade-out 200ms forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-table {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
.options-table {
|
|
||||||
display: inline-table;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,5 +15,11 @@ framework:
|
|||||||
#app: cache.adapter.apcu
|
#app: cache.adapter.apcu
|
||||||
|
|
||||||
# Namespaced pools use the above "app" backend by default
|
# Namespaced pools use the above "app" backend by default
|
||||||
#pools:
|
pools:
|
||||||
#my.dedicated.cache: null
|
torrentio.cache:
|
||||||
|
adapter: cache.app
|
||||||
|
tmdb.cache:
|
||||||
|
adapter: cache.app
|
||||||
|
default_lifetime: 2592000
|
||||||
|
page.cache:
|
||||||
|
adapter: cache.app
|
||||||
|
|||||||
@@ -29,3 +29,12 @@ controllersMonitor:
|
|||||||
type: attribute
|
type: attribute
|
||||||
defaults:
|
defaults:
|
||||||
schemes: ['https']
|
schemes: ['https']
|
||||||
|
|
||||||
|
controllersTorrentio:
|
||||||
|
resource:
|
||||||
|
path: ../src/Torrentio/Framework/Controller
|
||||||
|
namespace: App\Torrentio\Framework\Controller
|
||||||
|
type: attribute
|
||||||
|
defaults:
|
||||||
|
schemes: [ 'https' ]
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\Response;
|
|||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Contracts\Cache\CacheInterface;
|
use Symfony\Contracts\Cache\CacheInterface;
|
||||||
use Symfony\Contracts\Cache\ItemInterface;
|
use Symfony\Contracts\Cache\ItemInterface;
|
||||||
|
use Symfony\Contracts\Cache\TagAwareCacheInterface;
|
||||||
|
|
||||||
final class TorrentioController extends AbstractController
|
final class TorrentioController extends AbstractController
|
||||||
{
|
{
|
||||||
@@ -25,7 +26,7 @@ final class TorrentioController extends AbstractController
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[Route('/torrentio/movies/{tmdbId}/{imdbId}', name: 'app_torrentio_movies')]
|
#[Route('/torrentio/movies/{tmdbId}/{imdbId}', name: 'app_torrentio_movies')]
|
||||||
public function movieOptions(GetMovieOptionsInput $input, CacheInterface $cache): Response
|
public function movieOptions(GetMovieOptionsInput $input, TagAwareCacheInterface $pageCache): Response
|
||||||
{
|
{
|
||||||
$cacheId = sprintf(
|
$cacheId = sprintf(
|
||||||
"page.torrentio.movies.%s.%s",
|
"page.torrentio.movies.%s.%s",
|
||||||
@@ -33,17 +34,29 @@ final class TorrentioController extends AbstractController
|
|||||||
$input->imdbId
|
$input->imdbId
|
||||||
);
|
);
|
||||||
|
|
||||||
return $cache->get($cacheId, function (ItemInterface $item) use ($input) {
|
try {
|
||||||
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
return $pageCache->get($cacheId, function (ItemInterface $item) use ($input) {
|
||||||
$results = $this->getMovieOptionsHandler->handle($input->toCommand());
|
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
||||||
return $this->render('torrentio/movies.html.twig', [
|
$item->tag(['page', 'page.torrentio', 'page.torrentio.movies', "page.torrentio.movies.$input->tmdbId.$input->imdbId", 'torrentio', 'torrentio.movies', "torrentio.movies.$input->tmdbId.$input->imdbId"]);
|
||||||
'results' => $results,
|
$results = $this->getMovieOptionsHandler->handle($input->toCommand());
|
||||||
]);
|
return $this->render('torrentio/movies.html.twig', [
|
||||||
});
|
'results' => $results,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
} catch (TorrentioRateLimitException $exception) {
|
||||||
|
$this->broadcaster->alert('Warning', 'Torrentio has rate limited your requests. Please wait a few minutes before trying again.', 'warning');
|
||||||
|
return $this->render('bare.html.twig',
|
||||||
|
[],
|
||||||
|
new Response('Too many requests',
|
||||||
|
Response::HTTP_TOO_MANY_REQUESTS,
|
||||||
|
['Retry-After' => 4000]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/torrentio/tvshows/{tmdbId}/{imdbId}/{season?}/{episode?}', name: 'app_torrentio_tvshows')]
|
#[Route('/torrentio/tvshows/{tmdbId}/{imdbId}/{season?}/{episode?}', name: 'app_torrentio_tvshows')]
|
||||||
public function tvShowOptions(GetTvShowOptionsInput $input, CacheInterface $cache): Response
|
public function tvShowOptions(GetTvShowOptionsInput $input, TagAwareCacheInterface $pageCache): Response
|
||||||
{
|
{
|
||||||
$cacheId = sprintf(
|
$cacheId = sprintf(
|
||||||
"page.torrentio.tvshows.%s.%s.%s.%s",
|
"page.torrentio.tvshows.%s.%s.%s.%s",
|
||||||
@@ -54,8 +67,9 @@ final class TorrentioController extends AbstractController
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return $cache->get($cacheId, function (ItemInterface $item) use ($input) {
|
return $pageCache->get($cacheId, function (ItemInterface $item) use ($input) {
|
||||||
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
||||||
|
$item->tag(['page', 'page.torrentio', 'page.torrentio.tvshows', "page.torrentio.tvshows.$input->tmdbId.$input->imdbId", "page.torrentio.tvshows.$input->tmdbId.$input->imdbId.$input->season", "page.torrentio.tvshows.$input->tmdbId.$input->imdbId.$input->season.$input->episode", 'torrentio', 'torrentio.tvshows', "torrentio.tvshows.$input->tmdbId.$input->imdbId", "torrentio.tvshows.$input->tmdbId.$input->imdbId.$input->season", "torrentio.tvshows.$input->tmdbId.$input->imdbId.$input->season.$input->episode", $input->imdbId, $input->tmdbId]);
|
||||||
$results = $this->getTvShowOptionsHandler->handle($input->toCommand());
|
$results = $this->getTvShowOptionsHandler->handle($input->toCommand());
|
||||||
return $this->render('torrentio/tvshows.html.twig', [
|
return $this->render('torrentio/tvshows.html.twig', [
|
||||||
'results' => $results,
|
'results' => $results,
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class Tmdb
|
|||||||
const POSTER_IMG_PATH = "https://image.tmdb.org/t/p/w500";
|
const POSTER_IMG_PATH = "https://image.tmdb.org/t/p/w500";
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly CacheItemPoolInterface $cache,
|
private readonly CacheItemPoolInterface $tmdbCache,
|
||||||
private readonly EventDispatcherInterface $eventDispatcher,
|
private readonly EventDispatcherInterface $eventDispatcher,
|
||||||
#[Autowire(env: 'TMDB_API')] string $apiKey,
|
#[Autowire(env: 'TMDB_API')] string $apiKey,
|
||||||
) {
|
) {
|
||||||
@@ -78,7 +78,7 @@ class Tmdb
|
|||||||
$requestListener = new Psr6CachedRequestListener(
|
$requestListener = new Psr6CachedRequestListener(
|
||||||
$this->client->getHttpClient(),
|
$this->client->getHttpClient(),
|
||||||
$this->eventDispatcher,
|
$this->eventDispatcher,
|
||||||
$cache,
|
$tmdbCache,
|
||||||
$this->client->getHttpClient()->getPsr17StreamFactory(),
|
$this->client->getHttpClient()->getPsr17StreamFactory(),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
@@ -325,7 +325,7 @@ class Tmdb
|
|||||||
|
|
||||||
public function getImdbId(string $tmdbId, $mediaType)
|
public function getImdbId(string $tmdbId, $mediaType)
|
||||||
{
|
{
|
||||||
$externalIds = $this->cache->get("tmdb.externalIds.{$tmdbId}",
|
$externalIds = $this->tmdbCache->get("tmdb.externalIds.{$tmdbId}",
|
||||||
function (ItemInterface $item) use ($tmdbId, $mediaType) {
|
function (ItemInterface $item) use ($tmdbId, $mediaType) {
|
||||||
switch (MediaType::tryFrom($mediaType)->value) {
|
switch (MediaType::tryFrom($mediaType)->value) {
|
||||||
case MediaType::Movie->value:
|
case MediaType::Movie->value:
|
||||||
@@ -346,7 +346,7 @@ class Tmdb
|
|||||||
|
|
||||||
public function getImages($tmdbId, $mediaType)
|
public function getImages($tmdbId, $mediaType)
|
||||||
{
|
{
|
||||||
return $this->cache->get("tmdb.images.{$tmdbId}",
|
return $this->tmdbCache->get("tmdb.images.{$tmdbId}",
|
||||||
function (ItemInterface $item) use ($tmdbId, $mediaType) {
|
function (ItemInterface $item) use ($tmdbId, $mediaType) {
|
||||||
switch (MediaType::tryFrom($mediaType)->value) {
|
switch (MediaType::tryFrom($mediaType)->value) {
|
||||||
case MediaType::Movie->value:
|
case MediaType::Movie->value:
|
||||||
|
|||||||
18
src/Torrentio/Action/Command/DeleteCacheCommand.php
Normal file
18
src/Torrentio/Action/Command/DeleteCacheCommand.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Torrentio\Action\Command;
|
||||||
|
|
||||||
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
|
|
||||||
|
class DeleteCacheCommand implements CommandInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public ?string $type = null,
|
||||||
|
public ?string $mediaType = null,
|
||||||
|
public ?string $tmdbId = null,
|
||||||
|
public ?string $imdbId = null,
|
||||||
|
public ?int $season = null,
|
||||||
|
public ?int $episode = null,
|
||||||
|
public ?array $tags = null,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
61
src/Torrentio/Action/Handler/DeleteCacheHandler.php
Normal file
61
src/Torrentio/Action/Handler/DeleteCacheHandler.php
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Torrentio\Action\Handler;
|
||||||
|
|
||||||
|
use Aimeos\Map;
|
||||||
|
use App\Torrentio\Action\Command\DeleteCacheCommand;
|
||||||
|
use App\Torrentio\Action\Result\DeleteCacheResult;
|
||||||
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
|
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||||
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
|
use Symfony\Contracts\Cache\TagAwareCacheInterface;
|
||||||
|
|
||||||
|
/** @implements HandlerInterface<DeleteCacheCommand, DeleteCacheResult> */
|
||||||
|
class DeleteCacheHandler implements HandlerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly TagAwareCacheInterface $torrentioCache
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function handle(CommandInterface $command): ResultInterface
|
||||||
|
{
|
||||||
|
$input = Map::from((array) $command)
|
||||||
|
->filter(fn ($value, $key) => null !== $value && "" !== $value)
|
||||||
|
;
|
||||||
|
|
||||||
|
$cacheKey = null;
|
||||||
|
if ($input->has('type')) {
|
||||||
|
$cacheKey = $input->get('type');
|
||||||
|
|
||||||
|
if ($input->has('mediaType')) {
|
||||||
|
$cacheKey .= ".".$input->get('mediaType');
|
||||||
|
|
||||||
|
if ($input->has('tmdbId')) {
|
||||||
|
$cacheKey .= ".".$input->get('tmdbId');
|
||||||
|
|
||||||
|
if ($input->has('imdbId')) {
|
||||||
|
$cacheKey .= ".".$input->get('imdbId');
|
||||||
|
|
||||||
|
if ($input->has('season')) {
|
||||||
|
$cacheKey .= ".".$input->get('season');
|
||||||
|
|
||||||
|
if ($input->has('episode')) {
|
||||||
|
$cacheKey .= ".".$input->get('episode');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($cacheKey !== null) {
|
||||||
|
$this->torrentioCache->invalidateTags([$cacheKey]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input->has('tags')) {
|
||||||
|
$this->torrentioCache->invalidateTags($input->get('tags'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DeleteCacheResult($input, $cacheKey, $command->tags);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/Torrentio/Action/Input/DeleteCacheInput.php
Normal file
49
src/Torrentio/Action/Input/DeleteCacheInput.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Torrentio\Action\Input;
|
||||||
|
|
||||||
|
use App\Torrentio\Action\Command\DeleteCacheCommand;
|
||||||
|
use OneToMany\RichBundle\Attribute\SourceRequest;
|
||||||
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
|
use OneToMany\RichBundle\Contract\InputInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implements DeleteCacheInput<DeleteCacheCommand>
|
||||||
|
*/
|
||||||
|
class DeleteCacheInput implements InputInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
#[SourceRequest('type', nullify: true)]
|
||||||
|
public ?string $type,
|
||||||
|
|
||||||
|
#[SourceRequest('mediaType', nullify: true)]
|
||||||
|
public ?string $mediaType,
|
||||||
|
|
||||||
|
#[SourceRequest('tmdbId', nullify: true)]
|
||||||
|
public ?string $tmdbId,
|
||||||
|
|
||||||
|
#[SourceRequest('imdbId', nullify: true)]
|
||||||
|
public ?string $imdbId,
|
||||||
|
|
||||||
|
#[SourceRequest('season', nullify: true)]
|
||||||
|
public ?int $season,
|
||||||
|
|
||||||
|
#[SourceRequest('episode', nullify: true)]
|
||||||
|
public ?int $episode,
|
||||||
|
|
||||||
|
#[SourceRequest('tags', nullify: true)]
|
||||||
|
public ?array $tags,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function toCommand(): CommandInterface
|
||||||
|
{
|
||||||
|
return new DeleteCacheCommand(
|
||||||
|
type: $this->type,
|
||||||
|
mediaType: $this->mediaType,
|
||||||
|
tmdbId: $this->tmdbId,
|
||||||
|
imdbId: $this->imdbId,
|
||||||
|
season: $this->season,
|
||||||
|
episode: $this->episode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/Torrentio/Action/Result/DeleteCacheResult.php
Normal file
15
src/Torrentio/Action/Result/DeleteCacheResult.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Torrentio\Action\Result;
|
||||||
|
|
||||||
|
use Aimeos\Map;
|
||||||
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
|
|
||||||
|
class DeleteCacheResult implements ResultInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public Map $result,
|
||||||
|
public ?string $cacheKey = null,
|
||||||
|
public ?array $tags = null,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ class Torrentio
|
|||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
#[Autowire(env: 'REAL_DEBRID_KEY')] private string $realDebridKey,
|
#[Autowire(env: 'REAL_DEBRID_KEY')] private string $realDebridKey,
|
||||||
private TagAwareCacheInterface $cache,
|
private TagAwareCacheInterface $torrentioCache,
|
||||||
private LoggerInterface $logger,
|
private LoggerInterface $logger,
|
||||||
) {
|
) {
|
||||||
$this->searchUrl = str_replace('{realDebridKey}', $this->realDebridKey, $this->baseUrl);
|
$this->searchUrl = str_replace('{realDebridKey}', $this->realDebridKey, $this->baseUrl);
|
||||||
@@ -36,7 +36,7 @@ class Torrentio
|
|||||||
{
|
{
|
||||||
$cacheKey = "torrentio.{$imdbCode}";
|
$cacheKey = "torrentio.{$imdbCode}";
|
||||||
|
|
||||||
$results = $this->cache->get($cacheKey, function (ItemInterface $item) use ($imdbCode, $type) {
|
$results = $this->torrentioCache->get($cacheKey, function (ItemInterface $item) use ($imdbCode, $type) {
|
||||||
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
||||||
$item->tag(['torrentio', $type, $imdbCode]);
|
$item->tag(['torrentio', $type, $imdbCode]);
|
||||||
try {
|
try {
|
||||||
@@ -62,7 +62,7 @@ class Torrentio
|
|||||||
public function fetchEpisodeResults(string $imdbId, int $season, int $episode): array
|
public function fetchEpisodeResults(string $imdbId, int $season, int $episode): array
|
||||||
{
|
{
|
||||||
$cacheKey = "torrentio.$imdbId.$season.$episode";
|
$cacheKey = "torrentio.$imdbId.$season.$episode";
|
||||||
$results = $this->cache->get($cacheKey, function (ItemInterface $item) use ($imdbId, $season, $episode) {
|
$results = $this->torrentioCache->get($cacheKey, function (ItemInterface $item) use ($imdbId, $season, $episode) {
|
||||||
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
||||||
$item->tag(['torrentio', 'tvshows', 'torrentio.tvshows', $imdbId, "torrentio.$imdbId", "$imdbId.$season", "torrentio.$imdbId.$season", "$imdbId.$season.$episode", "torrentio.$imdbId.$season.$episode"]);
|
$item->tag(['torrentio', 'tvshows', 'torrentio.tvshows', $imdbId, "torrentio.$imdbId", "$imdbId.$season", "torrentio.$imdbId.$season", "$imdbId.$season.$episode", "torrentio.$imdbId.$season.$episode"]);
|
||||||
try {
|
try {
|
||||||
|
|||||||
21
src/Torrentio/Framework/Controller/ApiController.php
Normal file
21
src/Torrentio/Framework/Controller/ApiController.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Torrentio\Framework\Controller;
|
||||||
|
|
||||||
|
use App\Torrentio\Action\Handler\DeleteCacheHandler;
|
||||||
|
use App\Torrentio\Action\Input\DeleteCacheInput;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
class ApiController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/api/torrentio/cache', name: 'api.torrentio.cache', methods: ['POST'])]
|
||||||
|
public function deleteCache(
|
||||||
|
DeleteCacheInput $deleteCacheInput,
|
||||||
|
DeleteCacheHandler $deleteCacheHandler,
|
||||||
|
): Response {
|
||||||
|
$result = $deleteCacheHandler->handle($deleteCacheInput->toCommand());
|
||||||
|
return $this->json($result, Response::HTTP_OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<title>{% block title %}Welcome!{% endblock %}</title>
|
<title>{% block title %}Welcome!{% 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>">
|
<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 %}
|
{% block stylesheets %}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<title>{% block title %}Welcome!{% endblock %}</title>
|
<title>{% block title %}Welcome!{% 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>">
|
<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 %}
|
{% block stylesheets %}
|
||||||
@@ -14,13 +13,18 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body class="flex flex-col bg-stone-700">
|
<body class="flex flex-col bg-stone-700">
|
||||||
<div class="grid md:grid-cols-6">
|
<div class="grid grid-cols-6">
|
||||||
<div class="hidden md:block md:col-span-1 md:h-screen">
|
<div class="col-span-1 h-screen">
|
||||||
<twig:NavBar />
|
<twig:NavBar />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-6 md:col-span-5 h-screen overflow-y-scroll">
|
<div class="col-span-5 h-screen overflow-y-scroll">
|
||||||
<twig:Header />
|
<twig:Header />
|
||||||
<h2 class="px-4 my-2 text-3xl font-bold text-gray-50">{% block h2 %}{% endblock %}</h2>
|
<div class="px-4 mt-3 flex flex-row justify-between">
|
||||||
|
<h2 class="m-2 text-3xl font-bold text-gray-50">{% block h2 %}{% endblock %}</h2>
|
||||||
|
<div class="flex flex-row gap-1 align-end justify-end items-end">
|
||||||
|
{% block action_buttons %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% block body %}{% endblock %}
|
{% block body %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
data-result-filter-tv-results-outlet=".results"
|
data-result-filter-tv-results-outlet=".results"
|
||||||
data-result-filter-tv-episode-list-outlet=".episode-list"
|
data-result-filter-tv-episode-list-outlet=".episode-list"
|
||||||
>
|
>
|
||||||
<div class="w-full p-4 flex flex-col md:flex-row gap-4 bg-stone-500 text-md text-gray-500 dark:text-gray-50 rounded-lg">
|
<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">
|
||||||
<label for="resolution">
|
<label for="resolution">
|
||||||
Resolution
|
Resolution
|
||||||
<select id="resolution"
|
<select id="resolution"
|
||||||
|
|||||||
@@ -5,17 +5,12 @@
|
|||||||
<div class="md:flex md:items-center md:gap-12">
|
<div class="md:flex md:items-center md:gap-12">
|
||||||
<nav aria-label="Global" class="md:block">
|
<nav aria-label="Global" class="md:block">
|
||||||
<ul class="flex items-center gap-6 text-sm">
|
<ul class="flex items-center gap-6 text-sm">
|
||||||
<li class="hidden">
|
<li><twig:ux:icon name="fluent:alert-12-regular" width="30px" class="text-gray-950 bg-orange-500 rounded-full p-2"/></li>
|
||||||
<twig:ux:icon name="fluent:alert-12-regular" width="30px" class="text-gray-950 bg-orange-500 rounded-full p-2"/>
|
<li>
|
||||||
</li>
|
|
||||||
<li class="hidden md:block">
|
|
||||||
<a href="{{ path('app_logout') }}">
|
<a href="{{ path('app_logout') }}">
|
||||||
<twig:ux:icon name="material-symbols:logout" width="25px" class="text-orange-500" />
|
<twig:ux:icon name="material-symbols:logout" width="25px" class="text-orange-500" />
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="md:hidden">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="text-orange-500 ml-4" width="25px" height="25px" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M0 3.75A.75.75 0 0 1 .75 3h14.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 3.75M0 8a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8m.75 3.5a.75.75 0 0 0 0 1.5h14.5a.75.75 0 0 0 0-1.5z" clip-rule="evenodd"/></svg>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
{% if show_cancel is defined or show_submit is defined %}
|
{% if show_cancel is defined or show_submit is defined %}
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
{% if show_cancel is defined %}
|
{% if show_cancel is defined %}
|
||||||
<button type="button" data-action="dialog#close" class="px-1 py-1 rounded-md self-end w-16 hover:bg-stone-100" autofocus>
|
<button type="button" data-action="dialog#close" class="secondary-btn" autofocus>
|
||||||
{{ cancel_text|default('Cancel') }}
|
{{ cancel_text|default('Cancel') }}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if show_submit is defined %}
|
{% if show_submit is defined %}
|
||||||
<button type="button" {{ submit_action|raw }} class="px-1 py-1 rounded-md bg-orange-500 self-end text-white w-16 ml-2 hover:bg-orange-600" autofocus>
|
<button type="button" {{ submit_action|raw }} class="primary-btn" autofocus>
|
||||||
{{ submit_text|default('Submit') }}
|
{{ submit_text|default('Submit') }}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -22,5 +22,5 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
<button type="button" data-action="dialog#open">{{ button_text|raw }}</button>
|
<button type="button" data-action="dialog#open" class="{{ button_class|default('') }}">{{ button_text|raw }}</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -3,12 +3,12 @@
|
|||||||
mediaType: mediaType,
|
mediaType: mediaType,
|
||||||
imdbId: imdbId
|
imdbId: imdbId
|
||||||
}) }}">
|
}) }}">
|
||||||
<img src="{{ image }}" class="w-full md: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
|
imdbId: imdbId
|
||||||
}) }}">
|
}) }}">
|
||||||
<h3 class="text-center text-white text-xl md:text-base md:max-w-[16ch]">{{ title }}</h3>
|
<h3 class="text-center text-gray-50 max-w-[16ch] text-extrabold">{{ title }}</h3>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<div{{ attributes }}>
|
<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">
|
<div class="p-4 flex 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" %}
|
{% 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-24 rounded-lg" src="{{ poster }}" />
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="w-full md:w-32 h-[144px] rounded-lg bg-gray-700 flex items-center justify-center">
|
<div class="w-32 h-[144px] rounded-lg bg-gray-700 flex items-center justify-center">
|
||||||
<twig:ux:icon width="16" name="hugeicons:loading-01" />
|
<twig:ux:icon width="16" name="hugeicons:loading-01" />
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -12,11 +12,11 @@
|
|||||||
<h3 class="mb-4 text-xl font-medium leading-tight font-bold text-gray-50">
|
<h3 class="mb-4 text-xl font-medium leading-tight font-bold text-gray-50">
|
||||||
{{ title }} - {{ year }}
|
{{ title }} - {{ year }}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="hidden md:text-gray-50">
|
<p class="text-gray-50">
|
||||||
{{ description }}
|
{{ description }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<a class="h-9 rounded-md py-1 px-2 bg-green-600 text-gray-50 text-center"
|
<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, imdbId: imdbId}) }}"
|
||||||
>choose</a>
|
>choose</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,12 +15,12 @@
|
|||||||
active: 'true',
|
active: 'true',
|
||||||
}) }}
|
}) }}
|
||||||
>
|
>
|
||||||
<div class="p-4 md:p-6 flex flex-col gap-6 bg-orange-500 bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-60 rounded-md">
|
<div class="p-6 flex flex-col gap-6 bg-orange-500 bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-60 rounded-md">
|
||||||
<div class="flex flex-col md:flex-row gap-4">
|
<div class="flex flex-row gap-4">
|
||||||
{% if episode['poster'] != null %}
|
{% if episode['poster'] != null %}
|
||||||
<img class="w-full md:w-64 rounded-lg" src="{{ episode['poster'] }}" />
|
<img class="w-64 rounded-lg" src="{{ episode['poster'] }}" />
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="w-full md:w-64 min-w-64 sticky h-[144px] rounded-lg bg-gray-700 flex items-center justify-center">
|
<div class="w-64 min-w-64 sticky h-[144px] rounded-lg bg-gray-700 flex items-center justify-center">
|
||||||
<twig:ux:icon width="32" name="hugeicons:loading-01" />
|
<twig:ux:icon width="32" name="hugeicons:loading-01" />
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
<twig:DownloadList type="active" :isWidget="false" :perPage="10"></twig:DownloadList>
|
<twig:DownloadList type="active" :isWidget="false" :perPage="10"></twig:DownloadList>
|
||||||
</twig:Card>
|
</twig:Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<twig:Card title="Recent Downloads">
|
<twig:Card title="Recent Downloads">
|
||||||
<twig:DownloadList type="complete" :isWidget="false" :perPage="10"></twig:DownloadList>
|
<twig:DownloadList type="complete" :isWidget="false" :perPage="10"></twig:DownloadList>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
{% block title %}Dashboard — Torsearch{% endblock %}
|
{% block title %}Dashboard — Torsearch{% endblock %}
|
||||||
|
{% block h2 %}Dashboard{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="p-4 flex flex-col grow gap-4 z-30">
|
<div class="p-4 flex flex-col grow gap-4 z-30">
|
||||||
<h2 class="mb-2 text-3xl font-bold text-gray-50">Dashboard</h2>
|
<div class="flex flex-row gap-4">
|
||||||
<div class="flex flex-col md:flex-row gap-4">
|
|
||||||
<twig:Card title="Active Downloads" class="w-full">
|
<twig:Card title="Active Downloads" class="w-full">
|
||||||
<twig:DownloadList :type="'active'" />
|
<twig:DownloadList :type="'active'" />
|
||||||
</twig:Card>
|
</twig:Card>
|
||||||
@@ -14,13 +14,13 @@
|
|||||||
<twig:DownloadList :type="'complete'" />
|
<twig:DownloadList :type="'complete'" />
|
||||||
</twig:Card>
|
</twig:Card>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col md:flex-row gap-4">
|
<div class="flex flex-row gap-4">
|
||||||
<twig:Card title="Monitors" class="w-full">
|
<twig:Card title="Monitors" class="w-full">
|
||||||
<twig:MonitorList :type="'active'" :isWidget="true" />
|
<twig:MonitorList :type="'active'" :isWidget="true" />
|
||||||
</twig:Card>
|
</twig:Card>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<twig:Card title="Popular Movies" contentClass="flex flex-col gap-4 md:flex-row md: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="{{ movie.imdbId }}"
|
||||||
tmdbId="{{ movie.tmdbId }}"
|
tmdbId="{{ movie.tmdbId }}"
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
/>
|
/>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</twig:Card>
|
</twig:Card>
|
||||||
<twig:Card title="Popular TV Shows" contentClass="flex flex-col md: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="{{ movie.imdbId }}"
|
||||||
tmdbId="{{ movie.tmdbId }}"
|
tmdbId="{{ movie.tmdbId }}"
|
||||||
|
|||||||
@@ -4,16 +4,14 @@
|
|||||||
{% block h2 %}Monitors{% endblock %}
|
{% block h2 %}Monitors{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="flex flex-row">
|
<div class="p-4">
|
||||||
|
<twig:Card title="Active Monitors" class="w-full">
|
||||||
<div class="p-2 flex flex-col gap-4">
|
<twig:MonitorList :type="'active'" :isWidget="false" :perPage="10"></twig:MonitorList>
|
||||||
<twig:Card title="Active Monitors">
|
</twig:Card>
|
||||||
<twig:MonitorList :type="'active'" :isWidget="false" :perPage="10"></twig:MonitorList>
|
</div>
|
||||||
</twig:Card>
|
<div class="p-4">
|
||||||
<twig:Card title="Complete Monitors">
|
<twig:Card title="Complete Monitors" class="w-full">
|
||||||
<twig:MonitorList :type="'complete'" :isWidget="false" :perPage="10"></twig:MonitorList>
|
<twig:MonitorList :type="'complete'" :isWidget="false" :perPage="10"></twig:MonitorList>
|
||||||
</twig:Card>
|
</twig:Card>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
<h2 class="mb-2 text-3xl font-bold text-gray-50">Media Results</h2>
|
<h2 class="mb-2 text-3xl font-bold text-gray-50">Media Results</h2>
|
||||||
<div class="flex flex-row w-full gap-2">
|
<div class="flex flex-row w-full gap-2">
|
||||||
<twig:Card title="" contentClass="flex flex-col gap-4 justify-between w-full text-gray-50">
|
<twig:Card title="" contentClass="flex flex-col gap-4 justify-between w-full text-gray-50">
|
||||||
<div class="p-2 md:p-4 flex flex-col md:flex-row gap-6">
|
<div class="p-4 flex flex-row gap-6">
|
||||||
{% if results.media.poster != null %}
|
{% if results.media.poster != null %}
|
||||||
<img class="w-full md:w-40 rounded-lg" src="{{ results.media.poster }}" />
|
<img class="w-40 rounded-lg" src="{{ results.media.poster }}" />
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="w-full md:w-40 h-[144px] rounded-lg bg-gray-700 flex items-center justify-center">
|
<div class="w-40 h-[144px] rounded-lg bg-gray-700 flex items-center justify-center">
|
||||||
<twig:ux:icon width="24" name="hugeicons:loading-01" />
|
<twig:ux:icon width="24" name="hugeicons:loading-01" />
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -127,20 +127,4 @@
|
|||||||
</twig:Card>
|
</twig:Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<style>
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
|
||||||
thead tr:not(:first-child) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
td:not(:last-child) {
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,62 +1,59 @@
|
|||||||
<table class="w-full max-w-[75vw] text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 flex-row flex-no-wrap {{ results.media.mediaType == "tvshows" ? "hidden" : "options-table" }}"
|
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 {{ results.media.mediaType == "tvshows" ? "hidden" }}"
|
||||||
{{ stimulus_target(controller, "list") }}
|
{{ stimulus_target(controller, "list") }}
|
||||||
>
|
>
|
||||||
<thead class="text-xs text-gray-700 uppercase dark:text-gray-400">
|
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||||
{% for result in results.results %}
|
<tr class="dark:bg-stone-600 overflow-hidden">
|
||||||
<tr class="dark:bg-stone-600 overflow-hidden flex flex-col md:flex-col flex-no wrap md:table-row border-b border-gray-500">
|
|
||||||
<th scope="col"
|
<th scope="col"
|
||||||
class="px-4 py-4 leading-[20px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
Size
|
Size
|
||||||
</th>
|
</th>
|
||||||
<th scope="col"
|
<th scope="col"
|
||||||
class="px-4 py-4 leading-[20px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
Resolution
|
Resolution
|
||||||
</th>
|
</th>
|
||||||
<th scope="col"
|
<th scope="col"
|
||||||
class="px-4 py-4 leading-[20px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
Codec
|
Codec
|
||||||
</th>
|
</th>
|
||||||
<th scope="col"
|
<th scope="col"
|
||||||
class="px-4 py-4 leading-[20px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
Seeders
|
Seeders
|
||||||
</th>
|
</th>
|
||||||
<th scope="col"
|
<th scope="col"
|
||||||
class="px-4 py-4 leading-[20px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
Provider
|
Provider
|
||||||
</th>
|
</th>
|
||||||
<th scope="col"
|
<th scope="col"
|
||||||
class="px-4 py-4 leading-[20px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
Language
|
Language
|
||||||
</th>
|
</th>
|
||||||
<th scope="col"
|
<th scope="col"
|
||||||
class="px-4 py-4 leading-[32px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
Actions
|
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="flex-1 sm:flex-none">
|
<tbody>
|
||||||
{% for result in results.results %}
|
{% for result in results.results %}
|
||||||
<tr class="bg-white dark:bg-slate-700 flex flex-col flex-no wrap sm:table-row border-b border-gray-500" data-provider="{{ result.provider }}" data-languages="{{ result.languages|json_encode }}" {% if "tvshows" == results.media.mediaType %} data-season="{{ results.season }}"{% endif %}>
|
<tr class="bg-white border-b dark:bg-slate-700 dark:border-gray-600 border-gray-200" data-provider="{{ result.provider }}" data-languages="{{ result.languages|json_encode }}" {% if "tvshows" == results.media.mediaType %} data-season="{{ results.season }}"{% endif %}>
|
||||||
<td id="size" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
|
<td id="size" class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
|
||||||
{{ result.size }}
|
{{ result.size }}
|
||||||
</td>
|
</td>
|
||||||
<td id="resolution" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
|
<td id="resolution" class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
|
||||||
{{ result.resolution }}
|
{{ result.resolution }}
|
||||||
</td>
|
</td>
|
||||||
<td id="codec" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
|
<td id="codec" class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
|
||||||
{{ result.codec }}
|
{{ result.codec }}
|
||||||
</td>
|
</td>
|
||||||
<td id="seeders" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
|
<td id="seeders" class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
|
||||||
{{ result.seeders }}
|
{{ result.seeders }}
|
||||||
</td>
|
</td>
|
||||||
<td id="provider" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50 " data-provider="{{ result.provider }}">
|
<td id="provider" class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50" data-provider="{{ result.provider }}">
|
||||||
{{ result.provider }}
|
{{ result.provider }}
|
||||||
</td>
|
</td>
|
||||||
<td id="language" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50 overflow-scroll" data-languages="{{ result.languages|json_encode }}">
|
<td id="language" class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50" data-languages="{{ result.languages|json_encode }}">
|
||||||
{{ result.languageFlags|raw }}
|
{{ result.languageFlags|raw }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50 flex flex-row gap-2 items-center justify-start mb:justify-end">
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50 flex flex-row gap-2 items-center justify-end">
|
||||||
<button class="download-btn p-1.5 bg-green-600 rounded-md text-gray-50"
|
<button class="download-btn p-1.5 bg-green-600 rounded-md text-gray-50"
|
||||||
{{ stimulus_controller('download_button', {
|
{{ stimulus_controller('download_button', {
|
||||||
url: result.url,
|
url: result.url,
|
||||||
|
|||||||
@@ -2,6 +2,18 @@
|
|||||||
{% block title %}Preferences{% endblock %}
|
{% block title %}Preferences{% endblock %}
|
||||||
{% block h2 %}Preferences{% endblock %}
|
{% block h2 %}Preferences{% endblock %}
|
||||||
|
|
||||||
|
{% block action_buttons %}
|
||||||
|
<div {{ stimulus_controller('clear_cache') }}>
|
||||||
|
<twig:Modal heading="Hold on a sec!" button_text="Clear Cache" cancel_text="Nope" submit_text="Yep" show_cancel show_submit
|
||||||
|
button_class="px-1.5 py-1 my-2 text-white text-sm bg-blue-950 hover:bg-black/80 border-2 border-blue-500/90 rounded-md inline-block"
|
||||||
|
submit_action="{{ stimulus_action('clear_cache', 'clearAll', 'click') }}"
|
||||||
|
>
|
||||||
|
This will clear the TMDB, Torrentio, and application cache. Clearing the cache is safe, but may lead to
|
||||||
|
slower page loads and rate limits by Torrentio. Would you like to proceed?
|
||||||
|
</twig:Modal>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="p-4 flex flex-row gap-2">
|
<div class="p-4 flex flex-row gap-2">
|
||||||
<twig:Card title="Media Preferences" class="w-full">
|
<twig:Card title="Media Preferences" class="w-full">
|
||||||
|
|||||||
Reference in New Issue
Block a user