feat(artists): change albums table to grid on artist pages

This commit is contained in:
Cory Dransfeldt 2025-05-24 13:28:04 -07:00
parent 80b0499550
commit 9b4baad5fb
No known key found for this signature in database
14 changed files with 180 additions and 88 deletions

View file

@ -0,0 +1,22 @@
<?php
namespace App\Classes;
class GlobalsFetcher extends PageFetcher
{
public function fetch(): ?array
{
$cacheKey = "globals";
$cached = $this->cacheGet($cacheKey);
if ($cached) return $cached;
$globals = $this->fetchFromApi("optimized_globals");
if (empty($globals)) return null;
$this->cacheSet($cacheKey, $globals[0]);
return $globals[0];
}
}

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "coryd.dev", "name": "coryd.dev",
"version": "6.1.14", "version": "6.2.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "coryd.dev", "name": "coryd.dev",
"version": "6.1.14", "version": "6.2.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"minisearch": "^7.1.2", "minisearch": "^7.1.2",

View file

@ -1,6 +1,6 @@
{ {
"name": "coryd.dev", "name": "coryd.dev",
"version": "6.1.14", "version": "6.2.0",
"description": "The source for my personal site. Built using 11ty (and other tools).", "description": "The source for my personal site. Built using 11ty (and other tools).",
"type": "module", "type": "module",
"engines": { "engines": {

View file

@ -17,8 +17,7 @@ SELECT
CONCAT('/', df.filename_disk) AS image, CONCAT('/', df.filename_disk) AS image,
json_build_object( json_build_object(
'title', ar.name_string, 'title', ar.name_string,
'image', 'image', CONCAT('/', df.filename_disk),
CONCAT('/', df.filename_disk),
'url', ar.slug, 'url', ar.slug,
'alt', CONCAT(to_char(ar.total_plays, 'FM999,999,999,999'), ' plays of ', ar.name_string), 'alt', CONCAT(to_char(ar.total_plays, 'FM999,999,999,999'), ' plays of ', ar.name_string),
'subtext', CONCAT(to_char(ar.total_plays, 'FM999,999,999,999'), ' plays') 'subtext', CONCAT(to_char(ar.total_plays, 'FM999,999,999,999'), ' plays')
@ -27,80 +26,107 @@ SELECT
'title', ar.name_string, 'title', ar.name_string,
'genre', g.name, 'genre', g.name,
'genre_url', g.slug, 'genre_url', g.slug,
'emoji', CASE WHEN ar.emoji IS NOT NULL THEN ar.emoji ELSE g.emoji END, 'emoji', COALESCE(ar.emoji, g.emoji),
'plays', to_char(ar.total_plays, 'FM999,999,999,999'), 'plays', to_char(ar.total_plays, 'FM999,999,999,999'),
'image', CONCAT('/', df.filename_disk), 'image', CONCAT('/', df.filename_disk),
'url', ar.slug, 'url', ar.slug,
'alt', CONCAT(to_char(ar.total_plays, 'FM999,999,999,999'), ' plays of ', ar.name_string) 'alt', CONCAT(to_char(ar.total_plays, 'FM999,999,999,999'), ' plays of ', ar.name_string)
) AS table, ) AS table,
( (
SELECT SELECT json_agg(
json_agg(json_build_object('name', a.name, 'release_year', a.release_year, 'total_plays', to_char(a.total_plays, 'FM999,999,999,999'), json_build_object(
'art', df_album.filename_disk) 'name', a.name,
ORDER BY a.release_year) 'release_year', a.release_year,
FROM 'total_plays', to_char(a.total_plays, 'FM999,999,999,999'),
albums a 'art', df_album.filename_disk,
'grid', json_build_object(
'title', a.name,
'image', CONCAT('/', df_album.filename_disk),
'alt', CONCAT(to_char(a.total_plays, 'FM999,999,999,999'), ' plays of ', a.name),
'subtext',
CASE
WHEN a.total_plays > 0
THEN CONCAT(a.release_year, '', to_char(a.total_plays, 'FM999,999,999,999'), ' plays')
ELSE CONCAT(a.release_year, '')
END
)
)
ORDER BY a.release_year
)
FROM albums a
LEFT JOIN directus_files df_album ON a.art = df_album.id LEFT JOIN directus_files df_album ON a.art = df_album.id
WHERE WHERE a.artist = ar.id
a.artist = ar.id) AS albums, ) AS albums,
( (
SELECT SELECT json_agg(
json_agg(json_build_object('id', c.id, 'date', c.date, 'venue_name', v.name, 'venue_name_short', trim(split_part(v.name, ',', 1)), 'venue_latitude', v.latitude, 'venue_longitude', v.longitude, 'notes', c.notes) json_build_object(
ORDER BY c.date DESC) 'id', c.id,
FROM 'date', c.date,
concerts c 'venue_name', v.name,
'venue_name_short', trim(split_part(v.name, ',', 1)),
'venue_latitude', v.latitude,
'venue_longitude', v.longitude,
'notes', c.notes
)
ORDER BY c.date DESC
)
FROM concerts c
LEFT JOIN venues v ON c.venue = v.id LEFT JOIN venues v ON c.venue = v.id
WHERE WHERE c.artist = ar.id
c.artist = ar.id) AS concerts, ) AS concerts,
( (
SELECT SELECT json_agg(
json_agg(json_build_object('title', b.title, 'author', b.author, 'url', b.slug) json_build_object('title', b.title, 'author', b.author, 'url', b.slug)
ORDER BY b.title ASC) ORDER BY b.title ASC
FROM )
books_artists ba FROM books_artists ba
LEFT JOIN books b ON ba.books_id = b.id LEFT JOIN books b ON ba.books_id = b.id
WHERE WHERE ba.artists_id = ar.id
ba.artists_id = ar.id) AS books, ) AS books,
( (
SELECT SELECT json_agg(
json_agg(json_build_object('title', m.title, 'year', m.year, 'url', m.slug) json_build_object('title', m.title, 'year', m.year, 'url', m.slug)
ORDER BY m.year DESC) ORDER BY m.year DESC
FROM )
movies_artists ma FROM movies_artists ma
LEFT JOIN movies m ON ma.movies_id = m.id LEFT JOIN movies m ON ma.movies_id = m.id
WHERE WHERE ma.artists_id = ar.id
ma.artists_id = ar.id) AS movies, ) AS movies,
( (
SELECT SELECT json_agg(
json_agg(json_build_object('title', s.title, 'year', s.year, 'url', s.slug) json_build_object('title', s.title, 'year', s.year, 'url', s.slug)
ORDER BY s.year DESC) ORDER BY s.year DESC
FROM )
shows_artists sa FROM shows_artists sa
LEFT JOIN shows s ON sa.shows_id = s.id LEFT JOIN shows s ON sa.shows_id = s.id
WHERE WHERE sa.artists_id = ar.id
sa.artists_id = ar.id) AS shows, ) AS shows,
( (
SELECT SELECT json_agg(
json_agg(json_build_object('title', p.title, 'date', p.date, 'url', p.slug) json_build_object('title', p.title, 'date', p.date, 'url', p.slug)
ORDER BY p.date DESC) ORDER BY p.date DESC
FROM )
posts_artists pa FROM posts_artists pa
LEFT JOIN posts p ON pa.posts_id = p.id LEFT JOIN posts p ON pa.posts_id = p.id
WHERE WHERE pa.artists_id = ar.id
pa.artists_id = ar.id) AS posts, ) AS posts,
( (
SELECT SELECT json_agg(
json_agg(json_build_object('name', related_ar.name_string, 'url', related_ar.slug, 'country', related_ar.country, 'total_plays', to_char(related_ar.total_plays, 'FM999,999,999,999')) json_build_object(
ORDER BY related_ar.name_string) 'name', related_ar.name_string,
FROM 'url', related_ar.slug,
related_artists ra 'country', related_ar.country,
'total_plays', to_char(related_ar.total_plays, 'FM999,999,999,999')
)
ORDER BY related_ar.name_string
)
FROM related_artists ra
LEFT JOIN artists related_ar ON ra.related_artists_id = related_ar.id LEFT JOIN artists related_ar ON ra.related_artists_id = related_ar.id
WHERE WHERE ra.artists_id = ar.id
ra.artists_id = ar.id) AS related_artists ) AS related_artists
FROM FROM artists ar
artists ar LEFT JOIN directus_files df ON ar.art = df.id
LEFT JOIN directus_files df ON ar.art = df.id LEFT JOIN genres g ON ar.genres = g.id
LEFT JOIN genres g ON ar.genres = g.id
GROUP BY GROUP BY
ar.id, ar.id,
df.filename_disk, df.filename_disk,

View file

@ -10,7 +10,8 @@
'device-tv-old' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-device-tv-old"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 7m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v9a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M16 3l-4 4l-4 -4" /><path d="M15 7v13" /><path d="M18 15v.01" /><path d="M18 12v.01" /></svg>', 'device-tv-old' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-device-tv-old"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 7m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v9a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M16 3l-4 4l-4 -4" /><path d="M15 7v13" /><path d="M18 15v.01" /><path d="M18 12v.01" /></svg>',
'headphones' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-headphones"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 13m0 2a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-1a2 2 0 0 1 -2 -2z" /><path d="M15 13m0 2a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-1a2 2 0 0 1 -2 -2z" /><path d="M4 15v-3a8 8 0 0 1 16 0v3" /></svg>', 'headphones' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-headphones"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 13m0 2a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-1a2 2 0 0 1 -2 -2z" /><path d="M15 13m0 2a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-1a2 2 0 0 1 -2 -2z" /><path d="M4 15v-3a8 8 0 0 1 16 0v3" /></svg>',
'movie' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-movie"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z" /><path d="M8 4l0 16" /><path d="M16 4l0 16" /><path d="M4 8l4 0" /><path d="M4 16l4 0" /><path d="M4 12l16 0" /><path d="M16 8l4 0" /><path d="M16 16l4 0" /></svg>', 'movie' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-movie"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z" /><path d="M8 4l0 16" /><path d="M16 4l0 16" /><path d="M4 8l4 0" /><path d="M4 16l4 0" /><path d="M4 12l16 0" /><path d="M16 8l4 0" /><path d="M16 16l4 0" /></svg>',
'star' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-star"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" /></svg>' 'star' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-star"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" /></svg>',
'vinyl' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-vinyl"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M16 3.937a9 9 0 1 0 5 8.063" /><path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M20 4m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M20 4l-3.5 10l-2.5 2" /></svg>'
]; ];
return $icons[$iconName] ?? '<span class="icon-placeholder">[Missing: ' . htmlspecialchars($iconName) . ']</span>'; return $icons[$iconName] ?? '<span class="icon-placeholder">[Missing: ' . htmlspecialchars($iconName) . ']</span>';

View file

@ -1,5 +1,57 @@
<?php <?php
function renderMediaGrid(array $items, string $cdnUrl, string $shape = 'square', int $count = 0, string $loading = 'lazy') {
$imageClass = 'square';
$width = 150;
$height = 150;
if ($shape === 'vertical') {
$imageClass = 'vertical';
$width = 120;
$height = 184;
}
$limit = $count > 0 ? $count : count($items);
echo '<div class="media-grid ' . htmlspecialchars($shape) . '">';
foreach (array_slice($items, 0, $limit) as $item) {
$grid = $item['grid'] ?? $item;
$alt = htmlspecialchars($grid['alt'] ?? '');
$image = htmlspecialchars($grid['image'] ?? '');
$title = htmlspecialchars($grid['title'] ?? '');
$subtext = htmlspecialchars($grid['subtext'] ?? '');
$url = $grid['url'] ?? null;
$openLink = $url ? '<a href="' . htmlspecialchars($url) . '" title="' . $alt . '">' : '';
$closeLink = $url ? '</a>' : '';
echo $openLink;
echo '<div class="media-grid-item">';
if ($title || $subtext) {
echo '<div class="meta-text media-highlight">';
if ($title) echo '<div class="header">' . $title . '</div>';
if ($subtext) echo '<div class="subheader">' . $subtext . '</div>';
echo '</div>';
}
echo '<img
srcset="' . $cdnUrl . $image . '?class=' . $imageClass . 'sm&type=webp ' . $width . 'w, ' .
$cdnUrl . $image . '?class=' . $imageClass . 'md&type=webp ' . ($width * 2) . 'w"
sizes="(max-width: 450px) ' . $width . 'px, ' . ($width * 2) . 'px"
src="' . $cdnUrl . $image . '?class=' . $imageClass . 'sm&type=webp"
alt="' . $alt . '"
loading="' . $loading . '"
decoding="async"
width="' . $width . '"
height="' . $height . '"
/>';
echo '</div>';
echo $closeLink;
}
echo '</div>';
}
function renderAssociatedMedia($artists = [], $books = [], $genres = [], $movies = [], $posts = [], $shows = []) function renderAssociatedMedia($artists = [], $books = [], $genres = [], $movies = [], $posts = [], $shows = [])
{ {
$media = array_merge($artists, $books, $genres, $movies, $posts, $shows); $media = array_merge($artists, $books, $genres, $movies, $posts, $shows);

View file

@ -368,7 +368,9 @@ article {
} }
h3 { h3 {
margin-top: 0; &:not(:has(svg)) {
margin-top: 0;
}
&:has(+ .tags) { &:has(+ .tags) {
margin-bottom: 0; margin-bottom: 0;

View file

@ -3,6 +3,7 @@
require __DIR__ . "/../../vendor/autoload.php"; require __DIR__ . "/../../vendor/autoload.php";
require __DIR__ . "/../../server/utils/init.php"; require __DIR__ . "/../../server/utils/init.php";
use App\Classes\GlobalsFetcher;
use App\Classes\ArtistFetcher; use App\Classes\ArtistFetcher;
use voku\helper\HtmlMin; use voku\helper\HtmlMin;
@ -11,8 +12,8 @@
if (strpos($url, "music/artists/") !== 0) redirectTo404(); if (strpos($url, "music/artists/") !== 0) redirectTo404();
$fetcher = new ArtistFetcher(); $globals = (new GlobalsFetcher())->fetch();
$artist = $fetcher->fetch($url); $artist = (new ArtistFetcher())->fetch($url);
if (!$artist) redirectTo404(); if (!$artist) redirectTo404();

View file

@ -11,8 +11,7 @@
if (!preg_match('/^reading\/books\/([\dXx-]+)$/', $url)) redirectTo404(); if (!preg_match('/^reading\/books\/([\dXx-]+)$/', $url)) redirectTo404();
$fetcher = new BookFetcher(); $book = (new BookFetcher())->fetch($url);
$book = $fetcher->fetch($url);
if (!$book) redirectTo404(); if (!$book) redirectTo404();

View file

@ -11,8 +11,7 @@
if (!preg_match('/^music\/genres\/[\w-]+$/', $url)) redirectTo404(); if (!preg_match('/^music\/genres\/[\w-]+$/', $url)) redirectTo404();
$fetcher = new GenreFetcher(); $genre = (new GenreFetcher())->fetch($url);
$genre = $fetcher->fetch($url);
if (!$genre) redirectTo404(); if (!$genre) redirectTo404();

View file

@ -11,8 +11,7 @@
if (!preg_match('/^watching\/movies\/[\w-]+$/', $url)) redirectTo404(); if (!preg_match('/^watching\/movies\/[\w-]+$/', $url)) redirectTo404();
$fetcher = new MovieFetcher(); $movie = (new MovieFetcher())->fetch($url);
$movie = $fetcher->fetch($url);
if (!$movie) redirectTo404(); if (!$movie) redirectTo404();

View file

@ -11,8 +11,7 @@
if (!preg_match('/^watching\/shows\/[\w-]+$/', $url)) redirectTo404(); if (!preg_match('/^watching\/shows\/[\w-]+$/', $url)) redirectTo404();
$fetcher = new ShowFetcher(); $show = (new ShowFetcher())->fetch($url);
$show = $fetcher->fetch($url);
if (!$show) redirectTo404(); if (!$show) redirectTo404();

View file

@ -27,8 +27,7 @@
$page = isset($matches[2]) ? max(1, (int)$matches[2]) : 1; $page = isset($matches[2]) ? max(1, (int)$matches[2]) : 1;
$pageSize = 20; $pageSize = 20;
$fetcher = new TagFetcher(); $tagged = (new TagFetcher())->fetch($tag, $page, $pageSize);
$tagged = $fetcher->fetch($tag, $page, $pageSize);
if (!$tagged || count($tagged) === 0) { if (!$tagged || count($tagged) === 0) {
header("Location: /404/", true, 302); header("Location: /404/", true, 302);

View file

@ -103,20 +103,13 @@ schema: artist
<?php endforeach; ?> <?php endforeach; ?>
</ul> </ul>
<?php endif; ?> <?php endif; ?>
<table> <?php if (!empty($artist["albums"])): ?>
<tr> <h3>
<th>Album</th> <?= getTablerIcon('vinyl') ?>
<th>Plays</th> Albums
<th>Year</th> </h3>
</tr> <?php renderMediaGrid($artist["albums"], $globals["cdn_url"]); ?>
<?php foreach ($artist["albums"] as $album): ?> <?php endif; ?>
<tr>
<td><?= htmlspecialchars($album["name"]) ?></td>
<td><?= $album["total_plays"] > 0 ? $album["total_plays"] : "-" ?></td>
<td><?= $album["release_year"] ?></td>
</tr>
<?php endforeach; ?>
</table>
</article> </article>
<?php <?php
$html = ob_get_clean(); $html = ob_get_clean();