an independent Bluesky client using Constellation, PDS Queries, and other services reddwarf.app
frontend spa bluesky reddwarf microcosm client app
99
fork

Configure Feed

Select the types of activity you want to include in your feed.

lightbox is now a route

rimar1337 efb287ec 07f352c4

+256 -143
+40 -125
src/components/UniversalPostRenderer.tsx
··· 3 3 import { useAtom } from "jotai"; 4 4 import * as React from "react"; 5 5 import { type SVGProps } from "react"; 6 - import { createPortal } from "react-dom"; 7 6 8 - import { ProfilePostComponent } from "~/routes/profile.$did/post.$rkey"; 9 7 import { likedPostsAtom } from "~/utils/atoms"; 10 8 import { useHydratedEmbed } from "~/utils/useHydrated"; 11 9 import { ··· 35 33 ref?: React.Ref<HTMLDivElement>; 36 34 dataIndexPropPass?: number; 37 35 nopics?: boolean; 36 + lightboxCallback?: (d:LightboxProps) => void; 38 37 } 39 38 40 39 // export async function cachedGetRecord({ ··· 143 142 ref, 144 143 dataIndexPropPass, 145 144 nopics, 145 + lightboxCallback, 146 146 }: UniversalPostRendererATURILoaderProps) { 147 147 // /*mass comment*/ console.log("atUri", atUri); 148 148 //const { get, set } = usePersistentStore(); ··· 421 421 ref={ref} 422 422 dataIndexPropPass={dataIndexPropPass} 423 423 nopics={nopics} 424 + lightboxCallback={lightboxCallback} 424 425 /> 425 426 ); 426 427 } ··· 449 450 ref, 450 451 dataIndexPropPass, 451 452 nopics, 453 + lightboxCallback, 452 454 }: { 453 455 postRecord: any; 454 456 profileRecord: any; ··· 467 469 ref?: React.Ref<HTMLDivElement>; 468 470 dataIndexPropPass?: number; 469 471 nopics?: boolean; 472 + lightboxCallback?: (d:LightboxProps) => void; 470 473 }) { 471 474 // /*mass comment*/ console.log(`received aturi: ${aturi} of post content: ${postRecord}`); 472 475 const navigate = useNavigate(); ··· 665 668 ref={ref} 666 669 dataIndexPropPass={dataIndexPropPass} 667 670 nopics={nopics} 671 + lightboxCallback={lightboxCallback} 668 672 /> 669 673 </> 670 674 ); ··· 983 987 984 988 import defaultpfp from "~/../public/favicon.png"; 985 989 import { useAuth } from "~/providers/UnifiedAuthProvider"; 990 + import type { LightboxProps } from "~/routes/profile.$did/post.$rkey.image.$i"; 986 991 // import type { OutputSchema } from "@atproto/api/dist/client/types/app/bsky/feed/getFeed"; 987 992 // import type { 988 993 // ViewRecord, ··· 1110 1115 ref, 1111 1116 dataIndexPropPass, 1112 1117 nopics, 1118 + lightboxCallback 1113 1119 }: { 1114 1120 post: PostView; 1115 1121 // optional for now because i havent ported every use to this yet ··· 1133 1139 ref?: React.Ref<HTMLDivElement>; 1134 1140 dataIndexPropPass?: number; 1135 1141 nopics?: boolean; 1142 + lightboxCallback?: (d:LightboxProps) => void; 1136 1143 }) { 1137 1144 const parsed = new AtUri(post.uri); 1138 1145 const navigate = useNavigate(); ··· 1514 1521 navigate={navigate} 1515 1522 postid={{ did: post.author.did, rkey: parsed.rkey }} 1516 1523 nopics={nopics} 1524 + lightboxCallback={lightboxCallback} 1517 1525 /> 1518 1526 ) : null} 1519 1527 {post.embed && depth > 0 && ( ··· 1720 1728 navigate, 1721 1729 postid, 1722 1730 nopics, 1731 + lightboxCallback 1723 1732 }: { 1724 1733 embed?: Embed; 1725 1734 moderation?: ModerationDecision; ··· 1730 1739 navigate: (_: any) => void; 1731 1740 postid?: { did: string; rkey: string }; 1732 1741 nopics?: boolean; 1742 + lightboxCallback?: (d:LightboxProps) => void; 1733 1743 }) { 1734 - const [lightboxIndex, setLightboxIndex] = useState<number | null>(null); 1744 + //const [lightboxIndex, setLightboxIndex] = useState<number | null>(null); 1745 + function setLightboxIndex(number:number) { 1746 + navigate({ 1747 + to: "/profile/$did/post/$rkey/image/$i", 1748 + params: { 1749 + did: postid?.did, 1750 + rkey: postid?.rkey, 1751 + i: number.toString(), 1752 + }, 1753 + }); 1754 + } 1735 1755 if ( 1736 1756 AppBskyEmbedRecordWithMedia.isView(embed) && 1737 1757 AppBskyEmbedRecord.isViewRecord(embed.record.record) && ··· 1767 1787 navigate={navigate} 1768 1788 postid={postid} 1769 1789 nopics={nopics} 1790 + lightboxCallback={lightboxCallback} 1770 1791 /> 1771 1792 {/* padding empty div of 8px height */} 1772 1793 <div style={{ height: 12 }} /> ··· 1934 1955 1935 1956 // image embed 1936 1957 // = 1937 - if (AppBskyEmbedImages.isView(embed) && !nopics) { 1958 + if (AppBskyEmbedImages.isView(embed)) { 1938 1959 const { images } = embed; 1939 1960 1940 1961 const lightboxImages = images.map((img) => ({ 1941 1962 src: img.fullsize, 1942 1963 alt: img.alt, 1943 1964 })); 1965 + console.log("rendering images") 1966 + if (lightboxCallback) { 1967 + lightboxCallback({images: lightboxImages}) 1968 + console.log("rendering images") 1969 + }; 1970 + 1971 + if (nopics) return; 1944 1972 1945 1973 if (images.length > 0) { 1946 1974 // const items = embed.images.map(img => ({ ··· 1972 2000 }} 1973 2001 className="border border-gray-200 dark:border-gray-800 was7 bg-gray-200 dark:bg-gray-900" 1974 2002 > 1975 - {lightboxIndex !== null && ( 2003 + {/* {lightboxIndex !== null && ( 1976 2004 <Lightbox 1977 2005 images={lightboxImages} 1978 2006 index={lightboxIndex} ··· 1980 2008 onNavigate={(newIndex) => setLightboxIndex(newIndex)} 1981 2009 post={postid} 1982 2010 /> 1983 - )} 2011 + )} */} 1984 2012 <img 1985 2013 src={image.fullsize} 1986 2014 alt={image.alt} ··· 2013 2041 }} 2014 2042 className="border border-gray-200 dark:border-gray-800 was7" 2015 2043 > 2016 - {lightboxIndex !== null && ( 2044 + {/* {lightboxIndex !== null && ( 2017 2045 <Lightbox 2018 2046 images={lightboxImages} 2019 2047 index={lightboxIndex} ··· 2021 2049 onNavigate={(newIndex) => setLightboxIndex(newIndex)} 2022 2050 post={postid} 2023 2051 /> 2024 - )} 2052 + )} */} 2025 2053 {images.map((img, i) => ( 2026 2054 <div 2027 2055 key={i} ··· 2063 2091 }} 2064 2092 className="border border-gray-200 dark:border-gray-800 was7" 2065 2093 > 2066 - {lightboxIndex !== null && ( 2094 + {/* {lightboxIndex !== null && ( 2067 2095 <Lightbox 2068 2096 images={lightboxImages} 2069 2097 index={lightboxIndex} ··· 2071 2099 onNavigate={(newIndex) => setLightboxIndex(newIndex)} 2072 2100 post={postid} 2073 2101 /> 2074 - )} 2102 + )} */} 2075 2103 {/* Left: 1:1 */} 2076 2104 <div 2077 2105 style={{ flex: 1, aspectRatio: "1 / 1", position: "relative" }} ··· 2148 2176 }} 2149 2177 className="border border-gray-200 dark:border-gray-800 was7" 2150 2178 > 2151 - {lightboxIndex !== null && ( 2179 + {/* {lightboxIndex !== null && ( 2152 2180 <Lightbox 2153 2181 images={lightboxImages} 2154 2182 index={lightboxIndex} ··· 2156 2184 onNavigate={(newIndex) => setLightboxIndex(newIndex)} 2157 2185 post={postid} 2158 2186 /> 2159 - )} 2187 + )} */} 2160 2188 {images.map((img, i) => ( 2161 2189 <div 2162 2190 key={i} ··· 2245 2273 } 2246 2274 2247 2275 return <div />; 2248 - } 2249 - 2250 - type LightboxProps = { 2251 - images: { src: string; alt?: string }[]; 2252 - index: number; 2253 - onClose: () => void; 2254 - onNavigate?: (newIndex: number) => void; 2255 - post?: { did: string; rkey: string }; 2256 - }; 2257 - export function Lightbox({ 2258 - images, 2259 - index, 2260 - onClose, 2261 - onNavigate, 2262 - post, 2263 - }: LightboxProps) { 2264 - const image = images[index]; 2265 - 2266 - useEffect(() => { 2267 - function handleKey(e: KeyboardEvent) { 2268 - if (e.key === "Escape") onClose(); 2269 - if (e.key === "ArrowRight" && onNavigate) 2270 - onNavigate((index + 1) % images.length); 2271 - if (e.key === "ArrowLeft" && onNavigate) 2272 - onNavigate((index - 1 + images.length) % images.length); 2273 - } 2274 - window.addEventListener("keydown", handleKey); 2275 - return () => window.removeEventListener("keydown", handleKey); 2276 - }, [index, images.length, onClose, onNavigate]); 2277 - 2278 - return createPortal( 2279 - <> 2280 - {post && ( 2281 - <div 2282 - onClick={(e) => { 2283 - e.stopPropagation(); 2284 - e.nativeEvent.stopImmediatePropagation(); 2285 - }} 2286 - className="lightbox-sidebar overscroll-none disablegutter border-l dark:border-gray-800 was7 border-gray-300 fixed z-50 flex top-0 right-0 flex-col max-w-[350px] min-w-[350px] max-h-screen overflow-y-scroll dark:bg-gray-950 bg-white" 2287 - > 2288 - <ProfilePostComponent 2289 - did={post.did} 2290 - rkey={post.rkey} 2291 - nopics={onClose} 2292 - /> 2293 - </div> 2294 - )} 2295 - <div 2296 - 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)]" 2297 - onClick={(e) => { 2298 - e.stopPropagation(); 2299 - onClose(); 2300 - }} 2301 - > 2302 - <img 2303 - src={image.src} 2304 - alt={image.alt} 2305 - className="max-h-[90%] max-w-[90%] object-contain rounded-lg shadow-lg" 2306 - onClick={(e) => e.stopPropagation()} 2307 - /> 2308 - 2309 - {images.length > 1 && ( 2310 - <> 2311 - <button 2312 - onClick={(e) => { 2313 - e.stopPropagation(); 2314 - onNavigate?.((index - 1 + images.length) % images.length); 2315 - }} 2316 - 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" 2317 - > 2318 - <svg 2319 - xmlns="http://www.w3.org/2000/svg" 2320 - width={28} 2321 - height={28} 2322 - viewBox="0 0 24 24" 2323 - > 2324 - <g fill="none" fillRule="evenodd"> 2325 - <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> 2326 - <path 2327 - fill="currentColor" 2328 - 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" 2329 - ></path> 2330 - </g> 2331 - </svg> 2332 - </button> 2333 - <button 2334 - onClick={(e) => { 2335 - e.stopPropagation(); 2336 - onNavigate?.((index + 1) % images.length); 2337 - }} 2338 - 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" 2339 - > 2340 - <svg 2341 - xmlns="http://www.w3.org/2000/svg" 2342 - width={28} 2343 - height={28} 2344 - viewBox="0 0 24 24" 2345 - > 2346 - <g fill="none" fillRule="evenodd"> 2347 - <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> 2348 - <path 2349 - fill="currentColor" 2350 - 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" 2351 - ></path> 2352 - </g> 2353 - </svg> 2354 - </button> 2355 - </> 2356 - )} 2357 - </div> 2358 - </>, 2359 - document.body 2360 - ); 2361 2276 } 2362 2277 2363 2278 function getDomain(url: string) {
+36 -5
src/routeTree.gen.ts
··· 21 21 import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' 22 22 import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' 23 23 import { Route as ProfileDidPostRkeyRouteImport } from './routes/profile.$did/post.$rkey' 24 + import { Route as ProfileDidPostRkeyImageIRouteImport } from './routes/profile.$did/post.$rkey.image.$i' 24 25 25 26 const SettingsRoute = SettingsRouteImport.update({ 26 27 id: '/settings', ··· 83 84 path: '/profile/$did/post/$rkey', 84 85 getParentRoute: () => rootRouteImport, 85 86 } as any) 87 + const ProfileDidPostRkeyImageIRoute = 88 + ProfileDidPostRkeyImageIRouteImport.update({ 89 + id: '/image/$i', 90 + path: '/image/$i', 91 + getParentRoute: () => ProfileDidPostRkeyRoute, 92 + } as any) 86 93 87 94 export interface FileRoutesByFullPath { 88 95 '/': typeof IndexRoute ··· 94 101 '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute 95 102 '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute 96 103 '/profile/$did': typeof ProfileDidIndexRoute 97 - '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRoute 104 + '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren 105 + '/profile/$did/post/$rkey/image/$i': typeof ProfileDidPostRkeyImageIRoute 98 106 } 99 107 export interface FileRoutesByTo { 100 108 '/': typeof IndexRoute ··· 106 114 '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute 107 115 '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute 108 116 '/profile/$did': typeof ProfileDidIndexRoute 109 - '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRoute 117 + '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren 118 + '/profile/$did/post/$rkey/image/$i': typeof ProfileDidPostRkeyImageIRoute 110 119 } 111 120 export interface FileRoutesById { 112 121 __root__: typeof rootRouteImport ··· 121 130 '/_pathlessLayout/_nested-layout/route-a': typeof PathlessLayoutNestedLayoutRouteARoute 122 131 '/_pathlessLayout/_nested-layout/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute 123 132 '/profile/$did/': typeof ProfileDidIndexRoute 124 - '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRoute 133 + '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren 134 + '/profile/$did/post/$rkey/image/$i': typeof ProfileDidPostRkeyImageIRoute 125 135 } 126 136 export interface FileRouteTypes { 127 137 fileRoutesByFullPath: FileRoutesByFullPath ··· 136 146 | '/route-b' 137 147 | '/profile/$did' 138 148 | '/profile/$did/post/$rkey' 149 + | '/profile/$did/post/$rkey/image/$i' 139 150 fileRoutesByTo: FileRoutesByTo 140 151 to: 141 152 | '/' ··· 148 159 | '/route-b' 149 160 | '/profile/$did' 150 161 | '/profile/$did/post/$rkey' 162 + | '/profile/$did/post/$rkey/image/$i' 151 163 id: 152 164 | '__root__' 153 165 | '/' ··· 162 174 | '/_pathlessLayout/_nested-layout/route-b' 163 175 | '/profile/$did/' 164 176 | '/profile/$did/post/$rkey' 177 + | '/profile/$did/post/$rkey/image/$i' 165 178 fileRoutesById: FileRoutesById 166 179 } 167 180 export interface RootRouteChildren { ··· 173 186 SettingsRoute: typeof SettingsRoute 174 187 CallbackIndexRoute: typeof CallbackIndexRoute 175 188 ProfileDidIndexRoute: typeof ProfileDidIndexRoute 176 - ProfileDidPostRkeyRoute: typeof ProfileDidPostRkeyRoute 189 + ProfileDidPostRkeyRoute: typeof ProfileDidPostRkeyRouteWithChildren 177 190 } 178 191 179 192 declare module '@tanstack/react-router' { ··· 262 275 preLoaderRoute: typeof ProfileDidPostRkeyRouteImport 263 276 parentRoute: typeof rootRouteImport 264 277 } 278 + '/profile/$did/post/$rkey/image/$i': { 279 + id: '/profile/$did/post/$rkey/image/$i' 280 + path: '/image/$i' 281 + fullPath: '/profile/$did/post/$rkey/image/$i' 282 + preLoaderRoute: typeof ProfileDidPostRkeyImageIRouteImport 283 + parentRoute: typeof ProfileDidPostRkeyRoute 284 + } 265 285 } 266 286 } 267 287 ··· 295 315 PathlessLayoutRouteChildren, 296 316 ) 297 317 318 + interface ProfileDidPostRkeyRouteChildren { 319 + ProfileDidPostRkeyImageIRoute: typeof ProfileDidPostRkeyImageIRoute 320 + } 321 + 322 + const ProfileDidPostRkeyRouteChildren: ProfileDidPostRkeyRouteChildren = { 323 + ProfileDidPostRkeyImageIRoute: ProfileDidPostRkeyImageIRoute, 324 + } 325 + 326 + const ProfileDidPostRkeyRouteWithChildren = 327 + ProfileDidPostRkeyRoute._addFileChildren(ProfileDidPostRkeyRouteChildren) 328 + 298 329 const rootRouteChildren: RootRouteChildren = { 299 330 IndexRoute: IndexRoute, 300 331 PathlessLayoutRoute: PathlessLayoutRouteWithChildren, ··· 304 335 SettingsRoute: SettingsRoute, 305 336 CallbackIndexRoute: CallbackIndexRoute, 306 337 ProfileDidIndexRoute: ProfileDidIndexRoute, 307 - ProfileDidPostRkeyRoute: ProfileDidPostRkeyRoute, 338 + ProfileDidPostRkeyRoute: ProfileDidPostRkeyRouteWithChildren, 308 339 } 309 340 export const routeTree = rootRouteImport 310 341 ._addFileChildren(rootRouteChildren)
+165
src/routes/profile.$did/post.$rkey.image.$i.tsx
··· 1 + import { 2 + createFileRoute, 3 + useNavigate, 4 + type UseNavigateResult, 5 + } from "@tanstack/react-router"; 6 + import { useEffect, useState } from "react"; 7 + import { createPortal } from "react-dom"; 8 + 9 + import { ProfilePostComponent } from "./post.$rkey"; 10 + 11 + export const Route = createFileRoute("/profile/$did/post/$rkey/image/$i")({ 12 + component: Lightbox, 13 + }); 14 + 15 + export type LightboxProps = { 16 + images: { src: string; alt?: string }[]; 17 + }; 18 + 19 + function 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 + 50 + export 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 border-l dark:border-gray-800 was7 border-gray-300 fixed z-50 flex 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 + }
+15 -13
src/routes/profile.$did/post.$rkey.tsx
··· 1 1 import { useQueryClient } from "@tanstack/react-query"; 2 - import { createFileRoute } from "@tanstack/react-router"; 2 + import { createFileRoute, Outlet } from "@tanstack/react-router"; 3 3 import React, { useLayoutEffect } from "react"; 4 4 5 5 import { Header } from "~/components/Header"; ··· 11 11 useQueryIdentity, 12 12 useQueryPost, 13 13 } from "~/utils/useQuery"; 14 + 15 + import type { LightboxProps } from "./post.$rkey.image.$i"; 14 16 15 17 //const HANDLE_DID_CACHE_TIMEOUT = 60 * 60 * 1000; // 1 hour 16 18 ··· 37 39 did, 38 40 rkey, 39 41 nopics, 42 + lightboxCallback 40 43 }: { 41 44 did: string; 42 45 rkey: string; 43 - nopics?: () => void; 46 + nopics?: boolean; 47 + lightboxCallback?: (d:LightboxProps) => void; 44 48 }) { 45 49 //const { get, set } = usePersistentStore(); 46 50 const queryClient = useQueryClient(); ··· 297 301 298 302 return ( 299 303 <> 304 + <Outlet /> 300 305 <Header 301 306 title={`Post`} 302 - backButtonCallback={ 303 - nopics 304 - ? nopics 305 - : () => { 306 - if (window.history.length > 1) { 307 - window.history.back(); 308 - } else { 309 - window.location.assign("/"); 310 - } 311 - } 312 - } 307 + backButtonCallback={() => { 308 + if (window.history.length > 1) { 309 + window.history.back(); 310 + } else { 311 + window.location.assign("/"); 312 + } 313 + }} 313 314 /> 314 315 315 316 {parentsLoading && ( ··· 342 343 detailed={true} 343 344 topReplyLine={parentsLoading || parents.length > 0} 344 345 nopics={!!nopics} 346 + lightboxCallback={lightboxCallback} 345 347 /> 346 348 </div> 347 349 <div