Reworked Mod Listing

Updates the mod listing components to be a little more friendly.
This commit is contained in:
Refringe 2024-09-13 00:08:00 -04:00
parent d9b7d6fcc8
commit d1b142c2ee
Signed by: Refringe
SSH Key Fingerprint: SHA256:t865XsQpfTeqPRBMN2G6+N8wlDjkgUCZF3WGW6O9N/k
17 changed files with 101 additions and 205 deletions

View File

@ -4,6 +4,7 @@ namespace App\Livewire\Mod;
use App\Http\Filters\ModFilter;
use App\Models\SptVersion;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Cache;
@ -12,7 +13,7 @@ use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
class Index extends Component
class Listing extends Component
{
use WithPagination;
@ -87,12 +88,19 @@ class Index extends Component
];
$mods = (new ModFilter($filters))->apply()->paginate(16);
// Check if the current page is greater than the last page. Redirect if it is.
$this->redirectOutOfBoundsPage($mods);
return view('livewire.mod.listing', compact('mods'));
}
/**
* Check if the current page is greater than the last page. Redirect if it is.
*/
private function redirectOutOfBoundsPage(LengthAwarePaginator $mods): void
{
if ($mods->currentPage() > $mods->lastPage()) {
$this->redirectRoute('mods', ['page' => $mods->lastPage()]);
}
return view('livewire.mod.index', compact('mods'));
}
/**

View File

@ -226,6 +226,9 @@ class Mod extends Model
'contains_ai_content' => 'boolean',
'contains_ads' => 'boolean',
'disabled' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
];
}

View File

@ -11,8 +11,8 @@ use App\Observers\ModDependencyObserver;
use App\Observers\ModObserver;
use App\Observers\ModVersionObserver;
use App\Observers\SptVersionObserver;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Number;
use Illuminate\Support\ServiceProvider;

View File

@ -9,34 +9,27 @@ use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\View\Component;
class ModListSection extends Component
class HomepageMods extends Component
{
/**
* The featured mods listed on the homepage.
*
* @var Collection<int, Mod>
*/
public Collection $modsFeatured;
/**
* The latest mods listed on the homepage.
*
* @var Collection<int, Mod>
*/
public Collection $modsLatest;
/**
* The last updated mods listed on the homepage.
*
* @var Collection<int, Mod>
*/
public Collection $modsUpdated;
public function __construct()
public function render(): View
{
$this->modsFeatured = $this->fetchFeaturedMods();
$this->modsLatest = $this->fetchLatestMods();
$this->modsUpdated = $this->fetchUpdatedMods();
return view('components.homepage-mods', [
'featured' => [
'title' => __('Featured Mods'),
'mods' => $this->fetchFeaturedMods(),
'link' => '/mods?featured=only',
],
'latest' => [
'title' => __('Newest Mods'),
'mods' => $this->fetchLatestMods(),
'link' => '/mods',
],
'updated' => [
'title' => __('Recently Updated Mods'),
'mods' => $this->fetchUpdatedMods(),
'link' => '/mods?order=updated',
],
]);
}
/**
@ -46,9 +39,8 @@ class ModListSection extends Component
*/
private function fetchFeaturedMods(): Collection
{
return Mod::select(['id', 'name', 'slug', 'teaser', 'thumbnail', 'featured', 'downloads'])
return Mod::select(['id', 'name', 'slug', 'teaser', 'thumbnail', 'featured', 'downloads', 'updated_at'])
->with([
'latestVersion',
'latestVersion.latestSptVersion:id,version,color_class',
'users:id,name',
'license:id,name,link',
@ -66,9 +58,8 @@ class ModListSection extends Component
*/
private function fetchLatestMods(): Collection
{
return Mod::select(['id', 'name', 'slug', 'teaser', 'thumbnail', 'featured', 'created_at', 'downloads'])
return Mod::select(['id', 'name', 'slug', 'teaser', 'thumbnail', 'featured', 'created_at', 'downloads', 'updated_at'])
->with([
'latestVersion',
'latestVersion.latestSptVersion:id,version,color_class',
'users:id,name',
'license:id,name,link',
@ -85,9 +76,8 @@ class ModListSection extends Component
*/
private function fetchUpdatedMods(): Collection
{
return Mod::select(['id', 'name', 'slug', 'teaser', 'thumbnail', 'featured', 'downloads'])
return Mod::select(['id', 'name', 'slug', 'teaser', 'thumbnail', 'featured', 'downloads', 'updated_at'])
->with([
'lastUpdatedVersion',
'lastUpdatedVersion.latestSptVersion:id,version,color_class',
'users:id,name',
'license:id,name,link',
@ -103,40 +93,4 @@ class ModListSection extends Component
->limit(6)
->get();
}
public function render(): View
{
return view('components.mod-list-section', [
'sections' => $this->getSections(),
]);
}
/**
* Prepare the sections for the homepage mod lists.
*
* @return array<int, array<string, mixed>>
*/
public function getSections(): array
{
return [
[
'title' => __('Featured Mods'),
'mods' => $this->modsFeatured,
'versionScope' => 'latestVersion',
'link' => '/mods?featured=only',
],
[
'title' => __('Newest Mods'),
'mods' => $this->modsLatest,
'versionScope' => 'latestVersion',
'link' => '/mods',
],
[
'title' => __('Recently Updated Mods'),
'mods' => $this->modsUpdated,
'versionScope' => 'lastUpdatedVersion',
'link' => '/mods?order=updated',
],
];
}
}

View File

@ -1,39 +0,0 @@
<?php
namespace App\View\Components;
use App\Models\Mod;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\View\Component;
class ModList extends Component
{
/**
* The mods to display.
*
* @var Collection<int, Mod>
*/
public Collection $mods;
public string $versionScope;
/**
* Create a new component instance.
*
* @param Collection<int, Mod> $mods
*/
public function __construct(Collection $mods, string $versionScope)
{
$this->mods = $mods;
$this->versionScope = $versionScope;
}
public function render(): View
{
return view('components.mod-list', [
'mods' => $this->mods,
'versionScope' => $this->versionScope,
]);
}
}

View File

@ -1,21 +0,0 @@
<?php
namespace App\View\Components;
use App\Models\Mod;
use App\Models\ModVersion;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class ModListStats extends Component
{
public function __construct(
public Mod $mod,
public ModVersion $modVersion
) {}
public function render(): View
{
return view('components.mod-list-stats');
}
}

View File

@ -0,0 +1,22 @@
<div class="mx-auto max-w-7xl px-4 pt-16 sm:px-6 lg:px-8">
<x-page-content-title :title="$featured['title']" :button-text="__('View All')" :button-link="$featured['link']" />
<div class="my-8 grid grid-cols-1 gap-6 lg:grid-cols-2">
@foreach ($featured['mods'] as $mod)
<x-mod-card :mod="$mod" :version="$mod->latestVersion" />
@endforeach
</div>
<x-page-content-title :title="$latest['title']" :button-text="__('View All')" :button-link="$latest['link']" />
<div class="my-8 grid grid-cols-1 gap-6 lg:grid-cols-2">
@foreach ($latest['mods'] as $mod)
<x-mod-card :mod="$mod" :version="$mod->latestVersion" />
@endforeach
</div>
<x-page-content-title :title="$updated['title']" :button-text="__('View All')" :button-link="$updated['link']" />
<div class="my-8 grid grid-cols-1 gap-6 lg:grid-cols-2">
@foreach ($updated['mods'] as $mod)
<x-mod-card :mod="$mod" :version="$mod->lastUpdatedVersion" />
@endforeach
</div>
</div>

View File

@ -1,37 +1,56 @@
@props(['mod', 'versionScope' => 'none'])
@props(['mod', 'version'])
<a href="/mod/{{ $mod->id }}/{{ $mod->slug }}" class="mod-list-component relative mx-auto w-full max-w-md md:max-w-2xl">
<a href="{{ $mod->detailUrl() }}" class="mod-list-component relative mx-auto w-full max-w-md md:max-w-2xl">
@if ($mod->featured && !request()->routeIs('home'))
<div class="ribbon z-10">{{ __('Featured!') }}</div>
@endif
<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))
@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
@endempty
</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>
@if($versionScope !== 'none' && $mod->{$versionScope} !== null && $mod->{$versionScope}->latestSptVersion !== null)
<span class="badge-version {{ $mod->{$versionScope}->latestSptVersion->first()->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
{{ $mod->{$versionScope}->latestSptVersion->first()->version_formatted }}
<span class="badge-version {{ $version->latestSptVersion->first()->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
{{ $version->latestSptVersion->first()->version_formatted }}
</span>
@endif
</div>
<p class="text-sm italic text-slate-600 dark:text-gray-200">
By {{ $mod->users->pluck('name')->implode(', ') }}
{{ __('By :authors', ['authors' => $mod->users->pluck('name')->implode(', ')]) }}
</p>
<p class="mt-2 text-slate-500 dark:text-gray-300">
{{ Str::limit($mod->teaser, 100) }}
</p>
<p class="mt-2 text-slate-500 dark:text-gray-300">{{ Str::limit($mod->teaser, 100) }}</p>
</div>
@if($versionScope !== 'none' && $mod->{$versionScope} !== null)
<x-mod-list-stats :mod="$mod" :modVersion="$mod->{$versionScope}"/>
<div class="text-slate-700 dark:text-gray-300 text-sm">
<div class="flex items-end w-full text-sm">
@if ($mod->updated_at || $mod->created_at)
<div class="flex items-end w-full">
<div class="flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5m-9-6h.008v.008H12v-.008ZM12 15h.008v.008H12V15Zm0 2.25h.008v.008H12v-.008ZM9.75 15h.008v.008H9.75V15Zm0 2.25h.008v.008H9.75v-.008ZM7.5 15h.008v.008H7.5V15Zm0 2.25h.008v.008H7.5v-.008Zm6.75-4.5h.008v.008h-.008v-.008Zm0 2.25h.008v.008h-.008V15Zm0 2.25h.008v.008h-.008v-.008Zm2.25-4.5h.008v.008H16.5v-.008Zm0 2.25h.008v.008H16.5V15Z" />
</svg>
<x-time :datetime="$version->created_at" />
</div>
</div>
@endif
<div class="flex justify-end items-center gap-1">
<span title="{{ __('Exactly :downloads', ['downloads' => $mod->downloads]) }}">
{{ Number::downloads($mod->downloads) }}
</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
</svg>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,6 +0,0 @@
@props(['mods', 'versionScope', 'title', 'link'])
<div class="mx-auto max-w-7xl px-4 pt-16 sm:px-6 lg:px-8">
<x-page-content-title :title="$title" button-text="View All" :button-link="$link" />
<x-mod-list :mods="$mods" :versionScope="$versionScope" />
</div>

View File

@ -1,8 +0,0 @@
@foreach ($sections as $section)
@include('components.mod-list-section-partial', [
'title' => $section['title'],
'mods' => $section['mods'],
'versionScope' => $section['versionScope'],
'link' => $section['link']
])
@endforeach

View File

@ -1,32 +0,0 @@
<p {{ $attributes->class(['text-slate-700 dark:text-gray-300 text-sm']) }}>
<div class="flex items-end w-full text-sm">
<div class="flex items-end w-full">
<div class="flex items-center gap-1">
<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M21 8.5v9.25A3.25 3.25 0 0 1 17.75 21H6.25A3.25 3.25 0 0 1 3 17.75V8.5h18ZM7.25 15a1.25 1.25 0 1 0 0 2.5 1.25 1.25 0 0 0 0-2.5ZM12 15a1.25 1.25 0 1 0 0 2.5 1.25 1.25 0 0 0 0-2.5Zm-4.75-4.5a1.25 1.25 0 1 0 0 2.5 1.25 1.25 0 0 0 0-2.5Zm4.75 0a1.25 1.25 0 1 0 0 2.5 1.25 1.25 0 0 0 0-2.5Zm4.75 0a1.25 1.25 0 1 0 0 2.5 1.25 1.25 0 0 0 0-2.5Zm1-7.5A3.25 3.25 0 0 1 21 6.25V7H3v-.75A3.25 3.25 0 0 1 6.25 3h11.5Z"/>
</svg>
<span>
@if(!is_null($mod->updated_at))
<time datetime="{{ $modVersion->updated_at->format('c') }}">
{{ Carbon::dynamicFormat($modVersion->updated_at) }}
</time>
@elseif(!is_null($mod->created_at))
<time datetime="{{ $modVersion->created_at->format('c') }}">
{{ Carbon::dynamicFormat($modVersion->created_at) }}
</time>
@else
N/A
@endif
</span>
</div>
</div>
<div class="flex justify-end items-center gap-1">
<span title="{{ __('Exactly') }} {{ $mod->downloads }}">
{{ Number::downloads($mod->downloads) }}
</span>
<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M5.25 20.5h13.498a.75.75 0 0 1 .101 1.493l-.101.007H5.25a.75.75 0 0 1-.102-1.494l.102-.006h13.498H5.25Zm6.633-18.498L12 1.995a1 1 0 0 1 .993.883l.007.117v12.59l3.294-3.293a1 1 0 0 1 1.32-.083l.094.084a1 1 0 0 1 .083 1.32l-.083.094-4.997 4.996a1 1 0 0 1-1.32.084l-.094-.083-5.004-4.997a1 1 0 0 1 1.32-1.498l.094.083L11 15.58V2.995a1 1 0 0 1 .883-.993L12 1.995l-.117.007Z"/>
</svg>
</div>
</div>
</p>

View File

@ -1,9 +0,0 @@
@props(['mods', 'versionScope'])
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
@foreach ($mods as $mod)
@if ($mod->{$versionScope})
<x-mod-card :mod="$mod" :versionScope="$versionScope"/>
@endif
@endforeach
</div>

View File

@ -2,7 +2,7 @@
<div class="min-w-0 flex-1">
<h2 class="text-2xl font-bold leading-7 text-gray-900 dark:text-gray-200 sm:truncate sm:text-3xl sm:tracking-tight">{{ __($title) }}</h2>
</div>
@if (isset($buttonText) && isset($buttonLink))
@if ($buttonText && $buttonLink)
<div class="mt-4 flex md:ml-4 md:mt-0">
<a href="{{ $buttonLink }}">
<button type="button">{{ __($buttonText) }}</button>

View File

@ -0,0 +1,5 @@
@props(['datetime'])
<time datetime="{{ $datetime->format('c') }}">
{{ Carbon::dynamicFormat($datetime) }}
</time>

View File

@ -20,7 +20,7 @@
</div>
<div class="pb-10">
<x-mod-list-section/>
<x-homepage-mods />
</div>
</div>
</div>

View File

@ -127,7 +127,7 @@
@if ($mods->isNotEmpty())
<div class="my-8 grid grid-cols-1 gap-6 lg:grid-cols-2">
@foreach ($mods as $mod)
<x-mod-card :mod="$mod" />
<x-mod-card :mod="$mod" :version="$mod->latestVersion" />
@endforeach
</div>
@else

View File

@ -1,3 +1,3 @@
<x-app-layout>
@livewire('mod.index')
@livewire('mod.listing')
</x-app-layout>