Create base for Albums

This commit is contained in:
itsfinniii
2026-04-26 13:52:56 +02:00
parent abaea70c7b
commit f86630bcb0
8 changed files with 4982 additions and 2830 deletions

View File

@@ -0,0 +1,41 @@
---
import { getAlbumRoute, getPhotoRoute } from '@/lib/routing';
import { AlbumPhotos } from './Album.tsx';
import { getImageSize, getImageUrl } from '@/lib/images';
import { getSettings } from '@/content/settings/settings';
interface Props {
album: PhotoAlbum;
}
const settings = await getSettings();
const album = Astro.props.album;
const remappedPhotos: PhotoAlbumGalleryItem[] = [];
album.photos.forEach((photo) => {
const resizedImage = getImageSize(photo.photo.width, photo.photo.height, 0.67);
remappedPhotos.push({
id: photo.id,
url: getPhotoRoute(settings.photo, album, photo),
photo: {
url: getImageUrl(photo.photo.url),
width: resizedImage.width,
height: resizedImage.height
},
text: photo.text
});
});
---
<div
id={`album-${album.id}`}
class="flex lg:flex-col flex-col py-12 px-12 lg:container mx-auto gap-y-10 gap-x-18 w-full"
>
<div class="flex flex-col gap-7">
<h1 class="text-5xl font-bold">{album.title}</h1>
<AlbumPhotos client:only client:load photos={remappedPhotos} />
</div>
</div>

View File

@@ -0,0 +1,84 @@
import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks";
import { JustifiedLayout } from '@immich/justified-layout-wasm';
export function AlbumPhotos(props: { photos: PhotoAlbumGalleryItem[] }) {
const containerRef = useRef(null);
const [ hasMounted, setHasMounted ] = useState<boolean>(false);
const [ layout, setLayout ] = useState<JustifiedLayout | null>(null);
const [ containerWidth, setContainerWidth ] = useState<number | null>(null);
const [ containerHeight, setContainerHeight ] = useState<number | null>(null);
useEffect(() => {
setHasMounted(true);
}, []);
useLayoutEffect(() => {
if (!hasMounted) {
return;
}
const observer = new ResizeObserver((entries) => {
setContainerWidth(entries[0].contentRect.width);
});
if (containerRef.current) {
observer.observe(containerRef.current);
}
return () => observer.disconnect();
}, [ hasMounted ]);
useEffect(() => {
if (containerWidth === null || !hasMounted) {
return;
}
const aspectRatios = new Float32Array(props.photos.map((photo => photo.photo.width / photo.photo.height)));
const justifiedLayout = new JustifiedLayout(aspectRatios, {
rowHeight: 265,
rowWidth: containerWidth,
spacing: 4,
heightTolerance: 0.11
});
setContainerHeight(justifiedLayout.containerHeight);
setLayout(justifiedLayout);
}, [ containerWidth, hasMounted ])
return (
<div ref={containerRef} id={`albumgallery`}>
{ layout !== null ? (
<div class="relative w-full" style={{ height: containerHeight }}>
{ props.photos.map((photo, index: number) => {
const layoutPosition = layout.getPosition(index);
return (
<a
href={photo.url}
key={`photo-${index}`}
class="group absolute overflow-hidden bg-neutral-200"
style={{
top: layoutPosition.top,
left: layoutPosition.left,
width: layoutPosition.width,
height: layoutPosition.height
}}
>
<img
src={photo.photo.url}
alt={photo.text ?? ""}
class="group-hover:scale-[101.5%] duration-200 w-full h-full"
loading="lazy"
/>
</a>
)
}) }
</div>
) : (
<div>Loading...</div>
) }
</div>
)
}