Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa31701ac8 | |||
| 781e4dcd23 | |||
| b5cd240fbd | |||
| ce5bc525dd | |||
| 63850e48fd | |||
| f9a284cb67 | |||
| 228d320edc | |||
| 6858d2d722 | |||
| 8cd004db4a | |||
| 6cc8985c4d | |||
| 9bfd92a011 | |||
| ac6276f444 | |||
| 6d4bbf2e72 | |||
| 8004de1dc8 | |||
| 2d9a2a1f14 | |||
| b6bc1645b4 | |||
| 16023f1a26 | |||
| 725034dd4e | |||
| 234aeab120 |
@@ -1,4 +1,4 @@
|
|||||||
FROM dunglas/frankenphp
|
FROM dunglas/frankenphp:php8.4-alpine
|
||||||
|
|
||||||
ENV SERVER_NAME=":80"
|
ENV SERVER_NAME=":80"
|
||||||
ENV CADDY_GLOBAL_OPTIONS="auto_https off"
|
ENV CADDY_GLOBAL_OPTIONS="auto_https off"
|
||||||
@@ -11,6 +11,8 @@ RUN install-php-extensions \
|
|||||||
zip \
|
zip \
|
||||||
opcache
|
opcache
|
||||||
|
|
||||||
|
RUN apk add --no-cache wget
|
||||||
|
|
||||||
HEALTHCHECK --interval=3s --timeout=3s --retries=10 CMD [ "php", "/app/bin/console", "startup:status" ]
|
HEALTHCHECK --interval=3s --timeout=3s --retries=10 CMD [ "php", "/app/bin/console", "startup:status" ]
|
||||||
|
|
||||||
COPY docker/app/Caddyfile /etc/frankenphp/Caddyfile
|
COPY docker/app/Caddyfile /etc/frankenphp/Caddyfile
|
||||||
|
|||||||
5
assets/bootstrap.js
vendored
5
assets/bootstrap.js
vendored
@@ -1,5 +1,8 @@
|
|||||||
import { startStimulusApp } from '@symfony/stimulus-bundle';
|
import { startStimulusApp } from '@symfony/stimulus-bundle';
|
||||||
|
import Popover from '@stimulus-components/popover'
|
||||||
|
import Dialog from '@stimulus-components/dialog'
|
||||||
|
|
||||||
const app = startStimulusApp();
|
const app = startStimulusApp();
|
||||||
// register any custom, 3rd party controllers here
|
// register any custom, 3rd party controllers here
|
||||||
// app.register('some_controller_name', SomeImportedController);
|
app.register('popover', Popover);
|
||||||
|
app.register('dialog', Dialog);
|
||||||
|
|||||||
1
assets/icons/line-md/circle-twotone.svg
Normal file
1
assets/icons/line-md/circle-twotone.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" fill-opacity="0" stroke="currentColor" stroke-dasharray="64" stroke-dashoffset="64" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12c0 -4.97 4.03 -9 9 -9c4.97 0 9 4.03 9 9c0 4.97 -4.03 9 -9 9c-4.97 0 -9 -4.03 -9 -9Z"><animate fill="freeze" attributeName="fill-opacity" begin="0.6s" dur="0.15s" values="0;0.3"/><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.6s" values="64;0"/></path></svg>
|
||||||
|
After Width: | Height: | Size: 517 B |
@@ -11,3 +11,40 @@
|
|||||||
font-size: var(--text-xl);
|
font-size: var(--text-xl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Prevent scrolling while dialog is open */
|
||||||
|
body:has(dialog[data-dialog-target="dialog"][open]) {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Customize the dialog backdrop */
|
||||||
|
dialog {
|
||||||
|
box-shadow: 0 0 0 100vw rgb(0 0 0 / 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-out {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add animations */
|
||||||
|
dialog[data-dialog-target="dialog"][open] {
|
||||||
|
animation: fade-in 200ms forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog[data-dialog-target="dialog"][closing] {
|
||||||
|
animation: fade-out 200ms forwards;
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- $PWD:/app
|
- $PWD:/app
|
||||||
|
- $PWD/var/download:/var/download
|
||||||
- mercure_data:/data
|
- mercure_data:/data
|
||||||
- mercure_config:/config
|
- mercure_config:/config
|
||||||
tty: true
|
tty: true
|
||||||
@@ -34,8 +35,9 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- $PWD:/app
|
- $PWD:/app
|
||||||
|
- $PWD/var/download:/var/download
|
||||||
tty: true
|
tty: true
|
||||||
command: php /app/bin/console messenger:consume async -vv --time-limit=3600 limit=10
|
command: php /app/bin/console messenger:consume async -vv --time-limit=3600 --limit=10
|
||||||
|
|
||||||
|
|
||||||
scheduler:
|
scheduler:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
"1tomany/rich-bundle": "^1.8",
|
"1tomany/rich-bundle": "^1.8",
|
||||||
"aimeos/map": "^3.12",
|
"aimeos/map": "^3.12",
|
||||||
|
"chrisullyott/php-filesize": "^4.2",
|
||||||
"doctrine/dbal": "^3",
|
"doctrine/dbal": "^3",
|
||||||
"doctrine/doctrine-bundle": "^2.14",
|
"doctrine/doctrine-bundle": "^2.14",
|
||||||
"doctrine/doctrine-fixtures-bundle": "^4.1",
|
"doctrine/doctrine-fixtures-bundle": "^4.1",
|
||||||
|
|||||||
51
composer.lock
generated
51
composer.lock
generated
@@ -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": "63610a631352051ae8327669536efcef",
|
"content-hash": "c519733202d45f8fb3a4f5b8e7dfb95b",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "1tomany/rich-bundle",
|
"name": "1tomany/rich-bundle",
|
||||||
@@ -186,6 +186,55 @@
|
|||||||
],
|
],
|
||||||
"time": "2023-12-11T17:09:12+00:00"
|
"time": "2023-12-11T17:09:12+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "chrisullyott/php-filesize",
|
||||||
|
"version": "v4.2.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/chrisullyott/php-filesize.git",
|
||||||
|
"reference": "967ea3365c00974b50b608ffc045a267ab92ef43"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/chrisullyott/php-filesize/zipball/967ea3365c00974b50b608ffc045a267ab92ef43",
|
||||||
|
"reference": "967ea3365c00974b50b608ffc045a267ab92ef43",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^7"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"ChrisUllyott\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Chris Ullyott",
|
||||||
|
"email": "contact@chrisullyott.com",
|
||||||
|
"homepage": "http://www.chrisullyott.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Easily calculate file sizes and convert between units.",
|
||||||
|
"homepage": "https://github.com/chrisullyott/php-filesize",
|
||||||
|
"keywords": [
|
||||||
|
"php",
|
||||||
|
"size-calculation"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/chrisullyott/php-filesize/issues",
|
||||||
|
"source": "https://github.com/chrisullyott/php-filesize"
|
||||||
|
},
|
||||||
|
"time": "2021-10-17T22:52:23+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "composer/semver",
|
"name": "composer/semver",
|
||||||
"version": "3.4.3",
|
"version": "3.4.3",
|
||||||
|
|||||||
@@ -5,9 +5,10 @@
|
|||||||
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||||
parameters:
|
parameters:
|
||||||
# Media
|
# Media
|
||||||
|
media.base_path: '/var/download'
|
||||||
media.default_movies_dir: movies
|
media.default_movies_dir: movies
|
||||||
media.default_tvshows_dir: tvshows
|
media.default_tvshows_dir: tvshows
|
||||||
media.movies_path: '%env(default:media.default_movies_dir:MOVIES_PATH)%'
|
media.movies_path: '/var/download/%env(default:media.default_movies_dir:MOVIES_PATH)%'
|
||||||
media.tvshows_path: '/var/download/%env(default:media.default_tvshows_dir:TVSHOWS_PATH)%'
|
media.tvshows_path: '/var/download/%env(default:media.default_tvshows_dir:TVSHOWS_PATH)%'
|
||||||
|
|
||||||
# Mercure
|
# Mercure
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ RUN install-php-extensions \
|
|||||||
zip \
|
zip \
|
||||||
opcache
|
opcache
|
||||||
|
|
||||||
|
RUN apk add --no-cache wget
|
||||||
|
|
||||||
COPY . /app
|
COPY . /app
|
||||||
|
|
||||||
ENTRYPOINT [ "php", "/app/bin/console", "messenger:consume", "async" ]
|
ENTRYPOINT [ "php", "/app/bin/console", "messenger:consume", "async" ]
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
frankenphp {
|
frankenphp {
|
||||||
{$FRANKENPHP_CONFIG}
|
{$FRANKENPHP_CONFIG}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,4 +28,10 @@ return [
|
|||||||
'@hotwired/turbo' => [
|
'@hotwired/turbo' => [
|
||||||
'version' => '7.3.0',
|
'version' => '7.3.0',
|
||||||
],
|
],
|
||||||
|
'@stimulus-components/popover' => [
|
||||||
|
'version' => '7.0.0',
|
||||||
|
],
|
||||||
|
'@stimulus-components/dialog' => [
|
||||||
|
'version' => '1.0.1',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -33,11 +33,12 @@ final class SearchController extends AbstractController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/result/{mediaType}/{tmdbId}', name: 'app_search_result')]
|
#[Route('/result/{mediaType}/{imdbId}', 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', [
|
||||||
|
|||||||
@@ -52,13 +52,13 @@ final class TorrentioController extends AbstractController
|
|||||||
$input->episode,
|
$input->episode,
|
||||||
);
|
);
|
||||||
|
|
||||||
return $cache->get($cacheId, function (ItemInterface $item) use ($input) {
|
// return $cache->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));
|
||||||
$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,
|
||||||
]);
|
]);
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/torrentio/tvshows/clear/{tmdbId}/{imdbId}/{season?}/{episode?}', name: 'app_clear_torrentio_tvshows')]
|
#[Route('/torrentio/tvshows/clear/{tmdbId}/{imdbId}/{season?}/{episode?}', name: 'app_clear_torrentio_tvshows')]
|
||||||
|
|||||||
@@ -3,8 +3,11 @@
|
|||||||
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)]
|
||||||
@@ -162,4 +165,16 @@ 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,27 +25,31 @@ class DownloadRepository extends ServiceEntityRepository
|
|||||||
$this->paginator = $paginator;
|
$this->paginator = $paginator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCompletePaginated(UserInterface $user, int $pageNumber = 1, int $perPage = 10): Paginator
|
public function getCompletePaginated(UserInterface $user, int $pageNumber = 1, int $perPage = 10, string $term = ""): Paginator
|
||||||
{
|
{
|
||||||
$query = $this->createQueryBuilder('d')
|
$query = $this->createQueryBuilder('d')
|
||||||
->andWhere('d.status IN (:statuses)')
|
->andWhere('d.status IN (:statuses)')
|
||||||
->andWhere('d.user = :user')
|
->andWhere('d.user = :user')
|
||||||
|
->andWhere('(d.title LIKE :term OR d.filename LIKE :term OR d.imdbId LIKE :term OR d.status LIKE :term OR d.mediaType LIKE :term)')
|
||||||
->orderBy('d.id', 'DESC')
|
->orderBy('d.id', 'DESC')
|
||||||
->setParameter('statuses', ['Complete'])
|
->setParameter('statuses', ['Complete'])
|
||||||
->setParameter('user', $user)
|
->setParameter('user', $user)
|
||||||
|
->setParameter('term', '%' . $term . '%')
|
||||||
->getQuery();
|
->getQuery();
|
||||||
|
|
||||||
return $this->paginator->paginate($query, $pageNumber, $perPage);
|
return $this->paginator->paginate($query, $pageNumber, $perPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getActivePaginated(UserInterface $user, int $pageNumber = 1, int $perPage = 5): Paginator
|
public function getActivePaginated(UserInterface $user, int $pageNumber = 1, int $perPage = 5, string $term = ""): Paginator
|
||||||
{
|
{
|
||||||
$query = $this->createQueryBuilder('d')
|
$query = $this->createQueryBuilder('d')
|
||||||
->andWhere('d.status IN (:statuses)')
|
->andWhere('d.status IN (:statuses)')
|
||||||
->andWhere('d.user = :user')
|
->andWhere('d.user = :user')
|
||||||
|
->andWhere('(d.title LIKE :term OR d.filename LIKE :term OR d.imdbId LIKE :term OR d.status LIKE :term OR d.mediaType LIKE :term)')
|
||||||
->orderBy('d.id', 'ASC')
|
->orderBy('d.id', 'ASC')
|
||||||
->setParameter('statuses', ['New', 'In Progress'])
|
->setParameter('statuses', ['New', 'In Progress'])
|
||||||
->setParameter('user', $user)
|
->setParameter('user', $user)
|
||||||
|
->setParameter('term', '%' . $term . '%')
|
||||||
->getQuery();
|
->getQuery();
|
||||||
|
|
||||||
return $this->paginator->paginate($query, $pageNumber, $perPage);
|
return $this->paginator->paginate($query, $pageNumber, $perPage);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class MonitorRepository extends ServiceEntityRepository
|
|||||||
$this->paginator = $paginator;
|
$this->paginator = $paginator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUserMonitorsPaginated(UserInterface $user, int $page, int $perPage): Paginator
|
public function getUserMonitorsPaginated(UserInterface $user, int $page, int $perPage, string $searchTerm): Paginator
|
||||||
{
|
{
|
||||||
$query = $this->createQueryBuilder('m')
|
$query = $this->createQueryBuilder('m')
|
||||||
->andWhere('m.status IN (:statuses)')
|
->andWhere('m.status IN (:statuses)')
|
||||||
|
|||||||
@@ -3,14 +3,18 @@
|
|||||||
namespace App\Monitor\Service;
|
namespace App\Monitor\Service;
|
||||||
|
|
||||||
use Aimeos\Map;
|
use Aimeos\Map;
|
||||||
|
use Nihilarr\PTN;
|
||||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
use Symfony\Component\Finder\Finder;
|
use Symfony\Component\Finder\Finder;
|
||||||
|
use Symfony\Component\Finder\SplFileInfo;
|
||||||
|
|
||||||
class MediaFiles
|
class MediaFiles
|
||||||
{
|
{
|
||||||
private Finder $finder;
|
private Finder $finder;
|
||||||
|
|
||||||
|
private string $basePath;
|
||||||
|
|
||||||
private string $moviesPath;
|
private string $moviesPath;
|
||||||
|
|
||||||
private string $tvShowsPath;
|
private string $tvShowsPath;
|
||||||
@@ -18,6 +22,9 @@ class MediaFiles
|
|||||||
private Filesystem $filesystem;
|
private Filesystem $filesystem;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
#[Autowire(param: 'media.base_path')]
|
||||||
|
string $basePath,
|
||||||
|
|
||||||
#[Autowire(param: 'media.movies_path')]
|
#[Autowire(param: 'media.movies_path')]
|
||||||
string $moviesPath,
|
string $moviesPath,
|
||||||
|
|
||||||
@@ -27,6 +34,7 @@ class MediaFiles
|
|||||||
Filesystem $filesystem,
|
Filesystem $filesystem,
|
||||||
) {
|
) {
|
||||||
$this->finder = new Finder();
|
$this->finder = new Finder();
|
||||||
|
$this->basePath = $basePath;
|
||||||
$this->moviesPath = $moviesPath;
|
$this->moviesPath = $moviesPath;
|
||||||
$this->tvShowsPath = $tvShowsPath;
|
$this->tvShowsPath = $tvShowsPath;
|
||||||
$this->filesystem = $filesystem;
|
$this->filesystem = $filesystem;
|
||||||
@@ -43,6 +51,11 @@ class MediaFiles
|
|||||||
throw new \Exception(sprintf('A path for media type %s does not exist.', $mediaType));
|
throw new \Exception(sprintf('A path for media type %s does not exist.', $mediaType));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getBasePath(): string
|
||||||
|
{
|
||||||
|
return $this->basePath;
|
||||||
|
}
|
||||||
|
|
||||||
public function getMoviesPath(): string
|
public function getMoviesPath(): string
|
||||||
{
|
{
|
||||||
return $this->moviesPath;
|
return $this->moviesPath;
|
||||||
@@ -125,4 +138,49 @@ class MediaFiles
|
|||||||
|
|
||||||
return $path;
|
return $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function episodeExists(string $tvshowTitle, int $seasonNumber, int $episodeNumber)
|
||||||
|
{
|
||||||
|
$existingEpisodes = $this->getEpisodes($tvshowTitle, false);
|
||||||
|
|
||||||
|
if ($existingEpisodes->isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var SplFileInfo $episode */
|
||||||
|
foreach ($existingEpisodes as $episode) {
|
||||||
|
$ptn = (object) (new PTN())->parse($episode->getFilename());
|
||||||
|
|
||||||
|
if ($ptn->season === $seasonNumber && $ptn->episode === $episodeNumber) {
|
||||||
|
return $episode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function movieExists(string $title)
|
||||||
|
{
|
||||||
|
$filepath = $this->moviesPath . DIRECTORY_SEPARATOR . $title;
|
||||||
|
$directoryExists = $this->filesystem->exists($filepath);
|
||||||
|
|
||||||
|
if (false === $directoryExists) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === $this->finder->in($filepath)->files()->hasResults()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = Map::from($this->finder->in($filepath)->files())->filter(function (SplFileInfo $file) {
|
||||||
|
$validExtensions = ['mkv', 'mp4', 'mpeg'];
|
||||||
|
return in_array($file->getExtension(), $validExtensions);
|
||||||
|
})->values();
|
||||||
|
|
||||||
|
if (false === $files->isEmpty()) {
|
||||||
|
return $files[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 $tmdbId,
|
public string $imdbId,
|
||||||
public string $mediaType,
|
public string $mediaType,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
@@ -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->tmdbId, $command->mediaType);
|
$media = $this->tmdb->mediaDetails($command->imdbId, $command->mediaType);
|
||||||
|
|
||||||
return new GetMediaInfoResult($media);
|
return new GetMediaInfoResult($media);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ use OneToMany\RichBundle\Contract\InputInterface;
|
|||||||
class GetMediaInfoInput implements InputInterface
|
class GetMediaInfoInput implements InputInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
#[SourceRoute('tmdbId')]
|
#[SourceRoute('imdbId')]
|
||||||
public string $tmdbId,
|
public string $imdbId,
|
||||||
|
|
||||||
#[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->tmdbId, $this->mediaType);
|
return new GetMediaInfoCommand($this->imdbId, $this->mediaType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,9 +133,12 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +146,16 @@ 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)
|
||||||
@@ -183,6 +195,8 @@ 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 {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Torrentio\Action\Handler;
|
namespace App\Torrentio\Action\Handler;
|
||||||
|
|
||||||
|
use App\Monitor\Service\MediaFiles;
|
||||||
use App\Tmdb\Tmdb;
|
use App\Tmdb\Tmdb;
|
||||||
use App\Torrentio\Action\Result\GetMovieOptionsResult;
|
use App\Torrentio\Action\Result\GetMovieOptionsResult;
|
||||||
use App\Torrentio\Client\Torrentio;
|
use App\Torrentio\Client\Torrentio;
|
||||||
@@ -14,12 +15,15 @@ class GetMovieOptionsHandler implements HandlerInterface
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly Tmdb $tmdb,
|
private readonly Tmdb $tmdb,
|
||||||
private readonly Torrentio $torrentio,
|
private readonly Torrentio $torrentio,
|
||||||
|
private readonly MediaFiles $mediaFiles
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function handle(CommandInterface $command): ResultInterface
|
public function handle(CommandInterface $command): ResultInterface
|
||||||
{
|
{
|
||||||
|
$media = $this->tmdb->mediaDetails($command->imdbId, 'movies');
|
||||||
return new GetMovieOptionsResult(
|
return new GetMovieOptionsResult(
|
||||||
media: $this->tmdb->mediaDetails($command->tmdbId, 'movies'),
|
media: $media,
|
||||||
|
file: $this->mediaFiles->movieExists($media->title),
|
||||||
results: $this->torrentio->search($command->imdbId, 'movies'),
|
results: $this->torrentio->search($command->imdbId, 'movies'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Torrentio\Action\Handler;
|
namespace App\Torrentio\Action\Handler;
|
||||||
|
|
||||||
|
use App\Monitor\Service\MediaFiles;
|
||||||
use App\Tmdb\Tmdb;
|
use App\Tmdb\Tmdb;
|
||||||
use App\Torrentio\Action\Command\GetTvShowOptionsCommand;
|
use App\Torrentio\Action\Command\GetTvShowOptionsCommand;
|
||||||
use App\Torrentio\Action\Result\GetTvShowOptionsResult;
|
use App\Torrentio\Action\Result\GetTvShowOptionsResult;
|
||||||
@@ -16,12 +17,18 @@ class GetTvShowOptionsHandler implements HandlerInterface
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly Tmdb $tmdb,
|
private readonly Tmdb $tmdb,
|
||||||
private readonly Torrentio $torrentio,
|
private readonly Torrentio $torrentio,
|
||||||
|
private readonly MediaFiles $mediaFiles,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function handle(CommandInterface $command): ResultInterface
|
public function handle(CommandInterface $command): ResultInterface
|
||||||
{
|
{
|
||||||
|
$media = $this->tmdb->episodeDetails($command->tmdbId, $command->season, $command->episode);
|
||||||
|
$parentShow = $this->tmdb->mediaDetails($command->imdbId, 'tvshows');
|
||||||
|
$file = $this->mediaFiles->episodeExists($parentShow->title, $command->season, $command->episode);
|
||||||
|
|
||||||
return new GetTvShowOptionsResult(
|
return new GetTvShowOptionsResult(
|
||||||
media: $this->tmdb->episodeDetails($command->tmdbId, $command->season, $command->episode),
|
media: $media,
|
||||||
|
file: $file,
|
||||||
season: $command->season,
|
season: $command->season,
|
||||||
episode: $command->episode,
|
episode: $command->episode,
|
||||||
results: $this->torrentio->fetchEpisodeResults(
|
results: $this->torrentio->fetchEpisodeResults(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class GetMovieOptionsResult implements ResultInterface
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public TmdbResult $media,
|
public TmdbResult $media,
|
||||||
|
public bool|\SplFileInfo $file,
|
||||||
public array $results
|
public array $results
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ namespace App\Torrentio\Action\Result;
|
|||||||
|
|
||||||
use App\Tmdb\TmdbResult;
|
use App\Tmdb\TmdbResult;
|
||||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
|
use Symfony\Component\Finder\SplFileInfo;
|
||||||
|
|
||||||
class GetTvShowOptionsResult implements ResultInterface
|
class GetTvShowOptionsResult implements ResultInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public TmdbResult $media,
|
public TmdbResult $media,
|
||||||
|
public bool|SplFileInfo $file,
|
||||||
public string $season,
|
public string $season,
|
||||||
public string $episode,
|
public string $episode,
|
||||||
public array $results
|
public array $results
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ final class DownloadList extends AbstractController
|
|||||||
|
|
||||||
use PaginateTrait;
|
use PaginateTrait;
|
||||||
|
|
||||||
|
#[LiveProp(writable: true)]
|
||||||
|
public string $term = "";
|
||||||
|
|
||||||
#[LiveProp(writable: true)]
|
#[LiveProp(writable: true)]
|
||||||
public string $type;
|
public string $type;
|
||||||
|
|
||||||
@@ -31,9 +34,9 @@ final class DownloadList extends AbstractController
|
|||||||
public function getDownloads()
|
public function getDownloads()
|
||||||
{
|
{
|
||||||
if ($this->type === "active") {
|
if ($this->type === "active") {
|
||||||
return $this->downloadRepository->getActivePaginated($this->getUser(), $this->pageNumber, $this->perPage);
|
return $this->downloadRepository->getActivePaginated($this->getUser(), $this->pageNumber, $this->perPage, $this->term);
|
||||||
} elseif ($this->type === "complete") {
|
} elseif ($this->type === "complete") {
|
||||||
return $this->downloadRepository->getCompletePaginated($this->getUser(), $this->pageNumber, $this->perPage);
|
return $this->downloadRepository->getCompletePaginated($this->getUser(), $this->pageNumber, $this->perPage, $this->term);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
14
src/Twig/Components/DownloadListRow.php
Normal file
14
src/Twig/Components/DownloadListRow.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Twig\Components;
|
||||||
|
|
||||||
|
use App\Download\Framework\Entity\Download;
|
||||||
|
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
|
||||||
|
|
||||||
|
#[AsTwigComponent]
|
||||||
|
final class DownloadListRow
|
||||||
|
{
|
||||||
|
public Download $download;
|
||||||
|
|
||||||
|
public bool $isWidget = true;
|
||||||
|
}
|
||||||
10
src/Twig/Components/Modal.php
Normal file
10
src/Twig/Components/Modal.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Twig\Components;
|
||||||
|
|
||||||
|
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
|
||||||
|
|
||||||
|
#[AsTwigComponent]
|
||||||
|
final class Modal
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -17,6 +17,9 @@ final class MonitorList extends AbstractController
|
|||||||
|
|
||||||
use PaginateTrait;
|
use PaginateTrait;
|
||||||
|
|
||||||
|
#[LiveProp(writable: true)]
|
||||||
|
public string $term = "";
|
||||||
|
|
||||||
#[LiveProp(writable: true)]
|
#[LiveProp(writable: true)]
|
||||||
public string $type;
|
public string $type;
|
||||||
|
|
||||||
@@ -44,7 +47,9 @@ final class MonitorList extends AbstractController
|
|||||||
{
|
{
|
||||||
return $this->asPaginator($this->monitorRepository->createQueryBuilder('m')
|
return $this->asPaginator($this->monitorRepository->createQueryBuilder('m')
|
||||||
->andWhere('m.status IN (:statuses)')
|
->andWhere('m.status IN (:statuses)')
|
||||||
|
->andWhere('(m.title LIKE :term OR m.imdbId LIKE :term OR m.monitorType LIKE :term OR m.status LIKE :term)')
|
||||||
->setParameter('statuses', ['New', 'In Progress'])
|
->setParameter('statuses', ['New', 'In Progress'])
|
||||||
|
->setParameter('term', '%'.$this->term.'%')
|
||||||
->orderBy('m.id', 'DESC')
|
->orderBy('m.id', 'DESC')
|
||||||
->getQuery()
|
->getQuery()
|
||||||
);
|
);
|
||||||
@@ -55,7 +60,9 @@ final class MonitorList extends AbstractController
|
|||||||
{
|
{
|
||||||
return $this->asPaginator($this->monitorRepository->createQueryBuilder('m')
|
return $this->asPaginator($this->monitorRepository->createQueryBuilder('m')
|
||||||
->andWhere('m.status = :status')
|
->andWhere('m.status = :status')
|
||||||
|
->andWhere('(m.title LIKE :term OR m.imdbId LIKE :term OR m.monitorType LIKE :term OR m.status LIKE :term)')
|
||||||
->setParameter('status', 'Complete')
|
->setParameter('status', 'Complete')
|
||||||
|
->setParameter('term', '%'.$this->term.'%')
|
||||||
->orderBy('m.id', 'DESC')
|
->orderBy('m.id', 'DESC')
|
||||||
->getQuery()
|
->getQuery()
|
||||||
);
|
);
|
||||||
|
|||||||
12
src/Twig/Components/MonitorListRow.php
Normal file
12
src/Twig/Components/MonitorListRow.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Twig\Components;
|
||||||
|
|
||||||
|
use App\Monitor\Framework\Entity\Monitor;
|
||||||
|
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
|
||||||
|
|
||||||
|
#[AsTwigComponent]
|
||||||
|
final class MonitorListRow
|
||||||
|
{
|
||||||
|
public Monitor $monitor;
|
||||||
|
}
|
||||||
32
src/Twig/Extensions/UtilExtension.php
Normal file
32
src/Twig/Extensions/UtilExtension.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Twig\Extensions;
|
||||||
|
|
||||||
|
use App\Monitor\Framework\Entity\Monitor;
|
||||||
|
use App\Monitor\Service\MediaFiles;
|
||||||
|
use ChrisUllyott\FileSize;
|
||||||
|
use Twig\Attribute\AsTwigFilter;
|
||||||
|
|
||||||
|
class UtilExtension
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly MediaFiles $mediaFiles,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[AsTwigFilter('filesize')]
|
||||||
|
public function type(string|int $size)
|
||||||
|
{
|
||||||
|
return (new FileSize($size))->asAuto();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[AsTwigFilter('strip_media_path')]
|
||||||
|
public function stripMediaPath(string $path)
|
||||||
|
{
|
||||||
|
return str_replace(
|
||||||
|
$this->mediaFiles->getBasePath() . DIRECTORY_SEPARATOR,
|
||||||
|
'',
|
||||||
|
$path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,13 +7,30 @@ module.exports = {
|
|||||||
safelist: [
|
safelist: [
|
||||||
"bg-blue-300",
|
"bg-blue-300",
|
||||||
"bg-orange-300",
|
"bg-orange-300",
|
||||||
"bg-rose-300",
|
"bg-fuchsia-300",
|
||||||
"bg-green-400",
|
"bg-green-400",
|
||||||
"bg-purple-400",
|
"bg-purple-400",
|
||||||
"bg-orange-400",
|
"bg-orange-400",
|
||||||
|
"bg-blue-600",
|
||||||
|
"bg-rose-600",
|
||||||
|
"transition-opacity",
|
||||||
|
"ease-in",
|
||||||
|
"duration-700",
|
||||||
|
"opacity-100"
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {
|
||||||
|
animation: {
|
||||||
|
fade: 'fadeIn .3s ease-in-out',
|
||||||
|
},
|
||||||
|
|
||||||
|
keyframes: {
|
||||||
|
fadeIn: {
|
||||||
|
from: { opacity: 0 },
|
||||||
|
to: { opacity: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,38 +5,19 @@
|
|||||||
|
|
||||||
<turbo-stream action="append" target="active_downloads">
|
<turbo-stream action="append" target="active_downloads">
|
||||||
<template>
|
<template>
|
||||||
<tr data-download-list-target="download" id="ad_download_{{ entity.id }}">
|
<twig:DownloadListRow download="{{ entity }}" />
|
||||||
<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">
|
|
||||||
{{ entity.title }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 dark:text-gray-50">
|
|
||||||
{% if entity.progress < 100 %}
|
|
||||||
<div class="w-[3.25ch] h-[3.25ch] bg-purple-600 rounded-full block text-center table-cell align-middle text-xs text-gray-50">
|
|
||||||
{{ entity.progress }}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<twig:StatusBadge color="green" status="Complete" />
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
</template>
|
||||||
</turbo-stream>
|
</turbo-stream>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block update %}
|
{% block update %}
|
||||||
{% if entity.status != "Complete" %}
|
{% if entity.status != "Complete" %}
|
||||||
<turbo-stream action="update" target="ad_download_{{ id }}">
|
<turbo-stream action="update" target="download_progress_{{ id }}">
|
||||||
<template>
|
<template>
|
||||||
<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">
|
<div class="text-green-700 rounded-sm text-bold text-gray-950 text-center bg-green-600 h-5"
|
||||||
{{ entity.title }}
|
style="width:{{ entity.progress }}%">
|
||||||
</td>
|
<span>{{ entity.progress }}%</span>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50">
|
</div>
|
||||||
<div class="border-2 border-green-700 rounded-md w-full h-6 align-middle overflow-hidden">
|
|
||||||
<div class="text-green-700 rounded-sm text-bold text-gray-950 text-center bg-green-600 h-5" style="width:{{ entity.progress }}%">{{ entity.progress }}%</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
</template>
|
</template>
|
||||||
</turbo-stream>
|
</turbo-stream>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -54,23 +35,7 @@
|
|||||||
|
|
||||||
<turbo-stream action="prepend" target="complete_downloads">
|
<turbo-stream action="prepend" target="complete_downloads">
|
||||||
<template>
|
<template>
|
||||||
<tr id="ad_download_{{ entity.id }}">
|
<twig:DownloadListRow download="{{ entity }}" />
|
||||||
<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">
|
|
||||||
{{ entity.title }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 dark:text-gray-50">
|
|
||||||
<twig:StatusBadge color="green" status="Complete" />
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 flex flex-row align-middle justify-center">
|
|
||||||
<button {{ stimulus_action('download_list', 'deleteDownload', 'click', {id: download.id}) }}>
|
|
||||||
<twig:ux:icon
|
|
||||||
name="ic:twotone-cancel" width="18px"
|
|
||||||
class="rounded-full align-middle text-red-600"
|
|
||||||
title="Remove {{ download.title }} from download list. This will not delete the file."
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
</template>
|
||||||
</turbo-stream>
|
</turbo-stream>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -3,81 +3,19 @@
|
|||||||
<turbo-stream action="remove" target="active_monitors_no_monitors">
|
<turbo-stream action="remove" target="active_monitors_no_monitors">
|
||||||
</turbo-stream>
|
</turbo-stream>
|
||||||
|
|
||||||
<turbo-stream action="append" target="monitors">
|
<turbo-stream action="prepend" target="monitors">
|
||||||
<template>
|
<template>
|
||||||
<tr id="monitor_{{ entity.id }}">
|
<twig:MonitorListRow monitor="{{ entity }}" />
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-stone-800 min-w-[50ch] max-w-[50ch] truncate">
|
|
||||||
{{ entity.title }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{{ entity.searchCount }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{{ entity.createdAt|date('m/d/Y h:i a') }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{{ entity.lastSearch|date('m/d/Y h:i a') }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{% if entity.status == "New" %}
|
|
||||||
<twig:StatusBadge color="orange" status="{{ entity.status }}" />
|
|
||||||
{% elseif entity.status == "In Progress" or entity.status == "Active" %}
|
|
||||||
<twig:StatusBadge color="purple" status="{{ entity.status }}" />
|
|
||||||
{% else %}
|
|
||||||
<twig:StatusBadge color="green" status="{{ entity.status }}" />
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 flex flex-row align-middle justify-center">
|
|
||||||
<button {{ stimulus_action('monitor_list', 'deleteMonitor', 'click', {id: entity.id}) }}>
|
|
||||||
<twig:ux:icon
|
|
||||||
name="ic:twotone-cancel" width="18px"
|
|
||||||
class="rounded-full align-middle text-red-600"
|
|
||||||
title="Remove {{ entity.title }} from your Monitor list."
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
</template>
|
||||||
</turbo-stream>
|
</turbo-stream>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block update %}
|
{% block update %}
|
||||||
<turbo-stream action="prepend" target="monitors">
|
<turbo-stream action="replace" target="monitor_{{ id }}">
|
||||||
<template>
|
<template>
|
||||||
<tr id="monitor_{{ entity.id }}">
|
<twig:MonitorListRow monitor="{{ entity }}" />
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-stone-800 min-w-[50ch] max-w-[50ch] truncate">
|
</template>
|
||||||
{{ entity.title }}
|
</turbo-stream>
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{{ entity.searchCount }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{{ entity.createdAt|date('m/d/Y h:i a') }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{{ entity.lastSearch|date('m/d/Y h:i a') }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{% if entity.status == "New" %}
|
|
||||||
<twig:StatusBadge color="orange" status="{{ entity.status }}" />
|
|
||||||
{% elseif entity.status == "In Progress" or entity.status == "Active" %}
|
|
||||||
<twig:StatusBadge color="purple" status="{{ entity.status }}" />
|
|
||||||
{% else %}
|
|
||||||
<twig:StatusBadge color="green" status="{{ entity.status }}" />
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 flex flex-row align-middle justify-center">
|
|
||||||
<button {{ stimulus_action('monitor_list', 'deleteMonitor', 'click', {id: entity.id}) }}>
|
|
||||||
<twig:ux:icon
|
|
||||||
name="ic:twotone-cancel" width="18px"
|
|
||||||
class="rounded-full align-middle text-red-600"
|
|
||||||
title="Remove {{ entity.title }} from your Monitor list."
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
</turbo-stream>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block remove %}
|
{% block remove %}
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
<div{{ attributes.defaults(stimulus_controller('download_list')) }} class="min-w-48" >
|
<div{{ attributes.defaults(stimulus_controller('download_list')) }} 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 == false %}
|
||||||
|
<div class="flex flex-row mb-2 justify-end">
|
||||||
|
<twig:DownloadSearch search_path="app_search" placeholder="Find one of your downloads..." />
|
||||||
|
</div>
|
||||||
|
{% 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,42 +38,7 @@
|
|||||||
<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 %}
|
||||||
<tr id="ad_download_{{ download.id }}">
|
<twig:DownloadListRow download="{{ download }}" isWidget="{{ this.isWidget }}" />
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800 {% if this.isWidget == true %}min-w-[45ch] max-w-[45ch]{% endif %} truncate">
|
|
||||||
{{ download.title }}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
{% if this.isWidget == false %}
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800 truncate">
|
|
||||||
{{ download.filename }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800 truncate">
|
|
||||||
{{ download.mediaType }}
|
|
||||||
</td>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm align-middle text-gray-800 dark:text-gray-50">
|
|
||||||
{% if download.progress < 100 %}
|
|
||||||
<div class="border-2 border-green-700 rounded-md w-full h-6 align-middle overflow-hidden">
|
|
||||||
<div class="text-green-700 rounded-sm text-bold text-gray-950 text-center bg-green-600 h-5"
|
|
||||||
style="width:{{ download.progress }}%">
|
|
||||||
{{ download.progress }}%
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<twig:StatusBadge color="green" status="Complete" />
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 flex flex-row align-middle justify-center">
|
|
||||||
<button {{ stimulus_action('download_list', 'deleteDownload', 'click', {id: download.id}) }}>
|
|
||||||
<twig:ux:icon
|
|
||||||
name="ic:twotone-cancel" width="18px"
|
|
||||||
class="rounded-full align-middle text-red-600"
|
|
||||||
title="Remove {{ download.title }} from your Download list. This will not delete the file."
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if this.isWidget == true and this.downloads.items|length > this.perPage %}
|
{% if this.isWidget == true and this.downloads.items|length > this.perPage %}
|
||||||
<tr id="download_view_all">
|
<tr id="download_view_all">
|
||||||
|
|||||||
42
templates/components/DownloadListRow.html.twig
Normal file
42
templates/components/DownloadListRow.html.twig
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<tr{{ attributes }} id="ad_download_{{ download.id }}">
|
||||||
|
<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}) }}"
|
||||||
|
class="hover:underline mr-1"
|
||||||
|
>
|
||||||
|
{{ download.title }}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{% if download.mediaType == "tvshows" %}
|
||||||
|
— <span class="ml-1">(S{{ download.ptn.season }}E{{ download.ptn.episode }})</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{% if isWidget == false %}
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800 max-w-[60ch] truncate">
|
||||||
|
{{ download.filename }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-stone-800 truncate">
|
||||||
|
{{ download.mediaType }}
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm align-middle text-gray-800 dark:text-gray-50">
|
||||||
|
{% if download.progress < 100 %}
|
||||||
|
<div id="download_progress_{{ download.id }}" class="border-2 border-green-600 rounded-md text-center w-full h-6 align-middle overflow-hidden">
|
||||||
|
<div class="text-black text-center rounded-sm text-bold bg-green-300 h-5 relative z-10"
|
||||||
|
style="width:{{ download.progress }}%">
|
||||||
|
</div>
|
||||||
|
<div class="absolute text-black text-center" style="z-index: 400;margin-top: -1.25rem; margin-left: 1.2rem">{{ download.progress }}%</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<twig:StatusBadge color="green" status="Complete" />
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 flex flex-row align-middle justify-center">
|
||||||
|
{% set delete_button = component('ux:icon', {name: 'ic:twotone-cancel', width: '18px', class: 'rounded-full align-middle text-red-600' }) %}
|
||||||
|
<twig:Modal heading="But wait!" button_text="{{ delete_button }}" submit_action="{{ stimulus_action('download_list', 'deleteDownload', 'click', {id: download.id}) }}" show_cancel show_submit>
|
||||||
|
Are you sure you want to delete <span class="font-bold">{{ download.filename }}</span>?
|
||||||
|
</twig:Modal>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
29
templates/components/DownloadSearch.html.twig
Normal file
29
templates/components/DownloadSearch.html.twig
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<div {{ attributes }} class="w-full max-w-sm min-w-[200px]">
|
||||||
|
<div class="relative">
|
||||||
|
<form>
|
||||||
|
<input
|
||||||
|
data-model="term"
|
||||||
|
class="w-full bg-transparent
|
||||||
|
placeholder:text-slate-200 text-gray-50
|
||||||
|
text-sm border-b border-orange-500 pl-3 pr-28 py-2 transition
|
||||||
|
duration-300 ease focus:outline-none focus:border-orange-400 hover:border-orange-300
|
||||||
|
shadow-sm focus:shadow"
|
||||||
|
placeholder="{{ placeholder ?? 'TV Show, Movie...' }}"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="absolute top-1 right-1 flex items-center
|
||||||
|
bg-green-600 py-1 px-2 text-center
|
||||||
|
text-md text-white transition-all
|
||||||
|
focus:bg-green-700 active:bg-green-700 hover:bg-green-700
|
||||||
|
|
||||||
|
text-white bg-green-600 text-xs
|
||||||
|
rounded-sm
|
||||||
|
bg-opacity-80
|
||||||
|
"
|
||||||
|
onclick="return false;"
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
26
templates/components/Modal.html.twig
Normal file
26
templates/components/Modal.html.twig
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<div{{ attributes }} data-controller="dialog" data-action="click->dialog#backdropClose">
|
||||||
|
<dialog data-dialog-target="dialog" class="py-3 px-4 w-[30rem] rounded-md">
|
||||||
|
<h2 class="mb-4 text-2xl font-bold">{{ heading }}</h2>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if show_cancel is defined or show_submit is defined %}
|
||||||
|
<div class="flex justify-end">
|
||||||
|
{% 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>
|
||||||
|
{{ cancel_text|default('Cancel') }}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% 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>
|
||||||
|
{{ submit_text|default('Submit') }}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<button type="button" data-action="dialog#open">{{ button_text|raw }}</button>
|
||||||
|
</div>
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
<div{{ attributes.defaults(stimulus_controller('monitor_list')) }}>
|
<div{{ attributes.defaults(stimulus_controller('monitor_list')) }}>
|
||||||
|
{% if this.isWidget == false %}
|
||||||
|
<div class="flex flex-row mb-2 justify-end">
|
||||||
|
<twig:DownloadSearch search_path="app_search" placeholder="Find one of your monitors..." />
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<table id="monitor_list" class="divide-y divide-gray-200 bg-gray-50 overflow-hidden rounded-lg table-auto w-full" {{ turbo_stream_listen('App\\Monitor\\Framework\\Entity\\Monitor') }}>
|
<table id="monitor_list" class="divide-y divide-gray-200 bg-gray-50 overflow-hidden rounded-lg table-auto w-full" {{ turbo_stream_listen('App\\Monitor\\Framework\\Entity\\Monitor') }}>
|
||||||
<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">
|
||||||
@@ -36,50 +41,7 @@
|
|||||||
<tbody id="monitors" class="divide-y divide-gray-50">
|
<tbody id="monitors" class="divide-y divide-gray-50">
|
||||||
{% if this.monitors.items|length > 0 %}
|
{% if this.monitors.items|length > 0 %}
|
||||||
{% for monitor in this.monitors.items %}
|
{% for monitor in this.monitors.items %}
|
||||||
<tr id="monitor_{{ monitor.id }}">
|
<twig:MonitorListRow :monitor="monitor" />
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-stone-800 truncate">
|
|
||||||
{{ monitor.title }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{{ monitor|monitor_media_id }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{{ monitor.searchCount }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{{ monitor.createdAt|date('m/d/Y h:i a') }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{{ monitor.lastSearch|date('m/d/Y h:i a') }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{% if monitor.monitorType == "tvshow" %}
|
|
||||||
<twig:StatusBadge color="blue" number="300" text="black" status="{{ monitor.monitorType|monitor_type }}" />
|
|
||||||
{% elseif monitor.monitorType == "tvseason" %}
|
|
||||||
<twig:StatusBadge color="orange" number="300" text="black" status="{{ monitor.monitorType|monitor_type }}" />
|
|
||||||
{% else %}
|
|
||||||
<twig:StatusBadge color="rose" number="300" text="black" status="{{ monitor.monitorType|monitor_type }}" />
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
|
||||||
{% if monitor.status == "New" %}
|
|
||||||
<twig:StatusBadge color="orange" status="{{ monitor.status }}" />
|
|
||||||
{% elseif monitor.status == "In Progress" or monitor.status == "Active" %}
|
|
||||||
<twig:StatusBadge color="purple" status="{{ monitor.status }}" />
|
|
||||||
{% else %}
|
|
||||||
<twig:StatusBadge color="green" status="{{ monitor.status }}" />
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 flex flex-row align-middle justify-center">
|
|
||||||
<button {{ stimulus_action('monitor_list', 'deleteMonitor', 'click', {id: monitor.id}) }}>
|
|
||||||
<twig:ux:icon
|
|
||||||
name="ic:twotone-cancel" width="18px"
|
|
||||||
class="rounded-full align-middle text-red-600"
|
|
||||||
title="Remove {{ monitor.title }} from your Monitor list."
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if this.isWidget and this.monitors.items|length > 5 %}
|
{% if this.isWidget and this.monitors.items|length > 5 %}
|
||||||
<tr id="monitor_view_all">
|
<tr id="monitor_view_all">
|
||||||
|
|||||||
73
templates/components/MonitorListRow.html.twig
Normal file
73
templates/components/MonitorListRow.html.twig
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<tr{{ attributes }} id="monitor_{{ monitor.id }}">
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-stone-800 truncate">
|
||||||
|
{{ monitor.title }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
||||||
|
{{ monitor|monitor_media_id }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
||||||
|
{{ monitor.searchCount }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
||||||
|
{{ monitor.createdAt|date('m/d/Y h:i a') }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
||||||
|
{{ monitor.lastSearch|date('m/d/Y h:i a') }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
||||||
|
{% if monitor.monitorType == "tvshow" %}
|
||||||
|
<twig:StatusBadge color="blue" number="300" text="black" status="{{ monitor.monitorType|monitor_type }}" />
|
||||||
|
{% elseif monitor.monitorType == "tvseason" %}
|
||||||
|
<twig:StatusBadge color="orange" number="300" text="black" status="{{ monitor.monitorType|monitor_type }}" />
|
||||||
|
{% else %}
|
||||||
|
<twig:StatusBadge color="fuchsia" number="300" text="black" status="{{ monitor.monitorType|monitor_type }}" />
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
||||||
|
{% if monitor.status == "New" %}
|
||||||
|
<twig:StatusBadge color="orange" status="{{ monitor.status }}" />
|
||||||
|
{% elseif monitor.status == "In Progress" or monitor.status == "Active" %}
|
||||||
|
<twig:StatusBadge color="purple" status="{{ monitor.status }}" />
|
||||||
|
{% else %}
|
||||||
|
<twig:StatusBadge color="green" status="{{ monitor.status }}" />
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 flex flex-row align-middle justify-center">
|
||||||
|
{% set delete_button = component('ux:icon', {name: 'ic:twotone-cancel', width: '18px', class: 'rounded-full align-middle text-red-600' }) %}
|
||||||
|
<twig:Modal heading="But wait!" button_text="{{ delete_button }}" submit_action="{{ stimulus_action('monitor_list', 'deleteMonitor', 'click', {id: monitor.id}) }}" show_cancel show_submit>
|
||||||
|
Are you sure you want to delete this monitor?<br />
|
||||||
|
<div class="mt-2 border-2 border-orange-500 overflow-hidden rounded-lg">
|
||||||
|
<table class="table-auto">
|
||||||
|
<tr>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-stone-800 truncate">
|
||||||
|
{{ monitor.title }}
|
||||||
|
</td>
|
||||||
|
{% if monitor|monitor_media_id != "-" %}
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
||||||
|
{{ monitor|monitor_media_id }}
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
||||||
|
{% if monitor.monitorType == "tvshow" %}
|
||||||
|
<twig:StatusBadge color="blue" number="300" text="black" status="{{ monitor.monitorType|monitor_type }}" />
|
||||||
|
{% elseif monitor.monitorType == "tvseason" %}
|
||||||
|
<twig:StatusBadge color="orange" number="300" text="black" status="{{ monitor.monitorType|monitor_type }}" />
|
||||||
|
{% else %}
|
||||||
|
<twig:StatusBadge color="fuchsia" number="300" text="black" status="{{ monitor.monitorType|monitor_type }}" />
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800">
|
||||||
|
{% if monitor.status == "New" %}
|
||||||
|
<twig:StatusBadge color="orange" status="{{ monitor.status }}" />
|
||||||
|
{% elseif monitor.status == "In Progress" or monitor.status == "Active" %}
|
||||||
|
<twig:StatusBadge color="purple" status="{{ monitor.status }}" />
|
||||||
|
{% else %}
|
||||||
|
<twig:StatusBadge color="green" status="{{ monitor.status }}" />
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</twig:Modal>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
<div{{ attributes }}>
|
<div{{ attributes }}>
|
||||||
<a href="{{ path('app_search_result', {
|
<a href="{{ path('app_search_result', {
|
||||||
mediaType: mediaType,
|
mediaType: mediaType,
|
||||||
tmdbId: tmdbId
|
imdbId: imdbId
|
||||||
}) }}">
|
}) }}">
|
||||||
<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,
|
||||||
tmdbId: tmdbId
|
imdbId: imdbId
|
||||||
}) }}">
|
}) }}">
|
||||||
<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>
|
||||||
|
|||||||
@@ -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, tmdbId: tmdbId}) }}"
|
href="{{ path('app_search_result', {mediaType: mediaType, imdbId: imdbId}) }}"
|
||||||
>choose</a>
|
>choose</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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=""
|
<twig:Poster imdbId="{{ movie.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=""
|
<twig:Poster imdbId="{{ movie.imdbId }}"
|
||||||
tmdbId="{{ movie.tmdbId }}"
|
tmdbId="{{ movie.tmdbId }}"
|
||||||
title="{{ movie.title }}"
|
title="{{ movie.title }}"
|
||||||
description="{{ movie.description }}"
|
description="{{ movie.description }}"
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
<div class="p-4 flex flex-col gap-6 bg-orange-500 bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-60 rounded-md">
|
<div class="p-4 flex flex-col gap-6 bg-orange-500 bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-60 rounded-md">
|
||||||
|
{% if results.file != false %}
|
||||||
|
<div class="p-3 bg-stone-400 p-1 text-black rounded-md m-1 animate-fade">
|
||||||
|
<p class="font-bold text-sm text-left">Existing file(s) for this movie:</p>
|
||||||
|
<ul class="list-disc ml-3">
|
||||||
|
<li class="font-normal">{{ results.file.realPath|strip_media_path }} — <strong>{{ results.file.size|filesize }}</strong></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="overflow-hidden rounded-md">
|
<div class="overflow-hidden rounded-md">
|
||||||
{{ include('torrentio/partial/option-table.html.twig', {controller: 'movie-results'}) }}
|
{{ include('torrentio/partial/option-table.html.twig', {controller: 'movie-results'}) }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,29 +8,62 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="flex flex-col gap-4 grow">
|
<div class="flex flex-col gap-4 grow">
|
||||||
<h4 class="text-md font-bold">{{ results.episode }}. {{ results.media.title }}</h4>
|
<h4 class="text-md font-bold">
|
||||||
|
{{ results.episode }}. {{ results.media.title }}
|
||||||
|
</h4>
|
||||||
<p>{{ results.media.description }}</p>
|
<p>{{ results.media.description }}</p>
|
||||||
<div>
|
<div>
|
||||||
<small class="py-1 px-1.5 grow-0 font-bold bg-green-600 rounded-lg hover:cursor-pointer hover:bg-green-700 text-white"
|
<button class="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"
|
||||||
{{ stimulus_action('tv-results', 'toggleList', 'click') }}
|
{{ stimulus_action('tv-results', 'toggleList', 'click') }}
|
||||||
><span {{ stimulus_target('tv-results', 'count') }}>{{ results.results|length }}</span> results</small>
|
>
|
||||||
<small class="py-1 px-1.5 grow-0 font-bold bg-gray-700 rounded-lg font-normal text-white" title="Air date {{ results.media.episodeAirDate }}"
|
<span {{ stimulus_target('tv-results', 'count') }}>{{ results.results|length }}</span> results
|
||||||
>{{ results.media.episodeAirDate }}</small>
|
</button>
|
||||||
|
|
||||||
|
{% if results.file != false %}
|
||||||
|
<span data-controller="popover">
|
||||||
|
<template data-popover-target="content">
|
||||||
|
<div data-popover-target="card" class="absolute z-40 p-1 bg-stone-400 p-1 text-black rounded-md m-1 animate-fade">
|
||||||
|
<p class="font-bold text-sm text-left">Existing file(s) for this episode:</p>
|
||||||
|
<ul class="list-disc ml-3">
|
||||||
|
<li class="font-normal">{{ results.file.realPath|strip_media_path }} — <strong>{{ results.file.size|filesize }}</strong></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<small
|
||||||
|
class="py-1 px-1.5 mr-1 grow-0 font-bold bg-blue-600 rounded-lg text-center text-white"
|
||||||
|
data-action="mouseenter->popover#show mouseleave->popover#hide"
|
||||||
|
>
|
||||||
|
exists
|
||||||
|
</small>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if results.file == false %}
|
||||||
|
<small class="py-1 px-1.5 mr-1 grow-0 font-bold bg-rose-600 rounded-lg text-white" title="Episode has not been downloaded yet.">
|
||||||
|
missing
|
||||||
|
</small>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<small class="py-1 px-1.5 mr-1 grow-0 font-bold bg-gray-700 rounded-lg font-normal text-white" title="Air date {{ results.media.episodeAirDate }}">
|
||||||
|
{{ results.media.episodeAirDate }}
|
||||||
|
</small>
|
||||||
{# <small class="py-1 px-1.5 grow-0 font-bold bg-red-600 hover:bg-red-700 rounded-lg font-normal text-white cursor-pointer" title="Clear cache for {{ results.media.title }}"#}
|
{# <small class="py-1 px-1.5 grow-0 font-bold bg-red-600 hover:bg-red-700 rounded-lg font-normal text-white cursor-pointer" title="Clear cache for {{ results.media.title }}"#}
|
||||||
{# {{ stimulus_action('tv-results', 'clearCache', 'click') }}#}
|
{# {{ stimulus_action('tv-results', 'clearCache', 'click') }}#}
|
||||||
{# >Clear Cache</small>#}
|
{# >Clear Cache</small>#}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-4 justify-between">
|
<div class="flex flex-col gap-4 justify-between">
|
||||||
<input type="checkbox"
|
<div class="flex flex-col items-center">
|
||||||
{{ stimulus_target('tv-results', 'episodeSelector') }}
|
<input type="checkbox"
|
||||||
/>
|
{{ stimulus_target('tv-results', 'episodeSelector') }}
|
||||||
<div class="flex flex-col items-end hover:cursor-pointer"
|
/>
|
||||||
|
</div>
|
||||||
|
<button class="flex flex-col items-end"
|
||||||
{{ stimulus_action('tv-results', 'toggleList', 'click') }}>
|
{{ stimulus_action('tv-results', 'toggleList', 'click') }}>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 32 32">
|
<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"/>
|
<path fill="currentColor" d="m16 10l10 10l-1.4 1.4l-8.6-8.6l-8.6 8.6L6 20z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inline-block overflow-hidden rounded-lg">
|
<div class="inline-block overflow-hidden rounded-lg">
|
||||||
|
|||||||
Reference in New Issue
Block a user