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

7685
astro/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@
"dependencies": {
"@astrojs/preact": "^5.1.2",
"@directus/sdk": "^21.2.0",
"@immich/justified-layout-wasm": "^0.4.3",
"@rollup/plugin-graphql": "^2.0.5",
"@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.2.4",
@@ -26,7 +27,7 @@
"reading-time": "^1.5.0",
"tailwindcss": "^4.2.4",
"tslib": "^2.8.1",
"vite": "^8.0.9 "
"vite": "^8.0.9"
},
"devDependencies": {
"@types/markdown-it": "^14.1.2",

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>
)
}

View File

@@ -33,6 +33,7 @@ export async function getAllAlbums(settings: GlobalSettings): Promise<PhotoAlbum
const album: PhotoAlbum = {
exists: true,
type: "PhotoAlbum",
id: albumRecord["id"],
title: albumRecord["title"],
description: albumRecord["description"],
url: albumRecord["url"],
@@ -116,6 +117,7 @@ export async function getAlbum(settings: GlobalSettings, route: string): Promise
const album: PhotoAlbum = {
exists: true,
type: "PhotoAlbum",
id: albumRecord["id"],
title: albumRecord["title"],
description: albumRecord["description"],
url: albumRecord["url"],
@@ -196,6 +198,7 @@ export async function getLastAlbums(amount: number) {
const album: PhotoAlbum = {
exists: true,
type: "PhotoAlbum",
id: albumRecord["id"],
title: albumRecord["title"],
description: albumRecord["description"],
url: albumRecord["url"],
@@ -281,6 +284,7 @@ export async function getCategoryAlbums(settings: GlobalSettings, route: string)
const album: PhotoAlbum = {
exists: true,
type: "PhotoAlbum",
id: albumRecord["id"],
title: albumRecord["title"],
description: albumRecord["description"],
url: albumRecord["url"],

View File

@@ -13,8 +13,8 @@ import BlogPost from "@/components/blogs/BlogPost.astro";
import ProjectPost from "@/components/projects/ProjectPost.astro";
import CategoryIndex from "@/components/photos/CategoryIndex.astro";
import Category from "@/components/photos/Category.astro";
import AlbumPage from "@/components/photos/Album.astro";
import { getImageUrl } from "@/lib/images";
import AlbumPage from "@/components/photos/AlbumPage.astro";
export async function getStaticPaths() {
const settings = await getSettings();
@@ -148,7 +148,7 @@ if (page === null || page.page === null || !page.page.exists) {
{ page.pageType === "PhotoAlbum" && (
<WebpageLayout settings={{
searchEngine: {
title: page.page.category.title,
title: page.page.title,
description: `See the photos in the ${page.page.category.title.toLowerCase()} category.`,
allowCrawlers: true,
canonical: null,

View File

@@ -2,6 +2,8 @@ type PhotoAlbum = {
type: "PhotoAlbum";
exists: boolean;
id: string;
title: string;
url: string;
description: string | null;

View File

@@ -15,3 +15,10 @@ type PhotoAlbumItem = {
photo: PhotoProps;
url: string;
}
type PhotoAlbumGalleryItem = {
id: string;
photo: PhotoProps;
text: string | null;
url: string;
}