Merge pull request 'setup mod index page' (#20) from waffle.lord/forge:impl/mod-listing-page into mod-listing-page

Reviewed-on: SPT/forge#20
This commit is contained in:
Refringe 2024-08-13 13:50:41 +00:00
commit 411514148b
7 changed files with 231 additions and 32 deletions

View File

@ -15,7 +15,7 @@ class ModController extends Controller
{
$this->authorize('viewAny', Mod::class);
return ModResource::collection(Mod::all());
return view('mod.index');
}
public function store(ModRequest $request)

71
app/Livewire/ModIndex.php Normal file
View File

@ -0,0 +1,71 @@
<?php
namespace App\Livewire;
use App\Models\Mod;
use App\Models\SptVersion;
use Illuminate\Support\Str;
use Livewire\Component;
use Livewire\WithPagination;
class ModIndex extends Component
{
use WithPagination;
public string $modSearch = '';
public string $sectionFilter = 'featured';
public int $versionFilter = -1;
public function render()
{
// 'featured' section is default
$section = 'featured';
switch ($this->sectionFilter) {
case 'new':
$section = 'created_at';
break;
case 'most_downloaded':
$section = 'total_downloads';
break;
case 'recently_updated':
$section = 'updated_at';
break;
case 'top_rated':
// probably use some kind of 'likes' or something
// not implemented yet afaik -waffle
break;
}
$mods = Mod::select(['id', 'name', 'slug', 'teaser', 'thumbnail', 'featured', 'created_at'])
->withTotalDownloads()
->with(['users:id,name'])
->where('name', 'like', '%'.Str::trim($this->modSearch).'%');
if ($this->versionFilter === -1) {
$mods = $mods
->with(['latestVersion', 'latestVersion.sptVersion'])
->whereHas('latestVersion.sptVersion');
} else {
$mods = $mods->with(['latestVersion' => function ($query) {
$query->where('spt_version_id', $this->versionFilter);
}, 'latestVersion.sptVersion'])
->whereHas('latestVersion.sptVersion', function ($query) {
$query->where('spt_version_id', $this->versionFilter);
});
}
$mods = $mods->orderByDesc($section)->paginate(12);
$sptVersions = SptVersion::select(['id', 'version', 'color_class'])->orderByDesc('version')->get();
return view('livewire.mod-index', ['mods' => $mods, 'sptVersions' => $sptVersions]);
}
public function changeSection($section): void
{
$this->sectionFilter = $section;
}
}

View File

@ -10,9 +10,9 @@ class ModPolicy
/**
* Determine whether the user can view multiple models.
*/
public function viewAny(User $user): bool
public function viewAny(?User $user): bool
{
return false;
return true;
}
/**

View File

@ -0,0 +1,31 @@
@props(['mod', 'versionScope' => 'latestVersion'])
<a href="/mod/{{ $mod->id }}/{{ $mod->slug }}" class="mod-list-component mx-auto w-full max-w-md md:max-w-2xl">
<div class="flex flex-col group h-full w-full bg-white dark:bg-gray-950 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl overflow-hidden hover:shadow-lg hover:bg-gray-50 dark:hover:bg-black hover:shadow-gray-400 dark:hover:shadow-black transition-all duration-200">
<div class="h-auto md:h-full md:flex">
<div class="h-auto md:h-full md:shrink-0 overflow-hidden">
@if (empty($mod->thumbnail))
<img src="https://placehold.co/450x450/EEE/31343C?font=source-sans-pro&text={{ $mod->name }}" alt="{{ $mod->name }}" class="block dark:hidden h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
<img src="https://placehold.co/450x450/31343C/EEE?font=source-sans-pro&text={{ $mod->name }}" alt="{{ $mod->name }}" class="hidden dark:block h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
@else
<img src="{{ $mod->thumbnailUrl }}" alt="{{ $mod->name }}" class="h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
@endif
</div>
<div class="flex flex-col w-full justify-between p-5">
<div class="pb-3">
<div class="flex justify-between items-center space-x-3">
<h3 class="block mt-1 text-lg leading-tight font-medium text-black dark:text-white group-hover:underline">{{ $mod->name }}</h3>
<span class="badge-version {{ $mod->{$versionScope}->sptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
{{ $mod->{$versionScope}->sptVersion->version }}
</span>
</div>
<p class="text-sm italic text-slate-600 dark:text-gray-200">
By {{ $mod->users->pluck('name')->implode(', ') }}
</p>
<p class="mt-2 text-slate-500 dark:text-gray-300">{{ Str::limit($mod->teaser, 100) }}</p>
</div>
<x-mod-list-stats :mod="$mod" :modVersion="$mod->{$versionScope}"/>
</div>
</div>
</div>
</a>

View File

@ -3,35 +3,7 @@
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
@foreach ($mods as $mod)
@if ($mod->{$versionScope})
<a href="/mod/{{ $mod->id }}/{{ $mod->slug }}" class="mod-list-component mx-auto w-full max-w-md md:max-w-2xl">
<div class="flex flex-col group h-full w-full bg-white dark:bg-gray-950 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl overflow-hidden hover:shadow-lg hover:bg-gray-50 dark:hover:bg-black hover:shadow-gray-400 dark:hover:shadow-black transition-all duration-200">
<div class="h-auto md:h-full md:flex">
<div class="h-auto md:h-full md:shrink-0 overflow-hidden">
@if (empty($mod->thumbnail))
<img src="https://placehold.co/450x450/EEE/31343C?font=source-sans-pro&text={{ $mod->name }}" alt="{{ $mod->name }}" class="block dark:hidden h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
<img src="https://placehold.co/450x450/31343C/EEE?font=source-sans-pro&text={{ $mod->name }}" alt="{{ $mod->name }}" class="hidden dark:block h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
@else
<img src="{{ $mod->thumbnailUrl }}" alt="{{ $mod->name }}" class="h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
@endif
</div>
<div class="flex flex-col w-full justify-between p-5">
<div class="pb-3">
<div class="flex justify-between items-center space-x-3">
<h3 class="block mt-1 text-lg leading-tight font-medium text-black dark:text-white group-hover:underline">{{ $mod->name }}</h3>
<span class="badge-version {{ $mod->{$versionScope}->sptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
{{ $mod->{$versionScope}->sptVersion->version }}
</span>
</div>
<p class="text-sm italic text-slate-600 dark:text-gray-200">
By {{ $mod->users->pluck('name')->implode(', ') }}
</p>
<p class="mt-2 text-slate-500 dark:text-gray-300">{{ Str::limit($mod->teaser, 100) }}</p>
</div>
<x-mod-list-stats :mod="$mod" :modVersion="$mod->{$versionScope}"/>
</div>
</div>
</div>
</a>
<x-mod-card :mod="$mod" :versionScope="$versionScope"/>
@endif
@endforeach
</div>

View File

@ -0,0 +1,121 @@
<div>
{{-- page links --}}
<div class="m-6">
{{ $mods->links() }}
</div>
{{-- grid layout --}}
<div class="grid gap-6 grid-cols-1 lg:grid-cols-4 m-4">
{{-- column 1 --}}
<div class="col-span-3">
{{-- mods serach bar --}}
<div>
<search class="relative group">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor"><path d="M771-593 630-734l-85 84-85-84 113-114q12-12 27-17.5t30-5.5q16 0 30.5 5.5T686-848l85 85q18 17 26.5 39.5T806-678q0 23-8.5 45T771-593ZM220-409q-18-18-18-42.5t18-42.5l98-99 85 85-99 99q-17 18-41.5 18T220-409Zm-43 297q-11-12-17-26.5t-6-30.5q0-16 5.5-30.5T177-226l283-282-127-128q-18-17-18-41.5t18-42.5q17-18 42-18t43 18l127 127 57-57 112 114q12 12 12 28t-12 28q-12 12-28 12t-28-12L290-112q-12 12-26.5 17.5T234-89q-15 0-30-6t-27-17Z"/></svg>
</div>
<input wire:model.live="modSearch" class="sm:w-1/3 w-full rounded-md border-0 bg-white dark:bg-gray-700 py-1.5 pl-10 pr-3 text-gray-900 dark:text-gray-300 ring-1 ring-inset ring-gray-300 dark:ring-gray-700 placeholder:text-gray-400 dark:placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-gray-600 dark:focus:bg-gray-200 dark:focus:text-black dark:focus:ring-0 sm:text-sm sm:leading-6" placeholder="Search Mods ..." />
</search>
</div>
{{-- mobile section filters --}}
<div class="flex flex-col sm:hidden my-4">
<select wire:model.live="sectionFilter" class="block w-full rounded-md dark:text-white bg-gray-100 dark:bg-gray-950 border-gray-300 dark:border-gray-700 focus:border-grey-500 dark:focus:border-grey-600 focus:ring-grey-500 dark:focus:ring-grey-600">
<option value="featured">{{__('Featured')}}</option>
<option value="new">{{__('New')}}</option>
<option value="recently_updated">{{__('Recently Updated')}}</option>
<option value="most_downloaded">{{__('Most Downloaded')}}</option>
<option value="top_rated">{{__('Top Rated')}}</option>
</select>
</div>
{{-- section filters --}}
<div class="hidden sm:block my-4">
<nav class="isolate flex divide-x divide-gray-200 dark:divide-gray-800 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl" aria-label="Tabs">
<button wire:click="changeSection('featured')" class="tab rounded-l-xl group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium text-gray-900 dark:text-white bg-white dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-black dark:hover:text-white focus:z-10">
<span>{{ __('Featured') }}</span>
<span aria-hidden="true" class="{{ $sectionFilter === 'featured' ? 'bg-gray-500 absolute inset-x-0 bottom-0 h-0.5' : 'bottom-0 h-0.5' }}"></span>
</button>
<button wire:click="changeSection('new')" class="tab group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium text-gray-900 dark:text-white bg-white dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-black dark:hover:text-white focus:z-10">
<span>{{ __('New') }}</span>
<span aria-hidden="true" class="{{ $sectionFilter === 'new' ? 'bg-gray-500 absolute inset-x-0 bottom-0 h-0.5' : 'bottom-0 h-0.5' }}"></span>
</button>
<button wire:click="changeSection('recently_updated')" class="tab group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium text-gray-900 dark:text-white bg-white dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-black dark:hover:text-white focus:z-10">
<span>{{ __('Recently Updated') }}</span>
<span aria-hidden="true" class="{{ $sectionFilter === 'recently_updated' ? 'bg-gray-500 absolute inset-x-0 bottom-0 h-0.5' : 'bottom-0 h-0.5' }}"></span>
</button>
<button wire:click="changeSection('most_downloaded')" class="tab group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium text-gray-900 dark:text-white bg-white dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-black dark:hover:text-white focus:z-10">
<span>{{ __('Most Downloaded') }}</span>
<span aria-hidden="true" class="{{ $sectionFilter === 'most_downloaded' ? 'bg-gray-500 absolute inset-x-0 bottom-0 h-0.5' : 'bottom-0 h-0.5' }}"></span>
</button>
<button wire:click="changeSection('top_rated')" class="tab rounded-r-xl group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium text-gray-900 dark:text-white bg-white dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-black dark:hover:text-white focus:z-10">
<span>{{ __('Top Rated') }}</span>
<span aria-hidden="true" class="{{ $sectionFilter === 'top_rated' ? 'bg-gray-500 absolute inset-x-0 bottom-0 h-0.5' : 'bottom-0 h-0.5' }}"></span>
</button>
</nav>
</div>
{{-- mobile version filters --}}
<div class="flex flex-col lg:hidden my-4">
<select wire:model.live="versionFilter" class="rounded-md dark:text-white bg-gray-100 dark:bg-gray-950 border-gray-300 dark:border-gray-700 focus:border-grey-500 dark:focus:border-grey-600 focus:ring-grey-500 dark:focus:ring-grey-600">
<option value="-1">Any Version</option>
@foreach($sptVersions as $version)
<option value="{{ $version->id }}">{{ $version->version }}</option>
@endforeach
</select>
</div>
{{-- mobile tags filters --}}
<div class="flex flex-col lg:hidden my-4">
<p class="text-gray-700 dark:text-gray-200">tags filters here when ready :)</p>
</div>
{{-- mod cards --}}
<div class="grid gap-6 grid-cols-2">
@foreach($mods as $mod)
<x-mod-card :mod="$mod" />
@endforeach
</div>
</div>
{{-- column 2 --}}
<div class="flex flex-col col-span-1 gap-4 hidden lg:flex">
{{-- spt version filters --}}
<div class="flex flex-col drop-shadow-2xl text-gray-700 bg-white dark:text-gray-200 dark:bg-gray-950 p-4 rounded-xl">
<h2>SPT Version</h2>
<select wire:model.live="versionFilter" class="rounded-md dark:text-white bg-gray-100 dark:bg-gray-950 border-gray-300 dark:border-gray-700 focus:border-grey-500 dark:focus:border-grey-600 focus:ring-grey-500 dark:focus:ring-grey-600">
<option value="-1">Any Version</option>
@foreach($sptVersions as $version)
<option value="{{ $version->id }}">{{ $version->version }}</option>
@endforeach
</select>
</div>
{{-- tag filters --}}
<div class="flex flex-col drop-shadow-2xl text-gray-700 bg-white dark:text-gray-200 dark:bg-gray-950 p-4 rounded-xl gap-2">
<h2>Tags</h2>
<button class="flex justify-between text-gray-700 dark:text-gray-200 bg-gray-200 dark:bg-gray-700 rounded-md p-2">
<span>Placeholder</span>
<span>2501</span>
</button>
<button class="flex justify-between text-gray-700 dark:text-gray-200 bg-gray-200 dark:bg-gray-700 rounded-md p-2">
<span>Stuff</span>
<span>420</span>
</button>
<button class="flex justify-between text-gray-700 dark:text-gray-200 bg-gray-200 dark:bg-gray-700 rounded-md p-2">
<span>Here</span>
<span>69</span>
</button>
</div>
</div>
</div>
{{-- page links --}}
<div class="m-6">
{{ $mods->links() }}
</div>
</div>

View File

@ -0,0 +1,4 @@
<x-app-layout>
@livewire('mod-index')
</x-app-layout>