Compare commits

...

4 Commits

Author SHA1 Message Date
497a3a74cd fix: basic hamburger menu button 2025-06-29 23:19:16 -05:00
101460cd47 fix: mobile results formatting 2025-06-29 22:26:01 -05:00
591c9cdd2a wip: mobile template 2025-06-29 16:01:56 -05:00
2dc53c5270 feat: tags torrentio cache 2025-06-22 22:08:08 -05:00
18 changed files with 158 additions and 56 deletions

View File

@@ -0,0 +1,48 @@
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 {
static outlets = ['navbar']
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)
}
navbarOutletConnected(outlet) {
console.log(outlet)
}
toggleMenu() {
console.log(this.navbarOutlet);
this.navbarOutlet.toggle();
}
// 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)
}
}

View File

@@ -17,9 +17,15 @@ export default class extends Controller {
link.className = this.activeStyles;
}
});
window.addEventListener("resize", (event) => {
});
}
setActive() {
toggle() {
this.element.parentElement.classList.toggle('hidden');
this.element.classList.toggle('fixed');
this.element.classList.toggle('z-20');
}
}

View File

@@ -90,13 +90,7 @@ export default class extends Controller {
}
toggleList() {
// if (!this.isOpen) {
// this.toggleButtonTarget.classList.add('rotate-180');
// this.toggleButtonTarget.classList.remove('-rotate-180');
// } else {
// this.toggleButtonTarget.classList.add('-rotate-180');
// this.toggleButtonTarget.classList.remove('rotate-180');
// }
this.listTarget.classList.toggle('options-table');
this.listTarget.classList.toggle('hidden');
this.toggleButtonTarget.classList.toggle('rotate-90');
this.toggleButtonTarget.classList.toggle('-rotate-90');

View File

@@ -0,0 +1 @@
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 0 0"><path fill="currentColor" fill-rule="evenodd" d="M0 3.75A.75.75 0 0 1 .75 3h14.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 3.75M0 8a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8m.75 3.5a.75.75 0 0 0 0 1.5h14.5a.75.75 0 0 0 0-1.5z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 333 B

View File

@@ -63,3 +63,17 @@ dialog[data-dialog-target="dialog"][open] {
dialog[data-dialog-target="dialog"][closing] {
animation: fade-out 200ms forwards;
}
.options-table {
display: flex;
:last-child {
border-bottom: none;
}
}
@media screen and (min-width: 768px) {
.options-table {
display: inline-table;
}
}

View File

@@ -40,4 +40,11 @@ return [
'stimulus-use' => [
'version' => '0.52.2',
],
'animate.css' => [
'version' => '4.1.1',
],
'animate.css/animate.min.css' => [
'version' => '4.1.1',
'type' => 'css',
],
];

View File

@@ -10,8 +10,8 @@ use App\Torrentio\Exception\TorrentioRateLimitException;
use GuzzleHttp\Client;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
class Torrentio
{
@@ -23,7 +23,7 @@ class Torrentio
public function __construct(
#[Autowire(env: 'REAL_DEBRID_KEY')] private string $realDebridKey,
private CacheInterface $cache,
private TagAwareCacheInterface $cache,
private LoggerInterface $logger,
) {
$this->searchUrl = str_replace('{realDebridKey}', $this->realDebridKey, $this->baseUrl);
@@ -36,8 +36,9 @@ class Torrentio
{
$cacheKey = "torrentio.{$imdbCode}";
$results = $this->cache->get($cacheKey, function (ItemInterface $item) use ($imdbCode) {
$results = $this->cache->get($cacheKey, function (ItemInterface $item) use ($imdbCode, $type) {
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
$item->tag(['torrentio', $type, $imdbCode]);
try {
$response = $this->client->get("$this->searchUrl/$imdbCode.json");
return json_decode(
@@ -63,6 +64,7 @@ class Torrentio
$cacheKey = "torrentio.$imdbId.$season.$episode";
$results = $this->cache->get($cacheKey, function (ItemInterface $item) use ($imdbId, $season, $episode) {
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
$item->tag(['torrentio', 'tvshows', 'torrentio.tvshows', $imdbId, "torrentio.$imdbId", "$imdbId.$season", "torrentio.$imdbId.$season", "$imdbId.$season.$episode", "torrentio.$imdbId.$season.$episode"]);
try {
$response = $this->client->get("$this->searchUrl/$imdbId:$season:$episode.json");
return json_decode(

View File

@@ -2,6 +2,7 @@
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% block title %}Welcome!{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
{% block stylesheets %}

View File

@@ -2,6 +2,7 @@
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% block title %}Welcome!{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
{% block stylesheets %}
@@ -13,11 +14,11 @@
{% endblock %}
</head>
<body class="flex flex-col bg-stone-700">
<div class="grid grid-cols-6">
<div class="col-span-1 h-screen">
<div class="grid md:grid-cols-6">
<div class="hidden md:block md:col-span-1 md:h-screen">
<twig:NavBar />
</div>
<div class="col-span-5 h-screen overflow-y-scroll">
<div class="col-span-6 md:col-span-5 h-screen overflow-y-scroll">
<twig:Header />
<h2 class="px-4 my-2 text-3xl font-bold text-gray-50">{% block h2 %}{% endblock %}</h2>
{% block body %}{% endblock %}

View File

@@ -6,7 +6,7 @@
data-result-filter-tv-results-outlet=".results"
data-result-filter-tv-episode-list-outlet=".episode-list"
>
<div class="w-full p-4 flex flex-row gap-4 bg-stone-500 text-md text-gray-500 dark:text-gray-50 rounded-lg">
<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">
Resolution
<select id="resolution"

View File

@@ -5,12 +5,21 @@
<div class="md:flex md:items-center md:gap-12">
<nav aria-label="Global" class="md:block">
<ul class="flex items-center gap-6 text-sm">
<li><twig:ux:icon name="fluent:alert-12-regular" width="30px" class="text-gray-950 bg-orange-500 rounded-full p-2"/></li>
<li>
<li class="hidden">
<twig:ux:icon name="fluent:alert-12-regular" width="30px" class="text-gray-950 bg-orange-500 rounded-full p-2"/>
</li>
<li class="hidden md:block">
<a href="{{ path('app_logout') }}">
<twig:ux:icon name="material-symbols:logout" width="25px" class="text-orange-500" />
</a>
</li>
<li {{ stimulus_controller('hamburger') }}
{{ stimulus_action('hamburger', 'toggleMenu', 'click') }}
data-hamburger-navbar-outlet="#navbar"
id="hamburger" class="cursor-pointer md:hidden"
>
<svg xmlns="http://www.w3.org/2000/svg" class="text-orange-500 ml-4" width="25px" height="25px" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M0 3.75A.75.75 0 0 1 .75 3h14.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 3.75M0 8a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8m.75 3.5a.75.75 0 0 0 0 1.5h14.5a.75.75 0 0 0 0-1.5z" clip-rule="evenodd"/></svg>
</li>
</ul>
</nav>
</div>

View File

@@ -1,4 +1,4 @@
<nav {{ attributes }} {{ stimulus_controller('navbar') }} {{ stimulus_action('navbar', 'setActive')}} class="flex h-screen flex-col justify-between bg-cyan-950">
<nav id="navbar" {{ attributes }} {{ stimulus_controller('navbar') }} {{ stimulus_action('navbar', 'setActive')}} class="flex h-screen flex-col justify-between bg-cyan-950 animate__animated animate__slideInLeft animate__slow">
<div class="px-4 py-4 flex flex-col gap-12">
<h1 class="text-3xl font-extrabold text-orange-500 mb-3">Torsearch</h1>
<ul class="space-y-1">

View File

@@ -3,12 +3,12 @@
mediaType: mediaType,
imdbId: imdbId
}) }}">
<img src="{{ image }}" class="w-40 rounded-md" />
<img src="{{ image }}" class="w-full md:w-40 rounded-md" />
</a>
<a href="{{ path('app_search_result', {
mediaType: mediaType,
imdbId: imdbId
}) }}">
<h3 class="text-center text-gray-50 max-w-[16ch] text-extrabold">{{ title }}</h3>
<h3 class="text-center text-white text-xl md:text-base md:max-w-[16ch]">{{ title }}</h3>
</a>
</div>

View File

@@ -1,9 +1,9 @@
<div{{ attributes }}>
<div class="p-4 flex flex-row gap-6 bg-orange-500 bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-60 rounded-md">
<div class="p-4 flex flex-col md:flex-row gap-6 bg-orange-500 bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-60 rounded-md">
{% if poster != null and poster != "https://image.tmdb.org/t/p/w500" %}
<img class="w-24 rounded-lg" src="{{ poster }}" />
<img class="w-full md:w-24 rounded-lg" src="{{ poster }}" />
{% else %}
<div class="w-32 h-[144px] rounded-lg bg-gray-700 flex items-center justify-center">
<div class="w-full md:w-32 h-[144px] rounded-lg bg-gray-700 flex items-center justify-center">
<twig:ux:icon width="16" name="hugeicons:loading-01" />
</div>
{% endif %}
@@ -12,11 +12,11 @@
<h3 class="mb-4 text-xl font-medium leading-tight font-bold text-gray-50">
{{ title }} - {{ year }}
</h3>
<p class="text-gray-50">
<p class="hidden md:text-gray-50">
{{ description }}
</p>
</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 text-center"
href="{{ path('app_search_result', {mediaType: mediaType, imdbId: imdbId}) }}"
>choose</a>
</div>

View File

@@ -15,12 +15,12 @@
active: 'true',
}) }}
>
<div class="p-6 flex flex-col gap-6 bg-orange-500 bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-60 rounded-md">
<div class="flex flex-row gap-4">
<div class="p-4 md:p-6 flex flex-col gap-6 bg-orange-500 bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-60 rounded-md">
<div class="flex flex-col md:flex-row gap-4">
{% if episode['poster'] != null %}
<img class="w-64 rounded-lg" src="{{ episode['poster'] }}" />
<img class="w-full md:w-64 rounded-lg" src="{{ episode['poster'] }}" />
{% else %}
<div class="w-64 min-w-64 sticky h-[144px] rounded-lg bg-gray-700 flex items-center justify-center">
<div class="w-full md:w-64 min-w-64 sticky h-[144px] rounded-lg bg-gray-700 flex items-center justify-center">
<twig:ux:icon width="32" name="hugeicons:loading-01" />
</div>
{% endif %}

View File

@@ -5,7 +5,7 @@
{% block body %}
<div class="p-4 flex flex-col grow gap-4 z-30">
<h2 class="mb-2 text-3xl font-bold text-gray-50">Dashboard</h2>
<div class="flex flex-row gap-4">
<div class="flex flex-col md:flex-row gap-4">
<twig:Card title="Active Downloads" class="w-full">
<twig:DownloadList :type="'active'" />
</twig:Card>
@@ -14,13 +14,13 @@
<twig:DownloadList :type="'complete'" />
</twig:Card>
</div>
<div class="flex flex-row gap-4">
<div class="flex flex-col md:flex-row gap-4">
<twig:Card title="Monitors" class="w-full">
<twig:MonitorList :type="'active'" :isWidget="true" />
</twig:Card>
</div>
<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-col gap-4 md:flex-row md:justify-between w-full">
{% for movie in popular_movies %}
<twig:Poster imdbId="{{ movie.imdbId }}"
tmdbId="{{ movie.tmdbId }}"
@@ -32,7 +32,7 @@
/>
{% endfor %}
</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-col md:flex-row justify-between w-full">
{% for movie in popular_tvshows %}
<twig:Poster imdbId="{{ movie.imdbId }}"
tmdbId="{{ movie.tmdbId }}"

View File

@@ -7,11 +7,11 @@
<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">
<div class="p-4 flex flex-row gap-6">
<div class="p-2 md:p-4 flex flex-col md:flex-row gap-6">
{% if results.media.poster != null %}
<img class="w-40 rounded-lg" src="{{ results.media.poster }}" />
<img class="w-full md:w-40 rounded-lg" src="{{ results.media.poster }}" />
{% else %}
<div class="w-40 h-[144px] rounded-lg bg-gray-700 flex items-center justify-center">
<div class="w-full md:w-40 h-[144px] rounded-lg bg-gray-700 flex items-center justify-center">
<twig:ux:icon width="24" name="hugeicons:loading-01" />
</div>
{% endif %}
@@ -127,4 +127,20 @@
</twig:Card>
</div>
</div>
<style>
html,
body {
height: 100%;
}
@media (min-width: 640px) {
thead tr:not(:first-child) {
display: none;
}
}
td:not(:last-child) {
border-bottom: 0;
}
</style>
{% endblock %}

View File

@@ -1,59 +1,62 @@
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 {{ results.media.mediaType == "tvshows" ? "hidden" }}"
<table class="w-full max-w-[75vw] text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 flex-row flex-no-wrap {{ results.media.mediaType == "tvshows" ? "hidden" : "options-table" }}"
{{ stimulus_target(controller, "list") }}
>
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr class="dark:bg-stone-600 overflow-hidden">
<thead class="text-xs text-gray-700 uppercase dark:text-gray-400">
{% for result in results.results %}
<tr class="dark:bg-stone-600 overflow-hidden flex flex-col md:flex-col flex-no wrap md:table-row border-b border-gray-500">
<th scope="col"
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
class="px-4 py-4 leading-[20px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
Size
</th>
<th scope="col"
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
class="px-4 py-4 leading-[20px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
Resolution
</th>
<th scope="col"
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
class="px-4 py-4 leading-[20px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
Codec
</th>
<th scope="col"
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
class="px-4 py-4 leading-[20px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
Seeders
</th>
<th scope="col"
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
class="px-4 py-4 leading-[20px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
Provider
</th>
<th scope="col"
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
class="px-4 py-4 leading-[20px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
Language
</th>
<th scope="col"
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
class="px-4 py-4 leading-[32px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
Actions
</th>
</tr>
{% endfor %}
</thead>
<tbody>
<tbody class="flex-1 sm:flex-none">
{% for result in results.results %}
<tr class="bg-white border-b dark:bg-slate-700 dark:border-gray-600 border-gray-200" data-provider="{{ result.provider }}" data-languages="{{ result.languages|json_encode }}" {% if "tvshows" == results.media.mediaType %} data-season="{{ results.season }}"{% endif %}>
<td id="size" class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
<tr class="bg-white dark:bg-slate-700 flex flex-col flex-no wrap sm:table-row border-b border-gray-500" data-provider="{{ result.provider }}" data-languages="{{ result.languages|json_encode }}" {% if "tvshows" == results.media.mediaType %} data-season="{{ results.season }}"{% endif %}>
<td id="size" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
{{ result.size }}
</td>
<td id="resolution" class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
<td id="resolution" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
{{ result.resolution }}
</td>
<td id="codec" class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
<td id="codec" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
{{ result.codec }}
</td>
<td id="seeders" class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
<td id="seeders" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
{{ result.seeders }}
</td>
<td id="provider" class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50" data-provider="{{ result.provider }}">
<td id="provider" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50 " data-provider="{{ result.provider }}">
{{ result.provider }}
</td>
<td id="language" class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50" data-languages="{{ result.languages|json_encode }}">
<td id="language" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50 overflow-scroll" data-languages="{{ result.languages|json_encode }}">
{{ result.languageFlags|raw }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50 flex flex-row gap-2 items-center justify-end">
<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"
{{ stimulus_controller('download_button', {
url: result.url,