grain.social is a photo sharing platform built on atproto.
1import { ProfileView } from "$lexicon/types/social/grain/actor/defs.ts";
2import { GalleryView } from "$lexicon/types/social/grain/gallery/defs.ts";
3import { PhotoView } from "$lexicon/types/social/grain/photo/defs.ts";
4import { AtUri } from "@atproto/syntax";
5import { cn } from "@bigmoves/bff/components";
6import { ReplyButton } from "../modules/comments.tsx";
7import { photoDialogLink } from "../utils.ts";
8import { Dialog } from "./Dialog.tsx";
9
10export function PhotoDialog({
11 userProfile,
12 gallery,
13 image,
14 nextImage,
15 prevImage,
16}: Readonly<{
17 userProfile?: ProfileView;
18 gallery: GalleryView;
19 image: PhotoView;
20 nextImage?: PhotoView;
21 prevImage?: PhotoView;
22}>) {
23 return (
24 <Dialog id="photo-dialog" class="bg-zinc-950">
25 <Dialog.X />
26 {nextImage
27 ? (
28 <div
29 hx-get={photoDialogLink(gallery, nextImage)}
30 hx-trigger="keyup[key=='ArrowRight'] from:body, swipeleft from:body"
31 hx-target="#photo-dialog"
32 hx-swap="innerHTML"
33 />
34 )
35 : null}
36 {prevImage
37 ? (
38 <div
39 hx-get={photoDialogLink(gallery, prevImage)}
40 hx-trigger="keyup[key=='ArrowLeft'] from:body, swiperight from:body"
41 hx-target="#photo-dialog"
42 hx-swap="innerHTML"
43 />
44 )
45 : null}
46 <div
47 class="flex flex-col w-5xl h-[calc(100vh-100px)] sm:h-screen z-20 relative sm:static"
48 _={Dialog._closeOnClick}
49 >
50 <div class="flex flex-col p-4 z-20 flex-1 relative">
51 <img
52 src={image.fullsize}
53 alt={image.alt}
54 class="absolute inset-0 w-full h-full object-contain"
55 />
56 </div>
57 {image.alt
58 ? (
59 <div class="px-4 sm:px-0 py-4 bg-black text-white text-left flex">
60 <span class="flex-1 mr-2">{image.alt}</span>
61 </div>
62 )
63 : null}
64 {(userProfile || image.exif)
65 ? (
66 <div class="flex w-full gap-2 p-2 sm:px-0 sm:py-2">
67 {userProfile
68 ? (
69 <ReplyButton
70 class="flex-1 bg-zinc-800 sm:bg-transparent sm:hover:bg-zinc-800 text-zinc-50"
71 gallery={gallery}
72 photo={image}
73 userProfile={userProfile}
74 />
75 )
76 : <div class="flex-1" />}
77 {image.exif ? <ExifButton photo={image} /> : null}
78 </div>
79 )
80 : null}
81 </div>
82 </Dialog>
83 );
84}
85
86function ExifButton(
87 { photo, class: classProp }: Readonly<{ photo: PhotoView; class?: string }>,
88) {
89 const atUri = new AtUri(photo.uri);
90 const did = atUri.hostname;
91 const rkey = atUri.rkey;
92 return (
93 <button
94 type="button"
95 class={cn("text-zinc-50 p-2 cursor-pointer", classProp)}
96 hx-get={`/dialogs/photo/${did}/${rkey}/exif-overlay`}
97 hx-trigger="click"
98 hx-target="#layout"
99 hx-swap="afterbegin"
100 _="on click halt"
101 >
102 <i class="fa fa-camera" />
103 <span class="sr-only">Show EXIF</span>
104 </button>
105 );
106}