Compare commits
7 Commits
v0.21.3
...
8c0ec98c20
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c0ec98c20 | |||
| 2c9138290a | |||
| c1a6cddb8f | |||
| 64d3fbbddb | |||
| 32389cb27a | |||
| 5e48fdb978 | |||
| 5f54e48b3f |
46
assets/controllers/action_button_controller.js
Normal file
46
assets/controllers/action_button_controller.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
/*
|
||||
* The following line makes this controller "lazy": it won't be downloaded until needed
|
||||
* See https://symfony.com/bundles/StimulusBundle/current/index.html#lazy-stimulus-controllers
|
||||
*/
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
|
||||
initialize() {
|
||||
// Called once when the controller is first instantiated (per element)
|
||||
|
||||
// Here you can initialize variables, create scoped callables for event
|
||||
// listeners, instantiate external libraries, etc.
|
||||
// this._fooBar = this.fooBar.bind(this)
|
||||
}
|
||||
|
||||
connect() {
|
||||
// Called every time the controller is connected to the DOM
|
||||
// (on page load, when it's added to the DOM, moved in the DOM, etc.)
|
||||
|
||||
// Here you can add event listeners on the element or target elements,
|
||||
// add or remove classes, attributes, dispatch custom events, etc.
|
||||
// this.fooTarget.addEventListener('click', this._fooBar)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
default() {
|
||||
console.log('Looks like you need to add an action to your action button...')
|
||||
}
|
||||
|
||||
monitorDispatch() {
|
||||
fetch('/api/monitor/dispatch')
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { Controller } from '@hotwired/stimulus';
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
static targets = ['button', 'options']
|
||||
static outlets = ['result-filter']
|
||||
static outlets = ['result-filter', 'dialog']
|
||||
static values = {
|
||||
tmdbId: String,
|
||||
imdbId: String,
|
||||
@@ -54,6 +54,9 @@ export default class extends Controller {
|
||||
title: this.titleValue,
|
||||
monitorType: 'tvshows',
|
||||
});
|
||||
if (this.hasDialogOutlet) {
|
||||
this.dialogOutlet.close();
|
||||
}
|
||||
}
|
||||
|
||||
async monitorSeason() {
|
||||
|
||||
@@ -25,6 +25,7 @@ export default class extends Controller {
|
||||
static outlets = ['movie-results', 'tv-results', 'tv-episode-list']
|
||||
static targets = ['resolution', 'codec', 'language', 'provider', 'season', 'quality', 'selectAll', 'downloadSelected']
|
||||
static values = {
|
||||
'imdbId': String,
|
||||
'media-type': String,
|
||||
'episodes': Array,
|
||||
'reverseMappedQualities': Object,
|
||||
@@ -156,6 +157,14 @@ export default class extends Controller {
|
||||
this.selectAllTarget.checked = false;
|
||||
}
|
||||
|
||||
downloadSeason() {
|
||||
fetch(`/api/download/${this.imdbIdValue}/${this.activeFilter['season']}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
selectAllEpisodes() {
|
||||
this.tvResultsOutlets.forEach((episode) => {
|
||||
if (episode.isActive()) {
|
||||
|
||||
@@ -40,7 +40,7 @@ services:
|
||||
tty: true
|
||||
environment:
|
||||
TZ: America/Chicago
|
||||
command: php /app/bin/console messenger:consume media_cache -vv --time-limit=3600
|
||||
command: php /app/bin/console messenger:consume async -vv --time-limit=3600
|
||||
|
||||
|
||||
scheduler:
|
||||
|
||||
@@ -2,15 +2,20 @@
|
||||
|
||||
namespace App\Base\Framework\Command;
|
||||
|
||||
use App\User\Framework\Entity\Preference;
|
||||
use App\User\Framework\Entity\UserPreference;
|
||||
use App\User\Framework\Repository\PreferenceOptionRepository;
|
||||
use App\User\Framework\Repository\PreferencesRepository;
|
||||
use App\User\Framework\Repository\UserRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
|
||||
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'db:seed',
|
||||
@@ -50,17 +55,23 @@ class SeedDatabaseCommand extends Command
|
||||
$preferences = $this->getPreferences();
|
||||
|
||||
foreach ($preferences as $preference) {
|
||||
if ($this->preferenceRepository->find($preference['id'])) {
|
||||
continue;
|
||||
$isNewRecord = false;
|
||||
$preferenceRecord = $this->preferenceRepository->findOneBy(['id' => $preference['id']]);
|
||||
if (null === $preferenceRecord) {
|
||||
$isNewRecord = true;
|
||||
$preferenceRecord = new Preference();
|
||||
}
|
||||
|
||||
$this->preferenceRepository->getEntityManager()->persist((new \App\User\Framework\Entity\Preference())
|
||||
$preferenceRecord
|
||||
->setId($preference['id'])
|
||||
->setName($preference['name'])
|
||||
->setDescription($preference['description'])
|
||||
->setEnabled($preference['enabled'])
|
||||
->setType($preference['type'])
|
||||
);
|
||||
->setType($preference['type']);
|
||||
|
||||
if (true === $isNewRecord) {
|
||||
$this->preferenceRepository->getEntityManager()->persist($preferenceRecord);
|
||||
}
|
||||
}
|
||||
|
||||
$this->preferenceRepository->getEntityManager()->flush();
|
||||
|
||||
@@ -54,10 +54,12 @@ class ApiController extends AbstractController
|
||||
}
|
||||
|
||||
#[Route('/api/monitor/dispatch', name: 'api_monitor_dispatch', methods: ['GET'])]
|
||||
public function dispatch(MonitorDispatcher $dispatcher): Response
|
||||
public function dispatch(MonitorDispatcher $dispatcher, Broadcaster $broadcaster): Response
|
||||
{
|
||||
$dispatcher();
|
||||
|
||||
$broadcaster->alert('Success', 'The monitor job has been dispatched.');
|
||||
|
||||
return $this->json([
|
||||
'status' => 200,
|
||||
'message' => 'Manually dispatched MonitorDispatcher'
|
||||
|
||||
10
src/Twig/Components/ActionButton.php
Normal file
10
src/Twig/Components/ActionButton.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Twig\Components;
|
||||
|
||||
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
|
||||
|
||||
#[AsTwigComponent]
|
||||
final class ActionButton
|
||||
{
|
||||
}
|
||||
@@ -20,7 +20,12 @@
|
||||
</div>
|
||||
<div class="col-span-6 md:col-span-5 h-screen overflow-y-scroll">
|
||||
<twig:Header />
|
||||
<h2 class="px-4 mt-4 mb-2 text-3xl font-bold text-gray-50">{% block h2 %}{% endblock %}</h2>
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="px-4 mt-4 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">
|
||||
{% block action_buttons %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
13
templates/components/ActionButton.html.twig
Normal file
13
templates/components/ActionButton.html.twig
Normal file
@@ -0,0 +1,13 @@
|
||||
<button
|
||||
class="h-6 bg-{{ color|default('orange') }}-500/80 hover:bg-{{ color|default('orange') }}-600/80 px-2 text-{{ text_color|default('white') }} rounded-ms text-sm font-semibold"
|
||||
|
||||
{% if custom_controller|default and custom_action|default %}
|
||||
{{ attributes.defaults(stimulus_controller(custom_controller, custom_controller_vars|default({}))) }}
|
||||
{{ stimulus_action(custom_controller, custom_action|default('default'), custom_action_event|default('click'), custom_action_params|default({})) }}
|
||||
{% else %}
|
||||
{{ attributes.defaults(stimulus_controller('action_button')) }}
|
||||
{{ stimulus_action('action_button', action|default('default')) }}
|
||||
{% endif %}
|
||||
>
|
||||
{{ text|default('button') }}
|
||||
</button>
|
||||
@@ -1,10 +1,10 @@
|
||||
<div id="filter" class="flex flex-col gap-4"
|
||||
{{ stimulus_controller('result_filter', {reverseMappedQualities: this.reverseMappedQualities}) }}
|
||||
{{ stimulus_controller('result_filter', {reverseMappedQualities: this.reverseMappedQualities, imdbId: results.media.imdbId}) }}
|
||||
data-result-filter-media-type-value="{{ results.media.mediaType }}"
|
||||
data-result-filter-movie-results-outlet=".results"
|
||||
data-result-filter-tv-results-outlet=".results"
|
||||
data-result-filter-tv-episode-list-outlet=".episode-list"
|
||||
data-action="change->result-filter#filter movie-results:optionsLoaded@window->result-filter#loadOptions tv-results:optionsLoaded@window->result-filter#loadOptions"
|
||||
data-action="change->result-filter#filter movie-results:optionsLoaded@window->result-filter#loadOptions tv-results:optionsLoaded@window->result-filter#loadOptions action-button:downloadSeason@window->result-filter#downloadSeason"
|
||||
>
|
||||
<div class="w-full p-4 flex flex-col md:flex-row gap-4 bg-stone-500 text-md text-gray-500 dark:text-gray-50 rounded-lg">
|
||||
<label for="resolution">
|
||||
@@ -94,10 +94,19 @@
|
||||
|
||||
{% if results.media.mediaType == "tvshows" %}
|
||||
<div class="flex flex-row gap-2 justify-end px-8">
|
||||
<button class="px-1.5 py-1 bg-green-600 hover:bg-green-700 rounded-md text-sm"
|
||||
<twig:Modal heading="Back up a sec!" button_text="Download Season" submit_action="{{ stimulus_action('result_filter', 'downloadSeason', 'click')|stimulus_action('dialog', 'close') }}" button_class="px-1.5 py-1 bg-green-600 rounded-ms text-sm font-semibold" show_cancel show_submit>
|
||||
Downloading an entire season this way will use the filter from your
|
||||
<a href="{{ path('app_user_preferences') }}" class="text-underline">preferences</a> to choose
|
||||
the appropriate file(s).
|
||||
<br /><br />
|
||||
Do you wish to download <strong>season {{ results.season }}</strong> of "<strong>{{ results.media.title }}</strong>"?
|
||||
</twig:Modal>
|
||||
|
||||
<button class="px-1.5 py-1 bg-green-600 hover:bg-green-700 rounded-ms text-sm font-semibold"
|
||||
{{ stimulus_target('result_filter', 'downloadSelected') }}
|
||||
{{ stimulus_action('result_filter', 'downloadSelectedEpisodes', 'click') }}
|
||||
>Download Selected</button>
|
||||
|
||||
<input type="checkbox" name="selectAll" id="selectAll"
|
||||
{{ stimulus_target('result_filter', 'selectAll') }}
|
||||
{{ stimulus_action('result_filter', 'selectAllEpisodes', 'change') }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div{{ attributes }} data-controller="dialog" data-action="click->dialog#backdropClose" class="flex flex-row items-center">
|
||||
<div{{ attributes }} data-controller="dialog" data-action="click->dialog#backdropClose" class="modal flex flex-row items-center {{ container_class|default('') }}">
|
||||
<dialog data-dialog-target="dialog" class="py-3 px-4 w-[30rem] rounded-md">
|
||||
<h2 class="mb-4 text-2xl font-bold">{{ heading }}</h2>
|
||||
|
||||
@@ -22,5 +22,5 @@
|
||||
{% endif %}
|
||||
</dialog>
|
||||
|
||||
<button type="button" data-action="dialog#open">{{ button_text|raw }}</button>
|
||||
<button type="button" class="{{ button_class|default('') }}" data-action="dialog#open">{{ button_text|raw }}</button>
|
||||
</div>
|
||||
@@ -3,6 +3,10 @@
|
||||
{% block title %}Monitors — Torsearch{% endblock %}
|
||||
{% block h2 %}Monitors{% endblock %}
|
||||
|
||||
{% block action_buttons %}
|
||||
<twig:ActionButton action="monitorDispatch" text="Run Monitors" />
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="px-4 py-2">
|
||||
<twig:Card title="Active Monitors">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="p-4 flex flex-col grow gap-4">
|
||||
<h2 class="mb-2 text-3xl font-bold text-gray-50">Media Results</h2>
|
||||
<div class="flex flex-row w-full gap-2">
|
||||
<twig:Card title="" contentClass="flex flex-col gap-4 justify-between w-full text-gray-50">
|
||||
<twig:Card title="" class="w-full" contentClass="flex flex-col gap-4 justify-between w-full text-gray-50">
|
||||
<div class="p-2 md:p-4 flex flex-col md:flex-row gap-6">
|
||||
{% if results.media.poster != null %}
|
||||
<img class="w-full md:w-40 rounded-lg" src="{{ results.media.poster }}" />
|
||||
@@ -22,87 +22,30 @@
|
||||
{{ results.media.title }} - {{ results.media.year }}
|
||||
</h3>
|
||||
|
||||
{# <div data-controller="dropdown" class="relative"#}
|
||||
{# {{ stimulus_controller('monitor_button', {#}
|
||||
{# tmdbId: results.media.tmdbId,#}
|
||||
{# imdbId: results.media.imdbId,#}
|
||||
{# title: results.media.title,#}
|
||||
{# })}}#}
|
||||
{# data-monitor-button-result-filter-outlet="#filter"#}
|
||||
{# >#}
|
||||
{# <button type="button" data-action="dropdown#toggle click@window->dropdown#hide"#}
|
||||
{# class="h-8 text-white bg-green-800 bg-opacity-60 font-medium rounded-lg text-sm#}
|
||||
{# px-2 py-1.5 text-center inline-flex items-center hover:bg-green-900 border-2#}
|
||||
{# border-green-500">#}
|
||||
{# Monitor#}
|
||||
{# <svg class="w-2.5 h-2.5 ms-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">#}
|
||||
{# <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4" /></svg>#}
|
||||
{# </svg>#}
|
||||
{# </button>#}
|
||||
|
||||
{# <div#}
|
||||
{# data-dropdown-target="menu"#}
|
||||
{# class="hidden transition transform origin-top-right absolute right-0#}
|
||||
{# flex flex-col rounded-md shadow-sm w-44 bg-green-800 border-2 border-green-500 mt-1"#}
|
||||
{# data-transition-enter-from="opacity-0 scale-95"#}
|
||||
{# data-transition-enter-to="opacity-100 scale-100"#}
|
||||
{# data-transition-leave-from="opacity-100 scale-100"#}
|
||||
{# data-transition-leave-to="opacity-0 scale-95"#}
|
||||
{# >#}
|
||||
{# <a href="#"#}
|
||||
{# data-action="dropdown#toggle"#}
|
||||
{# class="backdrop-filter p-2 bg-opacity-100 hover:bg-green-950 rounded-t-md"#}
|
||||
{# >#}
|
||||
{# Entire Series#}
|
||||
{# </a>#}
|
||||
{# <a href="#"#}
|
||||
{# data-action="dropdown#toggle"#}
|
||||
{# class="backdrop-filter p-2 bg-opacity-100 hover:bg-green-950 rounded-b-md"#}
|
||||
{# >#}
|
||||
{# Season#}
|
||||
{# </a>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
|
||||
|
||||
{% if results.media.mediaType == "tvshows" %}
|
||||
<div {{ stimulus_controller('monitor_button', {
|
||||
tmdbId: results.media.tmdbId,
|
||||
imdbId: results.media.imdbId,
|
||||
title: results.media.title,
|
||||
})}}
|
||||
data-monitor-button-result-filter-outlet="#filter"
|
||||
data-monitor-button-dialog-outlet=".monitor-modal"
|
||||
>
|
||||
<button data-monitor-button-target="button" {{ stimulus_action('monitor_button', 'toggle', 'click') }}
|
||||
class="h-8 text-white bg-green-800 bg-opacity-60 font-medium rounded-lg text-sm
|
||||
px-2 py-1.5 text-center inline-flex items-center hover:bg-green-900 border-2
|
||||
border-green-500"
|
||||
type="button"
|
||||
<twig:Modal
|
||||
unique_class="monitor-modal"
|
||||
button_class="h-8 text-white bg-green-800 bg-opacity-60 font-medium rounded-lg text-sm
|
||||
px-2 py-1.5 text-center inline-flex items-center hover:bg-green-900 border-2
|
||||
border-green-500"
|
||||
container_class="monitor-modal"
|
||||
heading="'Hol Up!" button_text="Monitor" submit_action="{{ stimulus_action('monitor_button', 'monitorSeries', 'click') }}" show_cancel show_submit
|
||||
>
|
||||
Monitor
|
||||
<svg class="w-2.5 h-2.5 ms-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Dropdown menu -->
|
||||
<div data-monitor-button-target="options"
|
||||
class="absolute mt-1 right-12 z-40 hidden divide-y rounded-md shadow-sm
|
||||
w-44 bg-green-800 backdrop-filter bg-opacity-100 border-2 border-green-500"
|
||||
>
|
||||
<ul class="py-2 text-sm text-gray-100" aria-labelledby="dropdownDefaultButton">
|
||||
<li {{ stimulus_action('monitor_button', 'monitorSeries', 'click') }}>
|
||||
<button class="px-4 py-2 hover:bg-green-950 w-full text-left">
|
||||
Entire Series
|
||||
</button>
|
||||
</li>
|
||||
<li {{ stimulus_action('monitor_button', 'monitorSeason', 'click') }}>
|
||||
<button class="px-4 py-2 hover:bg-green-950 w-full text-left">
|
||||
Season
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
Monitoring a series will continuously search for new episodes and attempt to automatically download them. Your download preferences
|
||||
will be used to choose the correct file. To stop monitoring for new episodes, delete the monitor.
|
||||
<br /><br />
|
||||
Would you like to add a new monitor for "{{ results.media.title }}"?
|
||||
</twig:Modal>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user