refactor: unify artwork card rendering
This commit is contained in:
@@ -11,6 +11,7 @@ use App\Services\ThumbnailPresenter;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Pagination\AbstractPaginator;
|
||||
use Illuminate\Pagination\AbstractCursorPaginator;
|
||||
|
||||
@@ -180,19 +181,25 @@ class BrowseGalleryController extends \App\Http\Controllers\Controller
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$catSlug = $category->slug;
|
||||
$categorySlugs = $this->categoryFilterSlugs($category);
|
||||
$categoryFilter = collect($categorySlugs)
|
||||
->map(fn (string $slug) => 'category = "' . addslashes($slug) . '"')
|
||||
->implode(' OR ');
|
||||
|
||||
$artworks = Cache::remember(
|
||||
"gallery.cat.{$catSlug}.{$sort}.{$page}",
|
||||
'gallery.cat.' . md5($contentSlug . '|' . implode('|', $categorySlugs)) . ".{$sort}.{$page}",
|
||||
$ttl,
|
||||
fn () => Artwork::search('')->options([
|
||||
'filter' => 'is_public = true AND is_approved = true AND category = "' . $catSlug . '"',
|
||||
'filter' => 'is_public = true AND is_approved = true AND (' . $categoryFilter . ')',
|
||||
'sort' => self::SORT_MAP[$sort] ?? ['created_at:desc'],
|
||||
])->paginate($perPage)
|
||||
);
|
||||
$artworks->getCollection()->transform(fn ($a) => $this->presentArtwork($a));
|
||||
$seo = $this->buildPaginationSeo($request, url('/' . $contentSlug . '/' . strtolower($category->full_slug_path)), $artworks);
|
||||
|
||||
$subcategories = $category->children()->orderBy('sort_order')->orderBy('name')->get();
|
||||
$navigationCategory = $category->parent ?: $category;
|
||||
|
||||
$subcategories = $navigationCategory->children()->orderBy('sort_order')->orderBy('name')->get();
|
||||
if ($subcategories->isEmpty()) {
|
||||
$subcategories = $rootCategories;
|
||||
}
|
||||
@@ -209,6 +216,7 @@ class BrowseGalleryController extends \App\Http\Controllers\Controller
|
||||
'gallery_type' => 'category',
|
||||
'mainCategories' => $mainCategories,
|
||||
'subcategories' => $subcategories,
|
||||
'subcategory_parent' => $navigationCategory,
|
||||
'contentType' => $contentType,
|
||||
'category' => $category,
|
||||
'artworks' => $artworks,
|
||||
@@ -298,6 +306,8 @@ class BrowseGalleryController extends \App\Http\Controllers\Controller
|
||||
return (object) [
|
||||
'id' => $artwork->id,
|
||||
'name' => $artwork->title,
|
||||
'content_type_name' => $primaryCategory?->contentType?->name ?? '',
|
||||
'content_type_slug' => $primaryCategory?->contentType?->slug ?? '',
|
||||
'category_name' => $primaryCategory->name ?? '',
|
||||
'category_slug' => $primaryCategory->slug ?? '',
|
||||
'thumb_url' => $present['url'],
|
||||
@@ -311,6 +321,35 @@ class BrowseGalleryController extends \App\Http\Controllers\Controller
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the category slug filter set for a gallery page.
|
||||
* Includes the current category and all descendant subcategories.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function categoryFilterSlugs(Category $category): array
|
||||
{
|
||||
$category->loadMissing('descendants');
|
||||
|
||||
$slugs = [];
|
||||
$stack = [$category];
|
||||
|
||||
while ($stack !== []) {
|
||||
/** @var Category $current */
|
||||
$current = array_pop($stack);
|
||||
if (! empty($current->slug)) {
|
||||
$slugs[] = Str::lower($current->slug);
|
||||
}
|
||||
|
||||
foreach ($current->children as $child) {
|
||||
$child->loadMissing('descendants');
|
||||
$stack[] = $child;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($slugs));
|
||||
}
|
||||
|
||||
private function resolvePerPage(Request $request): int
|
||||
{
|
||||
$limit = (int) $request->query('limit', 0);
|
||||
|
||||
@@ -429,6 +429,8 @@ final class DiscoverController extends Controller
|
||||
return (object) [
|
||||
'id' => $artwork->id,
|
||||
'name' => $artwork->title,
|
||||
'content_type_name' => $primaryCategory?->contentType?->name ?? '',
|
||||
'content_type_slug' => $primaryCategory?->contentType?->slug ?? '',
|
||||
'category_name' => $primaryCategory->name ?? '',
|
||||
'category_slug' => $primaryCategory->slug ?? '',
|
||||
'gid_num' => $primaryCategory ? ((int) $primaryCategory->id % 5) * 5 : 0,
|
||||
|
||||
@@ -285,6 +285,8 @@ final class ExploreController extends Controller
|
||||
return (object) [
|
||||
'id' => $artwork->id,
|
||||
'name' => $artwork->title,
|
||||
'content_type_name' => $primary?->contentType?->name ?? '',
|
||||
'content_type_slug' => $primary?->contentType?->slug ?? '',
|
||||
'category_name' => $primary->name ?? '',
|
||||
'category_slug' => $primary->slug ?? '',
|
||||
'thumb_url' => $present['url'],
|
||||
|
||||
@@ -6,6 +6,8 @@ use App\Http\Controllers\Controller;
|
||||
use App\Models\Artwork;
|
||||
use App\Services\ArtworkService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class FeaturedArtworksController extends Controller
|
||||
{
|
||||
@@ -24,22 +26,33 @@ class FeaturedArtworksController extends Controller
|
||||
|
||||
$typeFilter = $type === 4 ? null : $type;
|
||||
|
||||
/** @var LengthAwarePaginator $artworks */
|
||||
$artworks = $this->artworks->getFeaturedArtworks($typeFilter, $perPage);
|
||||
|
||||
$artworks->getCollection()->transform(function (Artwork $artwork) {
|
||||
$primaryCategory = $artwork->categories->sortBy('sort_order')->first();
|
||||
$categoryName = $primaryCategory->name ?? '';
|
||||
$categorySlug = $primaryCategory->slug ?? '';
|
||||
$gid = $primaryCategory ? ((int) $primaryCategory->id % 5) * 5 : 0;
|
||||
$present = \App\Services\ThumbnailPresenter::present($artwork, 'md');
|
||||
$username = $artwork->user->username ?? $artwork->user->name ?? 'Skinbase';
|
||||
|
||||
return (object) [
|
||||
'id' => $artwork->id,
|
||||
'name' => $artwork->title,
|
||||
'slug' => $artwork->slug,
|
||||
'url' => url('/art/' . $artwork->id . '/' . Str::slug($artwork->title ?? 'artwork')),
|
||||
'content_type_name' => $primaryCategory?->contentType?->name ?? '',
|
||||
'content_type_slug' => $primaryCategory?->contentType?->slug ?? '',
|
||||
'category_name' => $categoryName,
|
||||
'category_slug' => $categorySlug,
|
||||
'gid_num' => $gid,
|
||||
'thumb_url' => $present['url'],
|
||||
'thumb_srcset' => $present['srcset'] ?? $present['url'],
|
||||
'width' => $artwork->width,
|
||||
'height' => $artwork->height,
|
||||
'uname' => $artwork->user->name ?? 'Skinbase',
|
||||
'username' => $username,
|
||||
];
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Models\ContentType;
|
||||
use App\Models\Tag;
|
||||
use App\Services\ArtworkSearchService;
|
||||
use App\Services\EarlyGrowth\GridFiller;
|
||||
use App\Services\Tags\TagDiscoveryService;
|
||||
use App\Services\ThumbnailPresenter;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
@@ -18,20 +19,27 @@ final class TagController extends Controller
|
||||
public function __construct(
|
||||
private readonly ArtworkSearchService $search,
|
||||
private readonly GridFiller $gridFiller,
|
||||
private readonly TagDiscoveryService $tagDiscovery,
|
||||
) {}
|
||||
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$tags = \App\Models\Tag::withCount('artworks')
|
||||
->orderByDesc('artworks_count')
|
||||
->paginate(80)
|
||||
->withQueryString();
|
||||
$query = trim((string) $request->query('q', ''));
|
||||
$featuredTags = $this->tagDiscovery->featuredTags();
|
||||
$risingTags = $this->tagDiscovery->risingTags($featuredTags);
|
||||
$tags = $this->tagDiscovery->paginatedTags($query);
|
||||
$tagStats = $this->tagDiscovery->stats($tags->total());
|
||||
|
||||
return view('web.tags.index', [
|
||||
'tags' => $tags,
|
||||
'page_title' => 'Browse Tags — Skinbase',
|
||||
'page_canonical' => route('tags.index'),
|
||||
'page_robots' => 'index,follow',
|
||||
'tags' => $tags,
|
||||
'query' => $query,
|
||||
'featuredTags' => $featuredTags,
|
||||
'risingTags' => $risingTags,
|
||||
'tagStats' => $tagStats,
|
||||
'page_title' => 'Browse Tags — Skinbase',
|
||||
'page_meta_description' => 'Explore the most-used artwork tags on Skinbase and jump straight into the styles, themes, and aesthetics you want to browse.',
|
||||
'page_canonical' => route('tags.index'),
|
||||
'page_robots' => 'index,follow',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -82,6 +90,8 @@ final class TagController extends Controller
|
||||
return (object) [
|
||||
'id' => $a->id,
|
||||
'name' => $a->title ?? ($a->name ?? null),
|
||||
'content_type_name' => $primaryCategory?->contentType?->name ?? '',
|
||||
'content_type_slug' => $primaryCategory?->contentType?->slug ?? '',
|
||||
'category_name' => $primaryCategory->name ?? '',
|
||||
'category_slug' => $primaryCategory->slug ?? '',
|
||||
'thumb_url' => $present['url'] ?? ($a->thumbUrl('md') ?? null),
|
||||
@@ -111,6 +121,15 @@ final class TagController extends Controller
|
||||
];
|
||||
$gallerySort = $sortMapToGallery[$sort] ?? 'trending';
|
||||
|
||||
$sortLabels = [
|
||||
'popular' => 'Most viewed',
|
||||
'likes' => 'Most liked',
|
||||
'latest' => 'Latest uploads',
|
||||
'downloads' => 'Most downloaded',
|
||||
];
|
||||
|
||||
$relatedTags = $this->tagDiscovery->relatedTags($tag);
|
||||
|
||||
// Build simple pagination SEO links
|
||||
$prev = method_exists($artworks, 'previousPageUrl') ? $artworks->previousPageUrl() : null;
|
||||
$next = method_exists($artworks, 'nextPageUrl') ? $artworks->nextPageUrl() : null;
|
||||
@@ -122,6 +141,7 @@ final class TagController extends Controller
|
||||
'contentType' => null,
|
||||
'category' => null,
|
||||
'artworks' => $artworks,
|
||||
'gallery_nav_section' => 'tags',
|
||||
'current_sort' => $gallerySort,
|
||||
'sort_options' => [
|
||||
['value' => 'trending', 'label' => '🔥 Trending'],
|
||||
@@ -129,8 +149,17 @@ final class TagController extends Controller
|
||||
['value' => 'top-rated', 'label' => '⭐ Top Rated'],
|
||||
['value' => 'latest', 'label' => '🕐 Latest'],
|
||||
],
|
||||
'hero_title' => $tag->name,
|
||||
'hero_description' => 'Artworks tagged "' . $tag->name . '"',
|
||||
'hero_title' => '#' . $tag->name,
|
||||
'hero_description' => 'Browse artworks tagged "' . $tag->name . '" and jump between the strongest matching uploads on Skinbase.',
|
||||
'tag_context' => [
|
||||
'name' => $tag->name,
|
||||
'slug' => $tag->slug,
|
||||
'artworks_total' => $artworks->total(),
|
||||
'usage_count' => (int) $tag->usage_count,
|
||||
'current_sort_label' => $sortLabels[$sort] ?? 'Most viewed',
|
||||
'rss_url' => route('rss.tag', ['slug' => $tag->slug]),
|
||||
'related_tags' => $relatedTags,
|
||||
],
|
||||
'breadcrumbs' => collect([
|
||||
(object) ['name' => 'Home', 'url' => '/'],
|
||||
(object) ['name' => 'Tags', 'url' => route('tags.index')],
|
||||
|
||||
Reference in New Issue
Block a user