Banner rotator respects tags

This commit is contained in:
2026-06-22 06:40:49 -04:00
parent d1f41d91bb
commit d18f75b144
3 changed files with 78 additions and 7 deletions
+15 -2
View File
@@ -3,13 +3,14 @@ import type { HomepageBannerImage } from '../lib/fallback-data';
type Props = { type Props = {
images: HomepageBannerImage[]; images: HomepageBannerImage[];
randomizeAfterFirst?: boolean;
}; };
const { images } = Astro.props; const { images, randomizeAfterFirst = false } = Astro.props;
const slides = images.length > 0 ? images : [{ src: '/images/banner.png', alt: 'Swansea Airport banner' }]; const slides = images.length > 0 ? images : [{ src: '/images/banner.png', alt: 'Swansea Airport banner' }];
--- ---
<div class="banner-rotator" data-banner-rotator> <div class="banner-rotator" data-banner-rotator data-randomize-after-first={randomizeAfterFirst ? 'true' : undefined}>
{slides.map((image, index) => ( {slides.map((image, index) => (
<img <img
class:list={['banner-slide', { active: index === 0 }]} class:list={['banner-slide', { active: index === 0 }]}
@@ -31,6 +32,18 @@ const slides = images.length > 0 ? images : [{ src: '/images/banner.png', alt: '
const slides = Array.from(root.querySelectorAll('[data-banner-slide]')); const slides = Array.from(root.querySelectorAll('[data-banner-slide]'));
if (slides.length < 2) return; if (slides.length < 2) return;
if (root.getAttribute('data-randomize-after-first') === 'true') {
const firstSlide = slides[0];
const restSlides = slides.slice(1);
for (let index = restSlides.length - 1; index > 0; index -= 1) {
const swapIndex = Math.floor(Math.random() * (index + 1));
[restSlides[index], restSlides[swapIndex]] = [restSlides[swapIndex], restSlides[index]];
}
slides.splice(0, slides.length, firstSlide, ...restSlides);
}
let currentIndex = 0; let currentIndex = 0;
window.setInterval(() => { window.setInterval(() => {
slides[currentIndex].classList.remove('active'); slides[currentIndex].classList.remove('active');
+62 -4
View File
@@ -59,6 +59,12 @@ type DirectusFile = {
filename_download?: string; filename_download?: string;
filename_disk?: string; filename_disk?: string;
type?: string; type?: string;
tags?: string[] | string | null;
};
type ImageFolderOptions = {
firstTag?: string;
shuffleRest?: boolean;
}; };
type EventTemplateRecord = { type EventTemplateRecord = {
@@ -170,6 +176,49 @@ function directusObjectKey(file: string | DirectusFile): string {
return `${fileId}${extensionFromFilename(typeof file === 'string' ? undefined : file.filename_download)}`; return `${fileId}${extensionFromFilename(typeof file === 'string' ? undefined : file.filename_download)}`;
} }
function fileTags(file: DirectusFile): string[] {
if (Array.isArray(file.tags)) {
return file.tags;
}
if (typeof file.tags === 'string') {
return file.tags
.split(',')
.map((tag) => tag.trim())
.filter(Boolean);
}
return [];
}
function hasTag(file: DirectusFile, tag: string): boolean {
const targetTag = tag.toLowerCase();
return fileTags(file).some((fileTag) => fileTag.toLowerCase() === targetTag);
}
function shuffleFiles(files: DirectusFile[]): DirectusFile[] {
const shuffled = [...files];
for (let index = shuffled.length - 1; index > 0; index -= 1) {
const swapIndex = Math.floor(Math.random() * (index + 1));
[shuffled[index], shuffled[swapIndex]] = [shuffled[swapIndex], shuffled[index]];
}
return shuffled;
}
function orderImageFiles(files: DirectusFile[], options: ImageFolderOptions): DirectusFile[] {
const { firstTag, shuffleRest } = options;
if (!firstTag) {
return shuffleRest ? shuffleFiles(files) : files;
}
const firstFiles = files.filter((file) => hasTag(file, firstTag));
const restFiles = files.filter((file) => !hasTag(file, firstTag));
return [...firstFiles, ...(shuffleRest ? shuffleFiles(restFiles) : restFiles)];
}
export function resolveDirectusAssetUrl(file: string | DirectusFile): string { export function resolveDirectusAssetUrl(file: string | DirectusFile): string {
const fileId = directusFileId(file); const fileId = directusFileId(file);
const r2ObjectKey = directusObjectKey(file); const r2ObjectKey = directusObjectKey(file);
@@ -200,7 +249,11 @@ async function findFolderByName(name: string): Promise<DirectusFolder | null> {
return folders[0] ?? null; return folders[0] ?? null;
} }
async function getImagesFromFolder(folderName: string, fallbackImages: HomepageBannerImage[]): Promise<HomepageBannerImage[]> { async function getImagesFromFolder(
folderName: string,
fallbackImages: HomepageBannerImage[],
options: ImageFolderOptions = {},
): Promise<HomepageBannerImage[]> {
try { try {
const folder = await findFolderByName(folderName); const folder = await findFolderByName(folderName);
if (!folder) { if (!folder) {
@@ -213,10 +266,11 @@ async function getImagesFromFolder(folderName: string, fallbackImages: HomepageB
endpoint.searchParams.set('sort', '-uploaded_on'); endpoint.searchParams.set('sort', '-uploaded_on');
endpoint.searchParams.set('filter[folder][_eq]', folder.id); endpoint.searchParams.set('filter[folder][_eq]', folder.id);
endpoint.searchParams.set('filter[type][_starts_with]', 'image/'); endpoint.searchParams.set('filter[type][_starts_with]', 'image/');
endpoint.searchParams.set('fields', 'id,title,description,filename_download,filename_disk,type'); endpoint.searchParams.set('fields', 'id,title,description,filename_download,filename_disk,type,tags');
const files = await readDirectusEndpoint<DirectusFile>(endpoint); const files = await readDirectusEndpoint<DirectusFile>(endpoint);
const images = files.map((file) => ({ const orderedFiles = orderImageFiles(files, options);
const images = orderedFiles.map((file) => ({
src: resolveDirectusAssetUrl(file), src: resolveDirectusAssetUrl(file),
alt: file.description || file.title || file.filename_download || 'Swansea Airport', alt: file.description || file.title || file.filename_download || 'Swansea Airport',
})); }));
@@ -234,7 +288,11 @@ async function getImagesFromFolder(folderName: string, fallbackImages: HomepageB
} }
} }
export const getHomepageBannerImages = () => getImagesFromFolder(homepageBannerFolder, fallbackHomepageBannerImages); export const getHomepageBannerImages = () =>
getImagesFromFolder(homepageBannerFolder, fallbackHomepageBannerImages, {
firstTag: 'first',
shuffleRest: true,
});
export const getCafePageImages = () => getImagesFromFolder(cafePageFolder, fallbackCafePageImages); export const getCafePageImages = () => getImagesFromFolder(cafePageFolder, fallbackCafePageImages);
function stripHtml(value = ''): string { function stripHtml(value = ''): string {
+1 -1
View File
@@ -53,7 +53,7 @@ const businessPromos = [
--- ---
<BaseLayout title="Home" description="Fast, clear airfield information for pilots and visitors."> <BaseLayout title="Home" description="Fast, clear airfield information for pilots and visitors.">
<BannerRotator images={bannerImages} /> <BannerRotator images={bannerImages} randomizeAfterFirst />
<section class="hero"> <section class="hero">
<div class="container hero-stack"> <div class="container hero-stack">