diff --git a/app/Http/Controllers/StreamController.php b/app/Http/Controllers/StreamController.php index 485f52e..52f5ca0 100644 --- a/app/Http/Controllers/StreamController.php +++ b/app/Http/Controllers/StreamController.php @@ -56,14 +56,11 @@ class StreamController extends Controller // Playlist if ($request->has('playlist')) { // Get and check if playlist exists - $playlist = Playlist::where('id', $request->input('playlist'))->firstOrFail(); + $playlist = Playlist::withCount('episodes')->where('id', $request->input('playlist'))->firstOrFail(); // Check if episode is in playlist $inPlaylist = PlaylistEpisode::where('playlist_id', $playlist->id)->where('episode_id', $episode->id)->firstOrFail(); - // Get Playlist Episodes and order them - $playlistEpisodes = $playlist->episodes()->orderBy('position')->get(); - // Check if authorized if ($playlist->is_private && (Auth::guest() || (! Auth::guest() && Auth::user()->id != $playlist->user_id))) { abort(404); @@ -75,7 +72,6 @@ class StreamController extends Controller 'studioEpisodes' => $studioEpisodes, 'gallery' => $gallery, 'playlist' => $playlist, - 'playlistEpisodes' => $playlistEpisodes, 'popularWeekly' => CacheHelper::getPopularWeekly(), 'isMobile' => $isMobile, ]); diff --git a/app/Livewire/PlaylistSidebar.php b/app/Livewire/PlaylistSidebar.php new file mode 100644 index 0000000..aeecede --- /dev/null +++ b/app/Livewire/PlaylistSidebar.php @@ -0,0 +1,182 @@ +playlistService = $playlistService; + } + + public function mount(int $playlistId, int $currentEpisodeId, bool $collapsible = false): void + { + $this->playlist = Playlist::with('user')->withCount('episodes')->findOrFail($playlistId); + $this->currentEpisodeId = $currentEpisodeId; + $this->currentEpisode = Episode::with(['gallery' => fn ($query) => $query->orderBy('id')->limit(1)]) + ->findOrFail($currentEpisodeId); + $this->total = $this->playlist->episodes_count; + $this->collapsible = $collapsible; + $this->isOwner = Auth::check() && Auth::user()->id === $this->playlist->user_id; + + $this->repairNullPositions(); + + $position = $this->currentPosition() ?? 1; + + $this->setWindowAround($position); + $this->resolveAdjacentSlugs($position); + } + + public function getEpisodesProperty(): Collection + { + return PlaylistEpisode::query() + ->where('playlist_id', $this->playlist->id) + ->whereBetween('position', [$this->windowStart, $this->windowEnd]) + ->orderBy('position') + ->with([ + 'episode.gallery' => fn ($gallery) => $gallery->orderBy('id')->limit(1), + 'episode.studio', + ]) + ->get(); + } + + public function appendChunk(): void + { + if ($this->windowEnd >= $this->total) { + return; + } + + $this->windowEnd = min($this->total, $this->windowEnd + self::PAGE_SIZE); + $this->windowStart = max(1, $this->windowEnd - self::MAX_WINDOW + 1); + } + + public function prependChunk(): void + { + if ($this->windowStart <= 1) { + return; + } + + $this->windowStart = max(1, $this->windowStart - self::PAGE_SIZE); + $this->windowEnd = min($this->total, $this->windowStart + self::MAX_WINDOW - 1); + } + + public function remove(int $playlistEpisodeId): void + { + if (! $this->isOwner) { + return; + } + + $playlistEpisode = PlaylistEpisode::find($playlistEpisodeId); + + if (! $playlistEpisode) { + return; + } + + $playlistEpisode->delete(); + $this->playlistService->reorderPositions($this->playlist); + $this->playlist->loadCount('episodes'); + + $this->total = $this->playlist->episodes_count; + + $this->windowEnd = min($this->windowEnd, max(1, $this->total)); + $this->windowStart = min($this->windowStart, max(1, $this->total)); + + $position = $this->currentPosition(); + + if ($position) { + $this->resolveAdjacentSlugs($position); + } + } + + public function render(): View + { + $activePlaylistEpisode = $this->activePlaylistEpisode(); + + return view('livewire.playlist-sidebar', [ + 'episodes' => $this->episodes, + 'currentPosition' => $activePlaylistEpisode?->position ?? 1, + 'currentPlaylistEpisodeId' => $activePlaylistEpisode?->id, + 'isOwner' => $this->isOwner, + ]); + } + + private function activePlaylistEpisode(): ?PlaylistEpisode + { + return PlaylistEpisode::where('playlist_id', $this->playlist->id) + ->where('episode_id', $this->currentEpisodeId) + ->first(); + } + + private function currentPosition(): ?int + { + return $this->activePlaylistEpisode()?->position; + } + + private function setWindowAround(int $position): void + { + $this->windowStart = max(1, $position - self::PAGE_SIZE); + $this->windowEnd = min($this->total, $position + self::PAGE_SIZE); + } + + private function resolveAdjacentSlugs(int $position): void + { + $adjacent = PlaylistEpisode::query() + ->where('playlist_id', $this->playlist->id) + ->whereBetween('position', [$position - 1, $position + 1]) + ->orderBy('position') + ->with('episode') + ->get(); + + $slugsByPosition = $adjacent->keyBy('position'); + + $this->previousEpisodeSlug = $slugsByPosition->get($position - 1)?->episode->slug; + $this->nextEpisodeSlug = $slugsByPosition->get($position + 1)?->episode->slug; + } + + private function repairNullPositions(): void + { + if (! PlaylistEpisode::where('playlist_id', $this->playlist->id)->whereNull('position')->exists()) { + return; + } + + PlaylistEpisode::where('playlist_id', $this->playlist->id) + ->orderBy('position')->orderBy('id') + ->get() + ->each(fn ($playlistEpisode, $index) => $playlistEpisode->update(['position' => $index + 1])); + } +} diff --git a/lang/de/playlist.php b/lang/de/playlist.php index 6179e8b..ceda92a 100644 --- a/lang/de/playlist.php +++ b/lang/de/playlist.php @@ -13,4 +13,6 @@ return [ 'no-matches' => 'Keine Episoden entsprechen deiner Suche.', 'watched' => 'Gesehen', 'remove' => 'Entfernen', + 'now-playing' => 'Jetzt läuft', + 'remove-confirm' => 'Diese Episode aus der Playlist entfernen?', ]; diff --git a/lang/en/playlist.php b/lang/en/playlist.php index 84e3b59..2c2de46 100644 --- a/lang/en/playlist.php +++ b/lang/en/playlist.php @@ -13,4 +13,6 @@ return [ 'no-matches' => 'No episodes match your search.', 'watched' => 'Watched', 'remove' => 'Remove', + 'now-playing' => 'Now Playing', + 'remove-confirm' => 'Remove this episode from the playlist?', ]; diff --git a/lang/fr/playlist.php b/lang/fr/playlist.php index eba8e64..1c24270 100644 --- a/lang/fr/playlist.php +++ b/lang/fr/playlist.php @@ -13,4 +13,6 @@ return [ 'no-matches' => 'Aucun épisode ne correspond à votre recherche.', 'watched' => 'Vu', 'remove' => 'Supprimer', + 'now-playing' => 'En lecture', + 'remove-confirm' => 'Supprimer cet épisode de la playlist ?', ]; diff --git a/resources/js/playlist.js b/resources/js/playlist.js index b2da33a..0273fc4 100644 --- a/resources/js/playlist.js +++ b/resources/js/playlist.js @@ -14,107 +14,3 @@ export function playNextPlaylistVideo() { window.location.href = '/hentai/' + nextEpisode + '?playlist=' + playlistId; } - -function deleteEntry(playlistId, episodeId) { - window.axios.post('/user/playlist-episode', { - playlist: playlistId, - episode: episodeId - }).then(function (response) { - if (response.status == 200) { - console.log(response); - if (response.data.message == 'success') { - Swal.fire({ - title: "Deleted!", - text: "Removed entry from playlist!", - icon: "success", - confirmButtonText: "OK", - willClose: () => { - location.reload(); - } - }).then((result) => { - if (result.isConfirmed) { - location.reload(); - } - }); - } - } - }).catch(function (error) { - Swal.fire({ - title: "Error!", - text: error, - icon: "error" - }); - console.log(error); - }); -} - -function addDesktopDeleteListener() { - const deleteButtons = document.querySelectorAll('[id^="delD"]'); - - deleteButtons.forEach(button => { - const playlist = button.id.split('-')[1]; - const episode = button.id.split('-')[2]; - console.log("Playlist: " + playlist + " Episode: " + episode); - - button.addEventListener('click', () => deleteEntry(playlist, episode)); - }); -} - -// Playlist Swipe (Delete) -document.addEventListener('DOMContentLoaded', () => { - const swipeContainers = document.querySelectorAll('.swipe-container'); - - var swipeOptions = { - dragLockToAxis: true, - dragBlockHorizontal: true - }; - - swipeContainers.forEach(container => { - const controls = new Hammer(container, swipeOptions); - const originalColor = container.style.backgroundColor; - const playlistId = container.id.split('-')[0]; - const episodeId = container.id.split('-')[1]; - const delIcon = document.getElementById('del-' + container.id); - - // Set the initial position - let posX = 0; - - // Listen for the pan gesture - controls.on('pan', (event) => { - // Update the X position based on the drag delta - posX = event.deltaX; - if (posX > 0) { - // Only allow left swipe - posX = 0; - } - - // Apply the translation to the element - container.style.transform = `translateX(${posX}px)`; - container.style.backgroundColor = "rgba(159, 18, 18, 0.3)"; - - setTimeout(() => { - delIcon.classList.remove('fa-grip-lines-vertical'); - delIcon.classList.add('fa-trash'); - }, 300); - }); - - controls.on('panend', () => { - container.style.transition = 'transform 0.3s ease'; - container.style.transform = 'translateX(0)'; - setTimeout(() => { - container.style.transition = ''; // Reset transition for next drag - container.style.backgroundColor = originalColor; - delIcon.classList.remove('fa-trash'); - delIcon.classList.add('fa-grip-lines-vertical'); - }, 300); - }); - - controls.on('swipeleft', (event) => { - container.style.display = 'none'; - console.log(playlistId, episodeId); - deleteEntry(playlistId, episodeId); - }); - }); - - addDesktopDeleteListener(); -}); \ No newline at end of file diff --git a/resources/views/livewire/partials/playlist-sidebar-row.blade.php b/resources/views/livewire/partials/playlist-sidebar-row.blade.php new file mode 100644 index 0000000..da73d25 --- /dev/null +++ b/resources/views/livewire/partials/playlist-sidebar-row.blade.php @@ -0,0 +1,58 @@ +@props([ + 'playlistEpisode', + 'isActive' => false, + 'isOwner' => false, +]) + +@php + $episode = $playlistEpisode->episode; +@endphp + +
+ @if ($isActive) + + @endif + +
+ @if ($isActive) + + @else + {{ $playlistEpisode->position }} + @endif +
+ + + {{ $episode->title }} - {{ $episode->episode }} + + +
+ + {{ $episode->title }} + +

{{ $episode->studio->name }}

+
+ + @if ($isOwner) + + @endif +
diff --git a/resources/views/livewire/playlist-sidebar.blade.php b/resources/views/livewire/playlist-sidebar.blade.php new file mode 100644 index 0000000..1c8c39b --- /dev/null +++ b/resources/views/livewire/playlist-sidebar.blade.php @@ -0,0 +1,219 @@ +
+ + + +
+
+ + @if ($collapsible) + + +
+ @endif + + + +
+

+ {{ __('playlist.now-playing') }} +

+
+ +
+

+ {{ $currentEpisode->title }} +

+

+ {{ $currentPosition }}/{{ $total }} {{ __('playlist.episodes') }} +

+
+
+ @if ($previousEpisodeSlug) + + + + @endif + @if ($nextEpisodeSlug) + + + + @endif +
+
+
+ +
+
+ +
+ + @forelse ($episodes as $playlistEpisode) + @include('livewire.partials.playlist-sidebar-row', [ + 'playlistEpisode' => $playlistEpisode, + 'isActive' => $playlistEpisode->episode_id === $currentEpisodeId, + 'isOwner' => $isOwner, + ]) + @empty +
+
+ +
+

{{ __('playlist.empty-playlist') }}

+
+ @endforelse + +
+ +
+
+ + @if ($collapsible) +
+ @endif +
+
+
diff --git a/resources/views/stream/index.blade.php b/resources/views/stream/index.blade.php index 8e243cb..2e52cef 100644 --- a/resources/views/stream/index.blade.php +++ b/resources/views/stream/index.blade.php @@ -16,7 +16,9 @@ @if($isMobile)
- @include('stream.partials.playlist') + @isset($playlist) + + @endisset
@endif @@ -27,7 +29,9 @@
@if(! $isMobile) - @include('stream.partials.playlist') + @isset($playlist) + + @endisset @endif @include('stream.partials.more-episodes') diff --git a/resources/views/stream/partials/playlist.blade.php b/resources/views/stream/partials/playlist.blade.php deleted file mode 100644 index 98f20e6..0000000 --- a/resources/views/stream/partials/playlist.blade.php +++ /dev/null @@ -1,94 +0,0 @@ -@isset($playlist) -
-
-
-

- @if ($playlist->is_private) - {{ $playlist->name }} - @else - {{ $playlist->name }} - @endif -

- @php - $episodeCount = $playlistEpisodes->count(); - $currentIndex = 0; - $nextEpisode = ""; - - if ($episodeCount > 1) { - $currentIndex = $playlistEpisodes->search(fn($playlistEpisode) => $playlistEpisode->episode->id == $episode->id); - $nextEpisode = $currentIndex !== false && $currentIndex + 1 < $episodeCount - ? $playlistEpisodes[$currentIndex + 1]->episode->slug - : ""; - } - @endphp -

- {{ $playlist->user->name }} • {{ $currentIndex + 1 }}/{{ $episodeCount }} Episodes -

-
- - -
-
-
- @php - $counter = 1; - $isAuthedUsersPlaylist = false; - if (auth()->check() && $playlist->user->id == auth()->user()->id) { - $isAuthedUsersPlaylist = true; - } - @endphp - @foreach($playlistEpisodes as $playlistEpisode) - @if ($playlistEpisode->episode->id == $episode->id) -
- @else -
- @endif -
- @if ($playlistEpisode->episode->id == $episode->id) - - @else -

{{ $counter }}

- @endif -
- - {{ $playlistEpisode->episode->title }} - {{ $playlistEpisode->episode->episode }} - -
- -

{{ $playlistEpisode->episode->title }} - {{ $playlistEpisode->episode->episode }}

-
-

{{ $playlistEpisode->episode->viewCount() }} Views - {{ $playlistEpisode->episode->studio->name }}

-
- @if ($playlistEpisode->episode->id != $episode->id && $isAuthedUsersPlaylist) - @if($isMobile) -
- -
- @else -
- -
- @endif - @endif -
- @php $counter++; @endphp - @endforeach -
-
-
-
-
- - - - -@endisset