Compare commits

...

4 Commits

11 changed files with 80 additions and 87 deletions

View File

@@ -2,6 +2,10 @@ export default class DownloadOptionTr extends HTMLTableRowElement {
H264_CODECS = ['h264', 'h.264', 'x264'] H264_CODECS = ['h264', 'h.264', 'x264']
H265_CODECS = ['h265', 'h.265', 'x265', 'hevc'] H265_CODECS = ['h265', 'h.265', 'x265', 'hevc']
#downloadBtnEl;
#selectEpisodeInputEl;
url;
size; size;
quality; quality;
resolution; resolution;
@@ -12,34 +16,39 @@ export default class DownloadOptionTr extends HTMLTableRowElement {
mediaType; mediaType;
season; season;
episode; episode;
filename;
imdbId;
episodeId;
mediaTitle;
constructor() { constructor() {
super(); super();
this.url = this.getAttribute('url');
this.size = this.getAttribute('size'); this.size = this.getAttribute('size');
this.quality = this.getAttribute('quality'); this.quality = this.getAttribute('quality');
this.resolution = this.getAttribute('resolution'); this.resolution = this.getAttribute('resolution');
this.codec = this.getAttribute('codec'); this.codec = this.getAttribute('codec');
this.seeders = this.getAttribute('seeders'); this.seeders = this.getAttribute('seeders');
this.provider = this.getAttribute('provider'); this.provider = this.getAttribute('provider');
this.filename = this.getAttribute('filename');
this.imdbId = this.getAttribute('imdb-id');
this.languages = JSON.parse(this.getAttribute('languages')); this.languages = JSON.parse(this.getAttribute('languages'));
this.mediaType = this.getAttribute('media-type'); this.mediaType = this.getAttribute('media-type');
this.mediaTitle = this.getAttribute('media-title');
this.season = this.getAttribute('season') ?? null; this.season = this.getAttribute('season') ?? null;
this.episode = this.getAttribute('episode') ?? null; this.episode = this.getAttribute('episode') ?? null;
this.episodeId = this.getAttribute('episode-id') ?? null;
this.#downloadBtnEl = this.querySelector('.download-btn');
this.#selectEpisodeInputEl = this.querySelector('input[type="checkbox"]');
// document.addEventListener('filterDownloadOptions', this.filter.bind(this)); this.#downloadBtnEl.addEventListener('click', () => this.download());
} }
connectedCallback() { get isSelected() {
return this.#selectEpisodeInputEl.checked;
} }
// attribute change set isSelected(value) {
attributeChangedCallback(property, oldValue, newValue) { this.#selectEpisodeInputEl.checked = value;
if (oldValue === newValue) return;
this[ property ] = newValue;
}
static get observedAttributes() {
return ['size', 'quality', 'resolution', 'codec', 'seeders', 'provider'];
} }
filter({ detail: { activeFilter } }) { filter({ detail: { activeFilter } }) {
@@ -90,4 +99,26 @@ export default class DownloadOptionTr extends HTMLTableRowElement {
return include; return include;
} }
download() {
fetch('/api/download', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
url: this.url,
title: this.mediaTitle,
filename: this.filename,
mediaType: this.mediaType,
imdbId: this.imdbId,
episodeId: this.episodeId
})
})
.then(res => res.json())
.then(json => {
console.log(json)
})
}
} }

View File

@@ -1,8 +1,6 @@
export default class EpisodeContainer extends HTMLElement { export default class EpisodeContainer extends HTMLElement {
H264_CODECS = ['h264', 'h.264', 'x264']
H265_CODECS = ['h265', 'h.265', 'x265', 'hevc']
options = []; options = [];
showTitle;
#episodeSelectorEl; #episodeSelectorEl;
#resultsToggleBtnEl; #resultsToggleBtnEl;
@@ -12,6 +10,7 @@ export default class EpisodeContainer extends HTMLElement {
constructor() { constructor() {
super(); super();
this.showTitle = this.getAttribute('show-title');
this.#resultsTableEl = this.querySelector('.results-container'); this.#resultsTableEl = this.querySelector('.results-container');
this.#resultsToggleBtnEl = this.querySelector('.dropdown-button'); this.#resultsToggleBtnEl = this.querySelector('.dropdown-button');
this.#resultsCountBadgeEl = this.querySelector('.results-count-badge'); this.#resultsCountBadgeEl = this.querySelector('.results-count-badge');
@@ -22,21 +21,9 @@ export default class EpisodeContainer extends HTMLElement {
this.#resultsCountBadgeEl.addEventListener('click', () => this.toggleResults()); this.#resultsCountBadgeEl.addEventListener('click', () => this.toggleResults());
document.addEventListener('filterDownloadOptions', this.filter.bind(this)); document.addEventListener('filterDownloadOptions', this.filter.bind(this));
document.addEventListener('downloadSelectedEpisodes', this.downloadSelectedResults.bind(this));
document.addEventListener('selectEpisodeForDownload', (e) => this.selectEpisodeForDownload(e.detail.select)); document.addEventListener('selectEpisodeForDownload', (e) => this.selectEpisodeForDownload(e.detail.select));
} }
connectedCallback() {
}
// attribute change
attributeChangedCallback(property, oldValue, newValue) {
if (oldValue === newValue) return;
this[ property ] = newValue;
}
static get observedAttributes() {
return ['name'];
}
toggleResults() { toggleResults() {
this.#resultsToggleBtnEl.classList.toggle('rotate-90'); this.#resultsToggleBtnEl.classList.toggle('rotate-90');
@@ -50,6 +37,20 @@ export default class EpisodeContainer extends HTMLElement {
} }
} }
downloadSelectedResults() {
if (this.#episodeSelectorEl.disabled === false &&
this.#episodeSelectorEl.checked === true
) {
console.log('episode is selected')
this.options.forEach(option => {
if (option.isSelected === true) {
option.download();
}
option.isSelected = false;
})
}
}
filter({ detail: { activeFilter } }) { filter({ detail: { activeFilter } }) {
let firstIncluded = true; let firstIncluded = true;
let count = 0; let count = 0;

View File

@@ -1,7 +1,4 @@
export default class MovieContainer extends HTMLElement { export default class MovieContainer extends HTMLElement {
H264_CODECS = ['h264', 'h.264', 'x264']
H265_CODECS = ['h265', 'h.265', 'x265', 'hevc']
#resultsTableEl; #resultsTableEl;
#resultsCountNumberEl; #resultsCountNumberEl;
@@ -13,12 +10,6 @@ export default class MovieContainer extends HTMLElement {
document.addEventListener('filterDownloadOptions', this.filter.bind(this)); document.addEventListener('filterDownloadOptions', this.filter.bind(this));
} }
// attribute change
attributeChangedCallback(property, oldValue, newValue) {
if (oldValue === newValue) return;
this[ property ] = newValue;
}
filter({ detail: { activeFilter } }) { filter({ detail: { activeFilter } }) {
const options = this.querySelectorAll('tr.download-option'); const options = this.querySelectorAll('tr.download-option');
let firstIncluded = true; let firstIncluded = true;

View File

@@ -6,9 +6,6 @@ import { Controller } from '@hotwired/stimulus';
*/ */
/* stimulusFetch: 'lazy' */ /* stimulusFetch: 'lazy' */
export default class extends Controller { export default class extends Controller {
H264_CODECS = ['h264', 'h.264', 'x264']
H265_CODECS = ['h265', 'h.265', 'x265', 'hevc']
static values = { static values = {
title: String, title: String,
tmdbId: String, tmdbId: String,

View File

@@ -19,7 +19,7 @@ export default class extends Controller {
"quality": "", "quality": "",
} }
static outlets = ['movie-results', 'tv-results', 'tv-episode-list'] static outlets = ['tv-episode-list']
static targets = ['resolution', 'codec', 'language', 'provider', 'season', 'quality', 'selectAll', 'downloadSelected'] static targets = ['resolution', 'codec', 'language', 'provider', 'season', 'quality', 'selectAll', 'downloadSelected']
static values = { static values = {
'imdbId': String, 'imdbId': String,
@@ -55,6 +55,10 @@ export default class extends Controller {
})); }));
} }
downloadSelectedEpisodes() {
document.dispatchEvent(new CustomEvent('downloadSelectedEpisodes', {}));
}
addLanguages(option) { addLanguages(option) {
const languages = Object.assign([], option.languages); const languages = Object.assign([], option.languages);
languages.forEach((language) => { languages.forEach((language) => {
@@ -164,10 +168,6 @@ export default class extends Controller {
this.tvEpisodeListOutlet.setSeason(event.target.value); this.tvEpisodeListOutlet.setSeason(event.target.value);
} }
uncheckSelectAllBtn() {
this.selectAllTarget.checked = false;
}
downloadSeason() { downloadSeason() {
fetch(`/api/download/season/${this.imdbIdValue}/${this.activeFilter['season']}`, { fetch(`/api/download/season/${this.imdbIdValue}/${this.activeFilter['season']}`, {
headers: { headers: {
@@ -175,13 +175,4 @@ export default class extends Controller {
} }
}) })
} }
downloadSelectedEpisodes() {
this.tvResultsOutlets.forEach(episode => {
if (episode.isActive() && episode.isSelected()) {
episode.download();
}
});
this.selectAllTarget.checked = false;
}
} }

View File

@@ -18,7 +18,7 @@ export default class extends Controller {
active: Boolean, active: Boolean,
}; };
static targets = ['list', 'count', 'episodeSelector', 'toggleButton', 'listContainer'] static targets = ['list', 'count', 'episodeSelector',]
static outlets = ['loading-icon'] static outlets = ['loading-icon']
options = [] options = []
@@ -37,21 +37,4 @@ export default class extends Controller {
this.episodeSelectorTarget.disabled = true; this.episodeSelectorTarget.disabled = true;
} }
} }
isSelected() {
return this.episodeSelectorTarget.checked;
}
download() {
this.element.options.forEach(option => {
const optionSelector = option.querySelector('input[type="checkbox"]');
if (true === optionSelector.checked) {
const downloadBtn = option.querySelector('button.download-btn');
const downloadBtnController = this.application.getControllerForElementAndIdentifier(downloadBtn, 'download-button');
downloadBtnController.download();
optionSelector.checked = false;
this.episodeSelectorTarget.checked = false;
}
})
}
} }

View File

@@ -28,6 +28,7 @@ class GetTvShowOptionsHandler implements HandlerInterface
$file = $this->mediaFiles->episodeExists($parentShow->title, $command->season, $command->episode); $file = $this->mediaFiles->episodeExists($parentShow->title, $command->season, $command->episode);
return new GetTvShowOptionsResult( return new GetTvShowOptionsResult(
parentShow: $parentShow,
media: $media, media: $media,
file: MediaFileDto::fromSplFileInfo($file), file: MediaFileDto::fromSplFileInfo($file),
season: $command->season, season: $command->season,

View File

@@ -9,6 +9,7 @@ use OneToMany\RichBundle\Contract\ResultInterface;
class GetTvShowOptionsResult implements ResultInterface class GetTvShowOptionsResult implements ResultInterface
{ {
public function __construct( public function __construct(
public TmdbResult $parentShow,
public TmdbResult $media, public TmdbResult $media,
public MediaFileDto|false $file, public MediaFileDto|false $file,
public string $season, public string $season,

View File

@@ -16,10 +16,9 @@ class CodecList
public static function asSelectOptions(): array public static function asSelectOptions(): array
{ {
$result = []; return [
foreach (static::$codecs as $codec) { 'h264' => 'h264',
$result[$codec] = $codec; 'h265/HEVC' => 'h265',
} ];
return $result;
} }
} }

View File

@@ -4,6 +4,7 @@
<div data-live-id="{{ uniqid() }}" class="episode-container flex flex-col gap-4"> <div data-live-id="{{ uniqid() }}" class="episode-container flex flex-col gap-4">
{% for episode in this.getEpisodes().items %} {% for episode in this.getEpisodes().items %}
<episode-container id="{{ episode_anchor(episode['season_number'], episode['episode_number']) }}" class="results" <episode-container id="{{ episode_anchor(episode['season_number'], episode['episode_number']) }}" class="results"
show-title="{{ this.title }}"
data-tv-results-loading-icon-outlet=".loading-icon" data-tv-results-loading-icon-outlet=".loading-icon"
data-download-button-outlet=".download-btn" data-download-button-outlet=".download-btn"
{{ stimulus_controller('tv_results', { {{ stimulus_controller('tv_results', {

View File

@@ -43,6 +43,7 @@
{% for result in results.results %} {% for result in results.results %}
<tr is="dl-tr" <tr is="dl-tr"
class="download-option bg-white dark:bg-slate-700 flex flex-col flex-no wrap r-tablerow border-b border-gray-500" class="download-option bg-white dark:bg-slate-700 flex flex-col flex-no wrap r-tablerow border-b border-gray-500"
url="{{ result.url }}"
size="{{ result.size }}" size="{{ result.size }}"
quality="{{ result.quality }}" quality="{{ result.quality }}"
resolution="{{ result.resolution }}" resolution="{{ result.resolution }}"
@@ -51,10 +52,16 @@
provider="{{ result.provider }}" provider="{{ result.provider }}"
languages="{{ result.languages|json_encode }}" languages="{{ result.languages|json_encode }}"
media-type="{{ results.media.mediaType }}" media-type="{{ results.media.mediaType }}"
imdb-id="{{ results.media.imdbId }}"
filename="{{ result.filename }}"
data-local-id="{{ result.localId }}" data-local-id="{{ result.localId }}"
{% if "tvshows" == results.media.mediaType %} {% if "tvshows" == results.media.mediaType %}
season="{{ result.season }}" season="{{ result.season }}"
episode="{{ result.episodeNumber }}" episode="{{ result.episodeNumber }}"
episode-id="{{ episode_id(result.season, result.episodeNumber) }}"
media-title="{{ results.parentShow.title }}"
{% else %}
media-title="{{ results.media.title }}"
{% endif %} {% endif %}
> >
<td id="size" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50"> <td id="size" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
@@ -79,17 +86,7 @@
{{ result.languageFlags|raw }} {{ result.languageFlags|raw }}
</td> </td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50 flex flex-row gap-2 items-center justify-start mb:justify-end"> <td class="px-4 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50 flex flex-row gap-2 items-center justify-start mb:justify-end">
<button class="download-btn p-1.5 bg-green-600 rounded-md text-gray-50" <button class="download-btn p-1.5 bg-green-600 rounded-md text-gray-50">
{{ stimulus_controller('download_button', {
url: result.url,
title: results.media.title,
filename: result.filename,
mediaType: results.media.mediaType,
imdbId: results.media.imdbId ?? app.current_route_parameters.imdbId,
episodeId: results|episode_id_from_results
}) }}
{{ stimulus_action('download_button', 'download', 'click') }}
>
Download Download
</button> </button>
<label for="select"> <label for="select">