grain.social is a photo sharing platform built on atproto.
grain.social
atproto
photography
appview
1import { GalleryView } from "$lexicon/types/social/grain/gallery/defs.ts";
2import { AtUri } from "@atproto/syntax";
3import { Button } from "./Button.tsx";
4import { Dialog } from "./Dialog.tsx";
5import { Input } from "./Input.tsx";
6
7export function GallerySelectDialog(
8 { photoUri, userDid, galleries }: Readonly<
9 {
10 photoUri?: string;
11 userDid: string;
12 galleries: GalleryView[];
13 }
14 >,
15) {
16 return (
17 <Dialog id="gallery-select-dialog">
18 <Dialog.Content class="min-h-[calc(100vh-100px)] overflow-hidden flex flex-col gap-4">
19 <Dialog.X class="fill-zinc-950 dark:fill-zinc-50" />
20 <Dialog.Title>
21 {photoUri ? "Add to gallery" : "Select gallery"}
22 </Dialog.Title>
23
24 <form>
25 <Input
26 type="text"
27 name="q"
28 placeholder="Enter gallery name or select from the list"
29 hx-get={`/dialogs/gallery/${userDid}/select`}
30 hx-target="#search-results"
31 hx-trigger="input changed delay:500ms, keyup[key=='Enter']"
32 hx-swap="innerHTML"
33 autoFocus
34 />
35 </form>
36
37 <div id="search-results" class="flex-1 overflow-y-auto">
38 <GallerySelectDialogSearchResults
39 photoUri={photoUri}
40 galleries={galleries}
41 />
42 </div>
43 <Dialog.Close variant="secondary">
44 Close
45 </Dialog.Close>
46 </Dialog.Content>
47 </Dialog>
48 );
49}
50
51export function GallerySelectDialogSearchResults(
52 { photoUri, galleries }: {
53 photoUri?: string;
54 galleries: GalleryView[];
55 },
56) {
57 return (
58 galleries.length > 0
59 ? (
60 <ul class="divide-zinc-200 dark:divide-zinc-800 divide-y">
61 {galleries.map((gallery) => (
62 <li
63 key={gallery.cid}
64 class="w-full hover:bg-zinc-200 dark:hover:bg-zinc-800"
65 >
66 {photoUri
67 ? (
68 <button
69 type="button"
70 hx-put={addToGalleryActionLink(photoUri, gallery.uri)}
71 hx-swap="none"
72 class="block text-left w-full px-2 py-4"
73 >
74 {gallery.title}
75 <div class="text-sm text-zinc-600 dark:text-zinc-500">
76 {gallery.description}
77 </div>
78 </button>
79 )
80 : (
81 <a
82 href={uploadPageLink(gallery.uri)}
83 class="block w-full px-2 py-4"
84 >
85 {gallery.title}
86 <div class="text-sm text-zinc-600 dark:text-zinc-500">
87 {gallery.description}
88 </div>
89 </a>
90 )}
91 </li>
92 ))}
93 </ul>
94 )
95 : <p>No galleries found.</p>
96 );
97}
98
99function addToGalleryActionLink(photoUri: string, galleryUri: string) {
100 const photoRKey = new AtUri(photoUri).rkey;
101 const galleryRkey = new AtUri(galleryUri).rkey;
102 return `/actions/gallery/${galleryRkey}/add-photo/${photoRKey}?page=upload`;
103}
104
105function uploadPageLink(galleryUri: string) {
106 const rkey = new AtUri(galleryUri).rkey;
107 return `/upload?gallery=${rkey}`;
108}
109
110export function GallerySelectDialogButton(
111 { userDid }: Readonly<{ userDid: string }>,
112) {
113 return (
114 <Button
115 type="button"
116 variant="secondary"
117 class="w-full sm:w-fit"
118 hx-get={`/dialogs/gallery/${userDid}/select`}
119 hx-trigger="click"
120 hx-target="#layout"
121 hx-swap="afterbegin"
122 _="on click halt"
123 >
124 <i class="fa fa-filter mr-2" />
125 Filter by gallery
126 </Button>
127 );
128}