My personal photography website steve.phot
portfolio photography svelte sveltekit

feat: added individual photos

+80 -22
+1
package.json
··· 6 6 "scripts": { 7 7 "dev": "vite dev", 8 8 "build": "vite build", 9 + "deploy": "wrangler deploy --minify", 9 10 "preview": "vite preview", 10 11 "prepare": "svelte-kit sync || echo ''", 11 12 "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
+11 -18
src/routes/+page.server.ts
··· 7 7 export const load: PageServerLoad = async ({ platform }) => { 8 8 const db = platform?.env?.DB; 9 9 10 - if (!db) { 11 - // Fallback for local dev without D1 12 - const data = await import("$lib/data.json"); 13 - return { photos: data.default as ImageItem[] }; 14 - } 15 - 16 10 const result = await db 17 11 .prepare("SELECT * FROM photos ORDER BY date DESC") 18 12 .all(); 19 13 20 14 const photos: ImageItem[] = result.results.map( 21 15 (row: Record<string, unknown>) => ({ 22 - slug: row.slug as string, 23 - title: row.title as string, 24 - date: row.date as string, 16 + slug: row.slug, 17 + title: row.title, 18 + date: row.date, 25 19 image: `${R2_BASE_URL}/${row.image_key}`, 26 20 thumb: `${R2_BASE_URL}/${row.thumb_key}`, 27 - type: row.type as string, 28 - camera: row.camera as string, 29 - lens: row.lens as string, 30 - aperture: row.aperture as string, 31 - exposure: row.exposure as string, 32 - focalLength: row.focal_length as string, 33 - iso: row.iso as string, 34 - make: row.make as string, 35 - tags: JSON.parse((row.tags as string) || "[]"), 21 + type: row.type, 22 + camera: row.camera, 23 + lens: row.lens, 24 + aperture: row.aperture, 25 + exposure: row.exposure, 26 + focalLength: row.focal_length, 27 + iso: row.iso, 28 + make: row.make, 36 29 }), 37 30 ); 38 31
+3 -4
src/routes/+page.svelte
··· 1 1 <script lang="ts"> 2 2 import type { PageData } from "./$types"; 3 - import type { ImageItem } from "$lib/types"; 4 - 3 + type ImageItem = PageData["photos"][number]; 5 4 let { data }: { data: PageData } = $props(); 6 5 </script> 7 6 ··· 12 11 13 12 {#snippet figure(image: ImageItem)} 14 13 <div class="flex gap-2 px-8 pt-2"> 15 - <div class="flex-2 min-w-0"> 14 + <a href="/photo/{image.slug}" class="flex-2 min-w-0"> 16 15 <img class="max-w-full h-auto block" src={image.image} alt={image.title} /> 17 - </div> 16 + </a> 18 17 <div class="flex flex-col gap-1 flex-1 min-w-0 p-4"> 19 18 <h2 class="text-lg">{image.title.toUpperCase()}</h2> 20 19 <h3 class="text-sm">{image.make} {image.camera}</h3>
+37
src/routes/photo/[slug]/+page.server.ts
··· 1 + import type { PageServerLoad } from "./$types"; 2 + import type { ImageItem } from "$lib/types"; 3 + import { error } from "@sveltejs/kit"; 4 + 5 + const R2_BASE_URL = "https://r2.steve.photo"; 6 + 7 + export const load: PageServerLoad = async ({ platform, params }) => { 8 + const db = platform?.env?.DB; 9 + 10 + const result = await db 11 + .prepare("SELECT * FROM photos WHERE slug = ?") 12 + .bind(params.slug) 13 + .first(); 14 + 15 + if (!result) { 16 + throw error(404, "Photo not found"); 17 + } 18 + 19 + const photo: ImageItem = { 20 + slug: result.slug as string, 21 + title: result.title as string, 22 + date: result.date as string, 23 + image: `${R2_BASE_URL}/${result.image_key}`, 24 + thumb: `${R2_BASE_URL}/${result.thumb_key}`, 25 + type: result.type as string, 26 + camera: result.camera as string, 27 + lens: result.lens as string, 28 + aperture: result.aperture as string, 29 + exposure: result.exposure as string, 30 + focalLength: result.focal_length as string, 31 + iso: result.iso as string, 32 + make: result.make as string, 33 + tags: [], 34 + }; 35 + 36 + return { photo }; 37 + };
+28
src/routes/photo/[slug]/+page.svelte
··· 1 + <script lang="ts"> 2 + import type { PageData } from "./$types"; 3 + let { data }: { data: PageData } = $props(); 4 + </script> 5 + 6 + <div class="bg-[#121113] min-h-screen text-white"> 7 + <div class="fixed bg-[#121113] w-full py-4 px-8"> 8 + <a href="/" class="text-sm hover:underline">steve.photo</a> 9 + </div> 10 + 11 + <div class="flex gap-2 px-8 pt-16"> 12 + <div class="flex-2 min-w-0"> 13 + <img class="max-w-full h-auto block" src={data.photo.image} alt={data.photo.title} /> 14 + </div> 15 + <div class="flex flex-col gap-1 flex-1 min-w-0 p-4"> 16 + <h2 class="text-lg">{data.photo.title.toUpperCase()}</h2> 17 + <h3 class="text-sm">{data.photo.make} {data.photo.camera}</h3> 18 + <div class="flex flex-col gap-2 text-neutral-400 font-thin text-xs mt-4"> 19 + <p>{data.photo.focalLength}</p> 20 + <p>{data.photo.aperture}</p> 21 + <p>{data.photo.exposure}</p> 22 + <p>ISO {data.photo.iso}</p> 23 + <p>-</p> 24 + <p class="text-neutral-700 text-xs">{new Date(data.photo.date).toLocaleDateString()}</p> 25 + </div> 26 + </div> 27 + </div> 28 + </div>