an independent Bluesky client using Constellation, PDS Queries, and other services
reddwarf.app
frontend
spa
bluesky
reddwarf
microcosm
client
app
1import {
2 createFileRoute,
3 useNavigate,
4 type UseNavigateResult,
5} from "@tanstack/react-router";
6import { useEffect, useState } from "react";
7import { createPortal } from "react-dom";
8
9import { ProfilePostComponent } from "./post.$rkey";
10
11export const Route = createFileRoute("/profile/$did/post/$rkey/image/$i")({
12 component: Lightbox,
13});
14
15export type LightboxProps = {
16 images: { src: string; alt?: string }[];
17};
18
19function nextprev({
20 index,
21 images,
22 navigate,
23 did,
24 rkey,
25 prev,
26}: {
27 index?: number;
28 images?: LightboxProps["images"];
29 navigate: UseNavigateResult<string>;
30 did: string;
31 rkey: string;
32 prev?: boolean;
33}) {
34 const len = images?.length ?? 0;
35 if (len === 0) return;
36
37 const nextIndex = ((index ?? 0) + (prev ? -1 : 1) + len) % len;
38
39 navigate({
40 to: "/profile/$did/post/$rkey/image/$i",
41 params: {
42 did,
43 rkey,
44 i: nextIndex.toString(),
45 },
46 replace: true,
47 });
48}
49
50export function Lightbox() {
51 console.log("hey the $i route is loaded w!!!");
52 const { did, rkey, i } = Route.useParams();
53 const [images, setImages] = useState<LightboxProps["images"] | undefined>(
54 undefined
55 );
56 const index = Number(i);
57 const navigate = useNavigate();
58 const post = true;
59 const image = images?.[index] ?? undefined;
60
61 function lightboxCallback(d: LightboxProps) {
62 console.log("callback actually called!");
63 setImages(d.images);
64 }
65
66 useEffect(() => {
67 function handleKey(e: KeyboardEvent) {
68 if (e.key === "Escape") window.history.back();
69 if (e.key === "ArrowRight")
70 nextprev({ index, images, navigate, did, rkey });
71 //onNavigate((index + 1) % images.length);
72 if (e.key === "ArrowLeft")
73 nextprev({ index, images, navigate, did, rkey, prev: true });
74 //onNavigate((index - 1 + images.length) % images.length);
75 }
76 window.addEventListener("keydown", handleKey);
77 return () => window.removeEventListener("keydown", handleKey);
78 }, [index, navigate, did, rkey, images]);
79
80 return createPortal(
81 <>
82 {post && (
83 <div
84 onClick={(e) => {
85 e.stopPropagation();
86 e.nativeEvent.stopImmediatePropagation();
87 }}
88 className="lightbox-sidebar hidden lg:flex overscroll-none disablegutter disablescroll border-l dark:border-gray-800 was7 border-gray-300 fixed z-50 top-0 right-0 flex-col max-w-[350px] min-w-[350px] max-h-screen overflow-y-scroll dark:bg-gray-950 bg-white"
89 >
90 <ProfilePostComponent
91 key={`/profile/${did}/post/${rkey}`}
92 did={did}
93 rkey={rkey}
94 nopics
95 lightboxCallback={lightboxCallback}
96 />
97 </div>
98 )}
99 <div
100 className="lightbox fixed inset-0 z-50 flex items-center justify-center bg-black/80 w-screen lg:w-[calc(100vw-350px)] lg:max-w-[calc(100vw-350px)]"
101 onClick={(e) => {
102 e.stopPropagation();
103 window.history.back();
104 }}
105 >
106 <img
107 src={image?.src}
108 alt={image?.alt}
109 className="max-h-[90%] max-w-[90%] object-contain rounded-lg shadow-lg"
110 onClick={(e) => e.stopPropagation()}
111 />
112
113 {(images?.length ?? 0) > 1 && (
114 <>
115 <button
116 onClick={(e) => {
117 e.stopPropagation();
118 nextprev({ index, images, navigate, did, rkey, prev: true });
119 }}
120 className="absolute left-4 top-1/2 -translate-y-1/2 text-white text-4xl h-8 w-8 rounded-full bg-gray-900 flex items-center justify-center"
121 >
122 <svg
123 xmlns="http://www.w3.org/2000/svg"
124 width={28}
125 height={28}
126 viewBox="0 0 24 24"
127 >
128 <g fill="none" fillRule="evenodd">
129 <path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"></path>
130 <path
131 fill="currentColor"
132 d="M8.293 12.707a1 1 0 0 1 0-1.414l5.657-5.657a1 1 0 1 1 1.414 1.414L10.414 12l4.95 4.95a1 1 0 0 1-1.414 1.414z"
133 ></path>
134 </g>
135 </svg>
136 </button>
137 <button
138 onClick={(e) => {
139 e.stopPropagation();
140 nextprev({ index, images, navigate, did, rkey });
141 }}
142 className="absolute right-4 top-1/2 -translate-y-1/2 text-white text-4xl h-8 w-8 rounded-full bg-gray-900 flex items-center justify-center"
143 >
144 <svg
145 xmlns="http://www.w3.org/2000/svg"
146 width={28}
147 height={28}
148 viewBox="0 0 24 24"
149 >
150 <g fill="none" fillRule="evenodd">
151 <path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"></path>
152 <path
153 fill="currentColor"
154 d="M15.707 11.293a1 1 0 0 1 0 1.414l-5.657 5.657a1 1 0 1 1-1.414-1.414l4.95-4.95l-4.95-4.95a1 1 0 0 1 1.414-1.414z"
155 ></path>
156 </g>
157 </svg>
158 </button>
159 </>
160 )}
161 </div>
162 </>,
163 document.body
164 );
165}