feat(artists): change albums table to grid on artist pages
This commit is contained in:
parent
80b0499550
commit
9b4baad5fb
14 changed files with 180 additions and 88 deletions
22
api/Classes/GlobalsFetcher.php
Normal file
22
api/Classes/GlobalsFetcher.php
Normal 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
4
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>';
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -368,7 +368,9 @@ article {
|
|||
}
|
||||
|
||||
h3 {
|
||||
&:not(:has(svg)) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&:has(+ .tags) {
|
||||
margin-bottom: 0;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue