+40
-125
src/components/UniversalPostRenderer.tsx
+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
+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
+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
+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