feat: added individual photos
17b6dfaa
5 file(s) · +80 −22
| 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", |
| 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 |
| 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> |
|
| 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 | + | }; |
| 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> |