Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2315b995e0 | ||
|
|
83401f2a7a | ||
|
|
50bcb4e1df | ||
|
|
ea569b480d | ||
|
|
50ec0c1d6f |
5
.env
5
.env
@@ -62,3 +62,8 @@ NTFY_DSN=
|
|||||||
###> sentry/sentry-symfony ###
|
###> sentry/sentry-symfony ###
|
||||||
SENTRY_DSN=
|
SENTRY_DSN=
|
||||||
###< sentry/sentry-symfony ###
|
###< sentry/sentry-symfony ###
|
||||||
|
|
||||||
|
# TMDB 'with_original_language' option
|
||||||
|
# - only include media originally
|
||||||
|
# produced in this language
|
||||||
|
TMDB_ORIGINAL_LANGUAGE=en
|
||||||
|
|||||||
1
assets/bootstrap.js
vendored
1
assets/bootstrap.js
vendored
@@ -10,7 +10,6 @@ import { startStimulusApp } from '@symfony/stimulus-bundle';
|
|||||||
import Popover from '@stimulus-components/popover';
|
import Popover from '@stimulus-components/popover';
|
||||||
import Dialog from '@stimulus-components/dialog';
|
import Dialog from '@stimulus-components/dialog';
|
||||||
import Dropdown from '@stimulus-components/dropdown';
|
import Dropdown from '@stimulus-components/dropdown';
|
||||||
|
|
||||||
import 'animate.css';
|
import 'animate.css';
|
||||||
|
|
||||||
const app = startStimulusApp();
|
const app = startStimulusApp();
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
import { Controller } from '@hotwired/stimulus';
|
|
||||||
import {GridStack} from "../vendor/gridstack/gridstack.index.js";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 {
|
|
||||||
grid;
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
connect() {
|
|
||||||
this.grid = GridStack.init({
|
|
||||||
column: 2,
|
|
||||||
alwaysShowResizeHandle: true,
|
|
||||||
margin: "2rem",
|
|
||||||
resizable: {
|
|
||||||
handles: 'e,se,s,sw,w'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.grid.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom controller actions here
|
|
||||||
// fooBar() { this.fooTarget.classList.toggle(this.bazClass) }
|
|
||||||
|
|
||||||
disconnect() {
|
|
||||||
// Called anytime its element is disconnected from the DOM
|
|
||||||
// (on page change, when it's removed from or moved in the DOM, etc.)
|
|
||||||
|
|
||||||
// Here you should remove all event listeners added in "connect()"
|
|
||||||
// this.fooTarget.removeEventListener('click', this._fooBar)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
assets/icons/fluent-mdl2/back.svg
Normal file
1
assets/icons/fluent-mdl2/back.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><path fill="currentColor" d="M2048 1088H250l787 787l-90 90L6 1024L947 83l90 90l-787 787h1798z"/></svg>
|
||||||
|
After Width: | Height: | Size: 167 B |
@@ -5,18 +5,18 @@ when@prod:
|
|||||||
|
|
||||||
options:
|
options:
|
||||||
release: '%app.version%'
|
release: '%app.version%'
|
||||||
traces_sample_rate: 1
|
traces_sample_rate: 0
|
||||||
profiles_sample_rate: 1
|
profiles_sample_rate: 0
|
||||||
attach_stacktrace: true
|
attach_stacktrace: true
|
||||||
|
|
||||||
tracing:
|
# tracing:
|
||||||
enabled: true
|
# enabled: true
|
||||||
dbal: # DB queries
|
# dbal: # DB queries
|
||||||
enabled: true
|
# enabled: true
|
||||||
cache: # cache pools
|
# cache: # cache pools
|
||||||
enabled: true
|
# enabled: true
|
||||||
twig: # templating engine
|
# twig: # templating engine
|
||||||
enabled: true
|
# enabled: true
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# (Optionally) Configure the breadcrumb handler as a service (needed for the breadcrumb Monolog handler)
|
# (Optionally) Configure the breadcrumb handler as a service (needed for the breadcrumb Monolog handler)
|
||||||
|
|||||||
@@ -70,7 +70,4 @@ return [
|
|||||||
'@ungap/custom-elements' => [
|
'@ungap/custom-elements' => [
|
||||||
'version' => '1.3.0',
|
'version' => '1.3.0',
|
||||||
],
|
],
|
||||||
'gridstack' => [
|
|
||||||
'version' => '12.3.3',
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ final class IndexController extends AbstractController
|
|||||||
#[Route('/test')]
|
#[Route('/test')]
|
||||||
public function monitorTvShow(MonitorTvShowHandler $handler): Response
|
public function monitorTvShow(MonitorTvShowHandler $handler): Response
|
||||||
{
|
{
|
||||||
// $handler->handle(new MonitorTvShowCommand(82));
|
throw new \Exception('Test');
|
||||||
return $this->render('index/test.html.twig', []);
|
return $this->render('index/test.html.twig', []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,14 +46,14 @@ readonly class DownloadSeasonHandler implements HandlerInterface
|
|||||||
|
|
||||||
$downloadCommands = [];
|
$downloadCommands = [];
|
||||||
foreach ($episodesInSeason as $episode) {
|
foreach ($episodesInSeason as $episode) {
|
||||||
$this->logger->info('> [DownloadTvSeasonHandler] ...Evaluating episode ' . $episode['episode_number']);
|
$this->logger->info('> [DownloadTvSeasonHandler] ...Evaluating episode ' . $episode->episodeNumber);
|
||||||
|
|
||||||
$results = $this->getTvShowOptionsHandler->handle(
|
$results = $this->getTvShowOptionsHandler->handle(
|
||||||
new GetTvShowOptionsCommand(
|
new GetTvShowOptionsCommand(
|
||||||
$series->tmdbId,
|
$series->tmdbId,
|
||||||
$command->imdbId,
|
$command->imdbId,
|
||||||
$command->season,
|
$command->season,
|
||||||
$episode['episode_number']
|
$episode->episodeNumber
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ readonly class DownloadSeasonHandler implements HandlerInterface
|
|||||||
|
|
||||||
if (null !== $result) {
|
if (null !== $result) {
|
||||||
$this->logger->info('> [DownloadTvSeasonHandler] ......Found 1 matching result');
|
$this->logger->info('> [DownloadTvSeasonHandler] ......Found 1 matching result');
|
||||||
$this->logger->info('> [DownloadTvSeasonHandler] ......Dispatching DownloadMediaCommand for "' . $series->title . '" season ' . $command->season . ' episode ' . $episode['episode_number']);
|
$this->logger->info('> [DownloadTvSeasonHandler] ......Dispatching DownloadMediaCommand for "' . $series->title . '" season ' . $command->season . ' episode ' . $episode->episodeNumber);
|
||||||
$downloadCommand = new DownloadMediaCommand(
|
$downloadCommand = new DownloadMediaCommand(
|
||||||
$result->url,
|
$result->url,
|
||||||
$series->title,
|
$series->title,
|
||||||
@@ -99,10 +99,10 @@ readonly class DownloadSeasonHandler implements HandlerInterface
|
|||||||
->filter(fn ($episode) =>
|
->filter(fn ($episode) =>
|
||||||
property_exists($episode, 'episode')
|
property_exists($episode, 'episode')
|
||||||
&& property_exists($episode, 'season')
|
&& property_exists($episode, 'season')
|
||||||
&& null !== $episode->episode
|
&& null !== $episode->episodeNumber
|
||||||
&& null !== $episode->season
|
&& null !== $episode->season
|
||||||
)
|
)
|
||||||
->rekey(fn($episode) => $episode->episode);
|
->rekey(fn($episode) => $episode->episodeNumber);
|
||||||
$this->logger->info('> [MonitorTvSeasonHandler] Found ' . count($downloadedEpisodes) . ' downloaded episodes for title: ' . $monitor->getTitle());
|
$this->logger->info('> [MonitorTvSeasonHandler] Found ' . count($downloadedEpisodes) . ' downloaded episodes for title: ' . $monitor->getTitle());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
59
src/Download/Action/Handler/MonitorHandlerTrait.php
Normal file
59
src/Download/Action/Handler/MonitorHandlerTrait.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Download\Action\Handler;
|
||||||
|
|
||||||
|
use Aimeos\Map;
|
||||||
|
use App\Monitor\Framework\Entity\Monitor;
|
||||||
|
use App\Monitor\Framework\Repository\MonitorRepository;
|
||||||
|
use App\Tmdb\Dto\TmdbEpisodeDto;
|
||||||
|
use App\Tmdb\TmdbClient;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
trait MonitorHandlerTrait
|
||||||
|
{
|
||||||
|
private MonitorRepository $monitorRepository;
|
||||||
|
private LoggerInterface $logger;
|
||||||
|
private TmdbClient $tmdb;
|
||||||
|
|
||||||
|
private function episodeReleasedAfterMonitorCreated(
|
||||||
|
string|DateTimeImmutable $monitorStartDate,
|
||||||
|
TmdbEpisodeDto $episodeInShow
|
||||||
|
): bool {
|
||||||
|
$monitorStartDate = Carbon::parse($monitorStartDate)->setTime(0, 0);
|
||||||
|
$episodeAirDate = Carbon::parse($episodeInShow->airDate);
|
||||||
|
return $episodeAirDate >= $monitorStartDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function episodeExists(TmdbEpisodeDto $episodeInShow, Map $downloadedEpisodes): bool
|
||||||
|
{
|
||||||
|
return $downloadedEpisodes->filter(
|
||||||
|
fn(object $episode) => $episode->episode === $episodeInShow->episodeNumber
|
||||||
|
&& $episode->season === $episodeInShow->seasonNumber
|
||||||
|
)->count() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function monitorExists(Monitor $monitor, TmdbEpisodeDto $episode): bool
|
||||||
|
{
|
||||||
|
return $this->monitorRepository->findOneBy([
|
||||||
|
'imdbId' => $monitor->getImdbId(),
|
||||||
|
'title' => $monitor->getTitle(),
|
||||||
|
'monitorType' => 'tvepisode',
|
||||||
|
'season' => $episode->seasonNumber,
|
||||||
|
'episode' => $episode->episodeNumber,
|
||||||
|
'status' => ['New', 'Active', 'In Progress']
|
||||||
|
]) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function refreshData(Monitor $monitor)
|
||||||
|
{
|
||||||
|
if (null === $monitor->getPoster()) {
|
||||||
|
$this->logger->info('> [MonitorTvShowHandler] Refreshing poster for "' . $monitor->getTitle() . '"');
|
||||||
|
$poster = $this->tmdb->tvshowDetails($monitor->getImdbId())->poster;
|
||||||
|
if (null !== $poster && "" !== $poster) {
|
||||||
|
$monitor->setPoster($poster);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ class WebController extends AbstractController
|
|||||||
private readonly Environment $renderer,
|
private readonly Environment $renderer,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[Route('/monitors', name: 'app_monitors', methods: ['GET'])]
|
#[Route('/monitors', name: 'app.monitors', methods: ['GET'])]
|
||||||
public function addMonitor()
|
public function addMonitor()
|
||||||
{
|
{
|
||||||
return $this->render('monitor/index.html.twig');
|
return $this->render('monitor/index.html.twig');
|
||||||
|
|||||||
@@ -33,9 +33,7 @@ class MonitorDispatcher
|
|||||||
'tvshows' => MonitorTvShowCommand::class,
|
'tvshows' => MonitorTvShowCommand::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
$monitors = $this->monitorRepository->findBy([
|
$monitors = $this->monitorRepository->findBy(['status' => ['New', 'Active']]);
|
||||||
'status' => ['New', 'Active'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
foreach ($monitors as $monitor) {
|
foreach ($monitors as $monitor) {
|
||||||
$monitor->setStatus('In Progress');
|
$monitor->setStatus('In Progress');
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use Tmdb\Event\Listener\Request\ApiTokenRequestListener;
|
|||||||
use Tmdb\Event\Listener\Request\ContentTypeJsonRequestListener;
|
use Tmdb\Event\Listener\Request\ContentTypeJsonRequestListener;
|
||||||
use Tmdb\Event\Listener\Request\UserAgentRequestListener;
|
use Tmdb\Event\Listener\Request\UserAgentRequestListener;
|
||||||
use Tmdb\Event\RequestEvent;
|
use Tmdb\Event\RequestEvent;
|
||||||
|
use Tmdb\Repository\DiscoverRepository;
|
||||||
use Tmdb\Repository\MovieRepository;
|
use Tmdb\Repository\MovieRepository;
|
||||||
use Tmdb\Repository\SearchRepository;
|
use Tmdb\Repository\SearchRepository;
|
||||||
use Tmdb\Repository\TvEpisodeRepository;
|
use Tmdb\Repository\TvEpisodeRepository;
|
||||||
@@ -30,6 +31,7 @@ use Tmdb\Token\Api\BearerToken;
|
|||||||
class TmdbClient
|
class TmdbClient
|
||||||
{
|
{
|
||||||
const POSTER_IMG_PATH = "https://image.tmdb.org/t/p/w500";
|
const POSTER_IMG_PATH = "https://image.tmdb.org/t/p/w500";
|
||||||
|
const APPEND_TO_RESPONSE = 'external_ids,credits,watch/providers';
|
||||||
|
|
||||||
protected Client $client;
|
protected Client $client;
|
||||||
protected MovieRepository $movieRepository;
|
protected MovieRepository $movieRepository;
|
||||||
@@ -38,6 +40,8 @@ class TmdbClient
|
|||||||
protected TvEpisodeRepository $tvEpisodeRepository;
|
protected TvEpisodeRepository $tvEpisodeRepository;
|
||||||
protected SearchRepository $searchRepository;
|
protected SearchRepository $searchRepository;
|
||||||
|
|
||||||
|
protected DiscoverRepository $discoverRepository;
|
||||||
|
|
||||||
protected array $mediaTypeMap = [
|
protected array $mediaTypeMap = [
|
||||||
MediaType::Movie->value => MediaType::Movie->value,
|
MediaType::Movie->value => MediaType::Movie->value,
|
||||||
MediaType::TvShow->value => MediaType::TvShow->value,
|
MediaType::TvShow->value => MediaType::TvShow->value,
|
||||||
@@ -48,11 +52,14 @@ class TmdbClient
|
|||||||
|
|
||||||
protected $repos = [];
|
protected $repos = [];
|
||||||
|
|
||||||
|
private ?string $originalLanguage = 'en';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly SerializerInterface $serializer,
|
private readonly SerializerInterface $serializer,
|
||||||
private readonly CacheItemPoolInterface $cache,
|
private readonly CacheItemPoolInterface $cache,
|
||||||
private readonly EventDispatcherInterface $eventDispatcher,
|
private readonly EventDispatcherInterface $eventDispatcher,
|
||||||
#[Autowire(env: 'TMDB_API')] string $apiKey,
|
#[Autowire(env: 'TMDB_API')] string $apiKey,
|
||||||
|
#[Autowire(env: 'TMDB_ORIGINAL_LANGUAGE')] ?string $originalLanguage = null,
|
||||||
) {
|
) {
|
||||||
$this->client = new Client(
|
$this->client = new Client(
|
||||||
[
|
[
|
||||||
@@ -74,7 +81,7 @@ class TmdbClient
|
|||||||
'hydration' => [
|
'hydration' => [
|
||||||
'event_listener_handles_hydration' => false,
|
'event_listener_handles_hydration' => false,
|
||||||
'only_for_specified_models' => []
|
'only_for_specified_models' => []
|
||||||
]
|
],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -107,11 +114,14 @@ class TmdbClient
|
|||||||
$this->tvSeasonRepository = new TvSeasonRepository($this->client);
|
$this->tvSeasonRepository = new TvSeasonRepository($this->client);
|
||||||
$this->tvEpisodeRepository = new TvEpisodeRepository($this->client);
|
$this->tvEpisodeRepository = new TvEpisodeRepository($this->client);
|
||||||
$this->searchRepository = new SearchRepository($this->client);
|
$this->searchRepository = new SearchRepository($this->client);
|
||||||
|
$this->discoverRepository = new DiscoverRepository($this->client);
|
||||||
|
|
||||||
$this->repos = [
|
$this->repos = [
|
||||||
MediaType::Movie->value => $this->movieRepository,
|
MediaType::Movie->value => $this->movieRepository,
|
||||||
MediaType::TvShow->value => $this->tvRepository,
|
MediaType::TvShow->value => $this->tvRepository,
|
||||||
MediaType::TvEpisode->value => $this->tvEpisodeRepository,
|
MediaType::TvEpisode->value => $this->tvEpisodeRepository,
|
||||||
];
|
];
|
||||||
|
$this->originalLanguage = $originalLanguage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function search(string $term): TmdbResult|Map
|
public function search(string $term): TmdbResult|Map
|
||||||
@@ -133,7 +143,7 @@ class TmdbClient
|
|||||||
{
|
{
|
||||||
$tmdbId = $this->findByImdbId($imdbId)['id'];
|
$tmdbId = $this->findByImdbId($imdbId)['id'];
|
||||||
return $this->parseResult(
|
return $this->parseResult(
|
||||||
$this->movieRepository->getApi()->getMovie($tmdbId, ['append_to_response' => 'external_ids,credits']),
|
$this->movieRepository->getApi()->getMovie($tmdbId, ['append_to_response' => static::APPEND_TO_RESPONSE]),
|
||||||
MediaType::Movie->value,
|
MediaType::Movie->value,
|
||||||
$imdbId
|
$imdbId
|
||||||
);
|
);
|
||||||
@@ -142,7 +152,7 @@ class TmdbClient
|
|||||||
public function tvshowDetails(string $imdbId): ?TmdbResult
|
public function tvshowDetails(string $imdbId): ?TmdbResult
|
||||||
{
|
{
|
||||||
$tmdbId = $this->findByImdbId($imdbId)['id'];
|
$tmdbId = $this->findByImdbId($imdbId)['id'];
|
||||||
$media = $this->tvRepository->getApi()->getTvShow($tmdbId, ['append_to_response' => 'external_ids,credits']);
|
$media = $this->tvRepository->getApi()->getTvShow($tmdbId, ['append_to_response' => static::APPEND_TO_RESPONSE]);
|
||||||
|
|
||||||
$media['seasons'] = Map::from($media['seasons'])->filter(function ($data) {
|
$media['seasons'] = Map::from($media['seasons'])->filter(function ($data) {
|
||||||
return $data['season_number'] !== 0 &&
|
return $data['season_number'] !== 0 &&
|
||||||
@@ -163,7 +173,7 @@ class TmdbClient
|
|||||||
|
|
||||||
public function tvSeasonDetails(string $tmdbId, int $season): array
|
public function tvSeasonDetails(string $tmdbId, int $season): array
|
||||||
{
|
{
|
||||||
$result = $this->tvSeasonRepository->getApi()->getSeason($tmdbId, $season, ['append_to_response' => 'external_ids,credits']);
|
$result = $this->tvSeasonRepository->getApi()->getSeason($tmdbId, $season, ['append_to_response' => static::APPEND_TO_RESPONSE]);
|
||||||
$result['episodes'] = Map::from($result['episodes'])->map(function ($data) {
|
$result['episodes'] = Map::from($result['episodes'])->map(function ($data) {
|
||||||
$data['still_path'] = self::POSTER_IMG_PATH . $data['still_path'];
|
$data['still_path'] = self::POSTER_IMG_PATH . $data['still_path'];
|
||||||
$data['poster'] = $data['still_path'];
|
$data['poster'] = $data['still_path'];
|
||||||
@@ -174,7 +184,7 @@ class TmdbClient
|
|||||||
|
|
||||||
public function tvEpisodeDetails(string $tmdbId, string $showImdbId, int $season, int $episode): TmdbResult|TmdbEpisodeDto|null
|
public function tvEpisodeDetails(string $tmdbId, string $showImdbId, int $season, int $episode): TmdbResult|TmdbEpisodeDto|null
|
||||||
{
|
{
|
||||||
$result = $this->tvEpisodeRepository->getApi()->getEpisode($tmdbId, $season, $episode, ['append_to_response' => 'external_ids,credits']);
|
$result = $this->tvEpisodeRepository->getApi()->getEpisode($tmdbId, $season, $episode, ['append_to_response' => static::APPEND_TO_RESPONSE]);
|
||||||
return $this->parseResult(
|
return $this->parseResult(
|
||||||
$result,
|
$result,
|
||||||
MediaType::TvEpisode->value,
|
MediaType::TvEpisode->value,
|
||||||
@@ -193,7 +203,10 @@ class TmdbClient
|
|||||||
|
|
||||||
public function popularMovies(int $resultCount = 6): Map
|
public function popularMovies(int $resultCount = 6): Map
|
||||||
{
|
{
|
||||||
$results = $this->movieRepository->getApi()->getPopular();
|
$results = $this->discoverRepository->getApi()->discoverMovies([
|
||||||
|
'with_original_language' => $this->originalLanguage,
|
||||||
|
'append_to_response' => 'external_ids,watch/providers',
|
||||||
|
]);
|
||||||
$results['results'] = Map::from($results['results'])->map(function ($result) {
|
$results['results'] = Map::from($results['results'])->map(function ($result) {
|
||||||
$result['media_type'] = MediaType::Movie->value;
|
$result['media_type'] = MediaType::Movie->value;
|
||||||
return $result;
|
return $result;
|
||||||
@@ -206,7 +219,10 @@ class TmdbClient
|
|||||||
|
|
||||||
public function popularTvShows(int $resultCount = 6): Map
|
public function popularTvShows(int $resultCount = 6): Map
|
||||||
{
|
{
|
||||||
$results = $this->tvRepository->getApi()->getPopular();
|
$results = $this->discoverRepository->getApi()->discoverTv([
|
||||||
|
'with_original_language' => $this->originalLanguage,
|
||||||
|
'append_to_response' => 'external_ids,watch/providers',
|
||||||
|
]);
|
||||||
$results['results'] = Map::from($results['results'])->map(function ($result) {
|
$results['results'] = Map::from($results['results'])->map(function ($result) {
|
||||||
$result['media_type'] = MediaType::TvShow->value;
|
$result['media_type'] = MediaType::TvShow->value;
|
||||||
return $result;
|
return $result;
|
||||||
|
|||||||
@@ -11,14 +11,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block javascripts %}
|
{% block javascripts %}
|
||||||
{% block pre_js %}{% endblock %}
|
{% block importmap %}{{ importmap('app') }}{% endblock %}
|
||||||
|
|
||||||
{% block importmap %}
|
|
||||||
{{ importmap('app') }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block post_js %}{% endblock %}
|
|
||||||
|
|
||||||
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.19/index.global.min.js'></script>
|
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.19/index.global.min.js'></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
@@ -29,9 +22,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-span-6 md:col-span-5 h-screen overflow-y-scroll">
|
<div class="col-span-6 md:col-span-5 h-screen overflow-y-scroll">
|
||||||
<twig:Header />
|
<twig:Header />
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex flex-col md:flex-row justify-between md:items-center">
|
||||||
<h2 class="px-4 mt-4 mb-2 text-3xl font-bold text-gray-50">{% block h2 %}{% endblock %}</h2>
|
<h2 class="px-4 mt-4 mb-4 md:mb-2 text-3xl font-bold text-gray-50">{% block h2 %}{% endblock %}</h2>
|
||||||
<div class="flex mt-4 gap-2 items-center grow-0 md:px-4">
|
<div class="flex mx-4 mb-2 md:mt-4 gap-2 items-center grow-0 md:px-4">
|
||||||
{% block action_buttons %}{% endblock %}
|
{% block action_buttons %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
{% 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">
|
||||||
<td colspan="100%" class="py-2 whitespace-nowrap bg-orange-500/80 uppercase text-xs font-medium text-center truncate dark:text-black">
|
<td colspan="100%" class="py-2 whitespace-nowrap bg-orange-500/80 uppercase text-xs font-medium text-center truncate dark:text-black">
|
||||||
<a href="{{ path('app_monitors') }}">View All Monitors</a>
|
<a href="{{ path('app.monitors') }}">View All Monitors</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('app_monitors') }}"
|
<a href="{{ path('app.monitors') }}"
|
||||||
class="block rounded-lg
|
class="block rounded-lg
|
||||||
bg-orange-500 hover:bg-opacity-80 bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-60
|
bg-orange-500 hover:bg-opacity-80 bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-60
|
||||||
px-4 py-2 text-sm font-medium text-gray-50">
|
px-4 py-2 text-sm font-medium text-gray-50">
|
||||||
|
|||||||
@@ -4,31 +4,20 @@
|
|||||||
{% block h2 %}Dashboard{% endblock %}
|
{% block h2 %}Dashboard{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="p-4 z-10">
|
<div class="p-4 flex flex-col grow gap-4 z-10">
|
||||||
<div class="grid-stack gs-2">
|
<div class="flex flex-col md:flex-row gap-4">
|
||||||
<div class="grid-stack-item" gs-x="1">
|
<twig:Card title="Active Downloads" class="w-full">
|
||||||
<div class="grid-stack-item-content">
|
<twig:DownloadList :type="'active'" />
|
||||||
<twig:Card title="Active Downloads">
|
</twig:Card>
|
||||||
<twig:DownloadList :type="'active'" />
|
|
||||||
</twig:Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid-stack-item" gs-x="2">
|
<twig:Card title="Recent Downloads" class="w-full">
|
||||||
<div class="grid-stack-item-content">
|
<twig:DownloadList :type="'complete'" />
|
||||||
<twig:Card title="Complete Downloads" >
|
</twig:Card>
|
||||||
<twig:DownloadList :type="'complete'" />
|
</div>
|
||||||
</twig:Card>
|
<div class="flex flex-col md:flex-row gap-4">
|
||||||
</div>
|
<twig:Card title="Monitors" class="w-full">
|
||||||
</div>
|
<twig:MonitorList :type="'active'" :isWidget="true" />
|
||||||
|
</twig:Card>
|
||||||
<div class="grid-stack-item" gs-x="3">
|
|
||||||
<div class="grid-stack-item-content">
|
|
||||||
<twig:Card title="Active Monitors">
|
|
||||||
<twig:MonitorList :type="'active'" />
|
|
||||||
</twig:Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<twig:Card title="Popular Movies" contentClass="grid grid-cols-2 gap-4 md:flex md:flex-row md:justify-between w-full">
|
<twig:Card title="Popular Movies" contentClass="grid grid-cols-2 gap-4 md:flex md:flex-row md:justify-between w-full">
|
||||||
@@ -56,6 +45,5 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</twig:Card>
|
</twig:Card>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-stack" data-controller="dashboard-widgets"></div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -15,6 +15,12 @@
|
|||||||
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.19/index.global.min.js'></script>
|
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.19/index.global.min.js'></script>
|
||||||
|
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
|
<div class="mb-4 text-white">
|
||||||
|
<a href="{{ path('app.monitors') }}" class="btn btn-primary inline-flex items-center gap-2">
|
||||||
|
<twig:ux:icon name="fluent-mdl2:back" width="14" /> Back to Monitors
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<twig:Card title="Upcoming episodes of shows your monitoring">
|
<twig:Card title="Upcoming episodes of shows your monitoring">
|
||||||
<a href="{{ path('app.monitors.ical', {email: app.user.email}) }}" title="Subscribe to the 'Upcoming Episodes' calendar via iCal. Click to export the events to a .ics file or copy the link and use it to subscribe in a calendar app that supports iCal/ics calendars." class="mb-2 self-end dark:text-white decoration-underline">
|
<a href="{{ path('app.monitors.ical', {email: app.user.email}) }}" title="Subscribe to the 'Upcoming Episodes' calendar via iCal. Click to export the events to a .ics file or copy the link and use it to subscribe in a calendar app that supports iCal/ics calendars." class="mb-2 self-end dark:text-white decoration-underline">
|
||||||
<twig:ux:icon name="lets-icons:calendar-add-light" width="24" class="text-orange-500" />
|
<twig:ux:icon name="lets-icons:calendar-add-light" width="24" class="text-orange-500" />
|
||||||
|
|||||||
@@ -5,6 +5,11 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="px-4 py-2">
|
<div class="px-4 py-2">
|
||||||
|
<div class="mb-4 text-white">
|
||||||
|
<a href="{{ path('app.monitors') }}" class="btn btn-primary inline-flex items-center gap-2">
|
||||||
|
<twig:ux:icon name="fluent-mdl2:back" width="14" /> Back to Monitors
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<twig:Card title="Viewing your monitors for {{ monitor.title }}">
|
<twig:Card title="Viewing your monitors for {{ monitor.title }}">
|
||||||
<div class="p-2 md:p-4 flex flex-col md:flex-row gap-6">
|
<div class="p-2 md:p-4 flex flex-col md:flex-row gap-6">
|
||||||
{% if results.media.poster != null %}
|
{% if results.media.poster != null %}
|
||||||
@@ -53,7 +58,6 @@
|
|||||||
|
|
||||||
{% if results.media.genres != null %}
|
{% if results.media.genres != null %}
|
||||||
<div id="genres" class="text-gray-50 my-4">
|
<div id="genres" class="text-gray-50 my-4">
|
||||||
{# <strong>Genres</strong>: <br />#}
|
|
||||||
{% for genre in results.media.genres %}
|
{% for genre in results.media.genres %}
|
||||||
<small class="px-2 py-1 border border-orange-500 rounded-full">{{ genre }}</small>
|
<small class="px-2 py-1 border border-orange-500 rounded-full">{{ genre }}</small>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -65,10 +69,10 @@
|
|||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="flex flex-col grow text-white">
|
<div class="flex flex-col grow text-white">
|
||||||
<strong class="mb-1">In Your Library</strong>
|
<strong class="mb-1">In Your Library</strong>
|
||||||
<div class="flex flex-col md:flex-row border-t-orange-500 text-xs gap-2">
|
<div class="flex flex-col md:flex-row border-t-orange-500 text-xs gap-4">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-sm mb-1">Seasons</span>
|
<span class="text-sm mb-1">Seasons</span>
|
||||||
<div class="flex flex-col md:flex-row border p-2 border-orange-500 rounded-lg text-xs items-center">
|
<div class="flex flex-row border p-2 border-orange-500 rounded-lg text-xs items-center">
|
||||||
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-orange-500 rounded-lg text-white">
|
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-orange-500 rounded-lg text-white">
|
||||||
<span>{{ library.seasonCount }}</span> full
|
<span>{{ library.seasonCount }}</span> full
|
||||||
</span>
|
</span>
|
||||||
@@ -83,7 +87,7 @@
|
|||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-sm mb-1">Episodes</span>
|
<span class="text-sm mb-1">Episodes</span>
|
||||||
<div class="flex flex-col md:flex-row border p-2 border-orange-500 rounded-lg text-xs items-center">
|
<div class="flex flex-row border p-2 border-orange-500 rounded-lg text-xs items-center">
|
||||||
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-cyan-500 rounded-lg text-white">
|
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-cyan-500 rounded-lg text-white">
|
||||||
<span>{{ library.episodeCount }}</span> existing
|
<span>{{ library.episodeCount }}</span> existing
|
||||||
</span>
|
</span>
|
||||||
@@ -95,14 +99,14 @@
|
|||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-sm mb-1">Monitors</span>
|
<span class="text-sm mb-1">Monitors</span>
|
||||||
<div class="flex flex-col md:flex-row border p-2 border-orange-500 rounded-lg text-xs items-center">
|
<div class="flex flex-row border p-2 border-orange-500 rounded-lg text-xs items-center">
|
||||||
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-pink-500 rounded-lg text-white">
|
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-red-500 rounded-lg text-white">
|
||||||
<span>{{ library.monitorCount }}</span> total
|
<span>{{ library.monitorCount }}</span> total
|
||||||
</span>
|
</span>
|
||||||
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-pink-500 rounded-lg text-white">
|
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-red-500 rounded-lg text-white">
|
||||||
<span>{{ library.activeMonitorCount }}</span> active
|
<span>{{ library.activeMonitorCount }}</span> active
|
||||||
</span>
|
</span>
|
||||||
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-pink-500 rounded-lg text-white">
|
<span class="py-1 px-1.5 mr-1 grow-0 font-bold bg-red-500 rounded-lg text-white">
|
||||||
<span>{{ library.completeMonitorCount }}</span> complete
|
<span>{{ library.completeMonitorCount }}</span> complete
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user