tangled
alpha
login
or
join now
stevedylan.dev
/
steve.photo
2
fork
atom
My personal photography website
steve.phot
portfolio
photography
svelte
sveltekit
2
fork
atom
overview
issues
pulls
pipelines
feat: added individual photos
stevedylan.dev
1 month ago
17b6dfaa
a57c4837
+80
-22
5 changed files
expand all
collapse all
unified
split
package.json
src
routes
+page.server.ts
+page.svelte
photo
[slug]
+page.server.ts
+page.svelte
+1
package.json
···
6
6
"scripts": {
7
7
"dev": "vite dev",
8
8
"build": "vite build",
9
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
10
-
if (!db) {
11
11
-
// Fallback for local dev without D1
12
12
-
const data = await import("$lib/data.json");
13
13
-
return { photos: data.default as ImageItem[] };
14
14
-
}
15
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
22
-
slug: row.slug as string,
23
23
-
title: row.title as string,
24
24
-
date: row.date as string,
16
16
+
slug: row.slug,
17
17
+
title: row.title,
18
18
+
date: row.date,
25
19
image: `${R2_BASE_URL}/${row.image_key}`,
26
20
thumb: `${R2_BASE_URL}/${row.thumb_key}`,
27
27
-
type: row.type as string,
28
28
-
camera: row.camera as string,
29
29
-
lens: row.lens as string,
30
30
-
aperture: row.aperture as string,
31
31
-
exposure: row.exposure as string,
32
32
-
focalLength: row.focal_length as string,
33
33
-
iso: row.iso as string,
34
34
-
make: row.make as string,
35
35
-
tags: JSON.parse((row.tags as string) || "[]"),
21
21
+
type: row.type,
22
22
+
camera: row.camera,
23
23
+
lens: row.lens,
24
24
+
aperture: row.aperture,
25
25
+
exposure: row.exposure,
26
26
+
focalLength: row.focal_length,
27
27
+
iso: row.iso,
28
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
3
-
import type { ImageItem } from "$lib/types";
4
4
-
3
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
15
-
<div class="flex-2 min-w-0">
14
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
17
-
</div>
16
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
1
+
import type { PageServerLoad } from "./$types";
2
2
+
import type { ImageItem } from "$lib/types";
3
3
+
import { error } from "@sveltejs/kit";
4
4
+
5
5
+
const R2_BASE_URL = "https://r2.steve.photo";
6
6
+
7
7
+
export const load: PageServerLoad = async ({ platform, params }) => {
8
8
+
const db = platform?.env?.DB;
9
9
+
10
10
+
const result = await db
11
11
+
.prepare("SELECT * FROM photos WHERE slug = ?")
12
12
+
.bind(params.slug)
13
13
+
.first();
14
14
+
15
15
+
if (!result) {
16
16
+
throw error(404, "Photo not found");
17
17
+
}
18
18
+
19
19
+
const photo: ImageItem = {
20
20
+
slug: result.slug as string,
21
21
+
title: result.title as string,
22
22
+
date: result.date as string,
23
23
+
image: `${R2_BASE_URL}/${result.image_key}`,
24
24
+
thumb: `${R2_BASE_URL}/${result.thumb_key}`,
25
25
+
type: result.type as string,
26
26
+
camera: result.camera as string,
27
27
+
lens: result.lens as string,
28
28
+
aperture: result.aperture as string,
29
29
+
exposure: result.exposure as string,
30
30
+
focalLength: result.focal_length as string,
31
31
+
iso: result.iso as string,
32
32
+
make: result.make as string,
33
33
+
tags: [],
34
34
+
};
35
35
+
36
36
+
return { photo };
37
37
+
};
+28
src/routes/photo/[slug]/+page.svelte
···
1
1
+
<script lang="ts">
2
2
+
import type { PageData } from "./$types";
3
3
+
let { data }: { data: PageData } = $props();
4
4
+
</script>
5
5
+
6
6
+
<div class="bg-[#121113] min-h-screen text-white">
7
7
+
<div class="fixed bg-[#121113] w-full py-4 px-8">
8
8
+
<a href="/" class="text-sm hover:underline">steve.photo</a>
9
9
+
</div>
10
10
+
11
11
+
<div class="flex gap-2 px-8 pt-16">
12
12
+
<div class="flex-2 min-w-0">
13
13
+
<img class="max-w-full h-auto block" src={data.photo.image} alt={data.photo.title} />
14
14
+
</div>
15
15
+
<div class="flex flex-col gap-1 flex-1 min-w-0 p-4">
16
16
+
<h2 class="text-lg">{data.photo.title.toUpperCase()}</h2>
17
17
+
<h3 class="text-sm">{data.photo.make} {data.photo.camera}</h3>
18
18
+
<div class="flex flex-col gap-2 text-neutral-400 font-thin text-xs mt-4">
19
19
+
<p>{data.photo.focalLength}</p>
20
20
+
<p>{data.photo.aperture}</p>
21
21
+
<p>{data.photo.exposure}</p>
22
22
+
<p>ISO {data.photo.iso}</p>
23
23
+
<p>-</p>
24
24
+
<p class="text-neutral-700 text-xs">{new Date(data.photo.date).toLocaleDateString()}</p>
25
25
+
</div>
26
26
+
</div>
27
27
+
</div>
28
28
+
</div>