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",
"version": "6.1.14",
"version": "6.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "coryd.dev",
"version": "6.1.14",
"version": "6.2.0",
"license": "MIT",
"dependencies": {
"minisearch": "^7.1.2",

View file

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

View file

@ -17,8 +17,7 @@ SELECT
CONCAT('/', df.filename_disk) AS image,
json_build_object(
'title', ar.name_string,
'image',
CONCAT('/', df.filename_disk),
'image', CONCAT('/', df.filename_disk),
'url', ar.slug,
'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')
@ -27,80 +26,107 @@ SELECT
'title', ar.name_string,
'genre', g.name,
'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'),
'image', CONCAT('/', df.filename_disk),
'url', ar.slug,
'alt', CONCAT(to_char(ar.total_plays, 'FM999,999,999,999'), ' plays of ', ar.name_string)
) AS table,
(
SELECT
json_agg(json_build_object('name', a.name, 'release_year', a.release_year, 'total_plays', to_char(a.total_plays, 'FM999,999,999,999'),
'art', df_album.filename_disk)
ORDER BY a.release_year)
FROM
albums a
SELECT json_agg(
json_build_object(
'name', a.name,
'release_year', a.release_year,
'total_plays', to_char(a.total_plays, 'FM999,999,999,999'),
'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
WHERE
a.artist = ar.id) AS albums,
WHERE a.artist = ar.id
) AS albums,
(
SELECT
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)
ORDER BY c.date DESC)
FROM
concerts c
SELECT 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
)
ORDER BY c.date DESC
)
FROM concerts c
LEFT JOIN venues v ON c.venue = v.id
WHERE
c.artist = ar.id) AS concerts,
WHERE c.artist = ar.id
) AS concerts,
(
SELECT
json_agg(json_build_object('title', b.title, 'author', b.author, 'url', b.slug)
ORDER BY b.title ASC)
FROM
books_artists ba
SELECT json_agg(
json_build_object('title', b.title, 'author', b.author, 'url', b.slug)
ORDER BY b.title ASC
)
FROM books_artists ba
LEFT JOIN books b ON ba.books_id = b.id
WHERE
ba.artists_id = ar.id) AS books,
WHERE ba.artists_id = ar.id
) AS books,
(
SELECT
json_agg(json_build_object('title', m.title, 'year', m.year, 'url', m.slug)
ORDER BY m.year DESC)
FROM
movies_artists ma
SELECT json_agg(
json_build_object('title', m.title, 'year', m.year, 'url', m.slug)
ORDER BY m.year DESC
)
FROM movies_artists ma
LEFT JOIN movies m ON ma.movies_id = m.id
WHERE
ma.artists_id = ar.id) AS movies,
WHERE ma.artists_id = ar.id
) AS movies,
(
SELECT
json_agg(json_build_object('title', s.title, 'year', s.year, 'url', s.slug)
ORDER BY s.year DESC)
FROM
shows_artists sa
SELECT json_agg(
json_build_object('title', s.title, 'year', s.year, 'url', s.slug)
ORDER BY s.year DESC
)
FROM shows_artists sa
LEFT JOIN shows s ON sa.shows_id = s.id
WHERE
sa.artists_id = ar.id) AS shows,
WHERE sa.artists_id = ar.id
) AS shows,
(
SELECT
json_agg(json_build_object('title', p.title, 'date', p.date, 'url', p.slug)
ORDER BY p.date DESC)
FROM
posts_artists pa
SELECT json_agg(
json_build_object('title', p.title, 'date', p.date, 'url', p.slug)
ORDER BY p.date DESC
)
FROM posts_artists pa
LEFT JOIN posts p ON pa.posts_id = p.id
WHERE
pa.artists_id = ar.id) AS posts,
WHERE pa.artists_id = ar.id
) AS posts,
(
SELECT
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'))
ORDER BY related_ar.name_string)
FROM
related_artists ra
SELECT 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')
)
ORDER BY related_ar.name_string
)
FROM related_artists ra
LEFT JOIN artists related_ar ON ra.related_artists_id = related_ar.id
WHERE
ra.artists_id = ar.id) AS related_artists
FROM
artists ar
LEFT JOIN directus_files df ON ar.art = df.id
LEFT JOIN genres g ON ar.genres = g.id
WHERE ra.artists_id = ar.id
) AS related_artists
FROM artists ar
LEFT JOIN directus_files df ON ar.art = df.id
LEFT JOIN genres g ON ar.genres = g.id
GROUP BY
ar.id,
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>',
'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>',
'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>';

View file

@ -1,5 +1,57 @@
<?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 = [])
{
$media = array_merge($artists, $books, $genres, $movies, $posts, $shows);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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