grain.social is a photo sharing platform built on atproto.
at main 106 lines 3.2 kB view raw
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}