+40
-125
src/components/UniversalPostRenderer.tsx
+40
-125
src/components/UniversalPostRenderer.tsx
···
3
import { useAtom } from "jotai";
4
import * as React from "react";
5
import { type SVGProps } from "react";
6
-
import { createPortal } from "react-dom";
7
8
-
import { ProfilePostComponent } from "~/routes/profile.$did/post.$rkey";
9
import { likedPostsAtom } from "~/utils/atoms";
10
import { useHydratedEmbed } from "~/utils/useHydrated";
11
import {
···
35
ref?: React.Ref<HTMLDivElement>;
36
dataIndexPropPass?: number;
37
nopics?: boolean;
38
}
39
40
// export async function cachedGetRecord({
···
143
ref,
144
dataIndexPropPass,
145
nopics,
146
}: UniversalPostRendererATURILoaderProps) {
147
// /*mass comment*/ console.log("atUri", atUri);
148
//const { get, set } = usePersistentStore();
···
421
ref={ref}
422
dataIndexPropPass={dataIndexPropPass}
423
nopics={nopics}
424
/>
425
);
426
}
···
449
ref,
450
dataIndexPropPass,
451
nopics,
452
}: {
453
postRecord: any;
454
profileRecord: any;
···
467
ref?: React.Ref<HTMLDivElement>;
468
dataIndexPropPass?: number;
469
nopics?: boolean;
470
}) {
471
// /*mass comment*/ console.log(`received aturi: ${aturi} of post content: ${postRecord}`);
472
const navigate = useNavigate();
···
665
ref={ref}
666
dataIndexPropPass={dataIndexPropPass}
667
nopics={nopics}
668
/>
669
</>
670
);
···
983
984
import defaultpfp from "~/../public/favicon.png";
985
import { useAuth } from "~/providers/UnifiedAuthProvider";
986
// import type { OutputSchema } from "@atproto/api/dist/client/types/app/bsky/feed/getFeed";
987
// import type {
988
// ViewRecord,
···
1110
ref,
1111
dataIndexPropPass,
1112
nopics,
1113
}: {
1114
post: PostView;
1115
// optional for now because i havent ported every use to this yet
···
1133
ref?: React.Ref<HTMLDivElement>;
1134
dataIndexPropPass?: number;
1135
nopics?: boolean;
1136
}) {
1137
const parsed = new AtUri(post.uri);
1138
const navigate = useNavigate();
···
1514
navigate={navigate}
1515
postid={{ did: post.author.did, rkey: parsed.rkey }}
1516
nopics={nopics}
1517
/>
1518
) : null}
1519
{post.embed && depth > 0 && (
···
1720
navigate,
1721
postid,
1722
nopics,
1723
}: {
1724
embed?: Embed;
1725
moderation?: ModerationDecision;
···
1730
navigate: (_: any) => void;
1731
postid?: { did: string; rkey: string };
1732
nopics?: boolean;
1733
}) {
1734
-
const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);
1735
if (
1736
AppBskyEmbedRecordWithMedia.isView(embed) &&
1737
AppBskyEmbedRecord.isViewRecord(embed.record.record) &&
···
1767
navigate={navigate}
1768
postid={postid}
1769
nopics={nopics}
1770
/>
1771
{/* padding empty div of 8px height */}
1772
<div style={{ height: 12 }} />
···
1934
1935
// image embed
1936
// =
1937
-
if (AppBskyEmbedImages.isView(embed) && !nopics) {
1938
const { images } = embed;
1939
1940
const lightboxImages = images.map((img) => ({
1941
src: img.fullsize,
1942
alt: img.alt,
1943
}));
1944
1945
if (images.length > 0) {
1946
// const items = embed.images.map(img => ({
···
1972
}}
1973
className="border border-gray-200 dark:border-gray-800 was7 bg-gray-200 dark:bg-gray-900"
1974
>
1975
-
{lightboxIndex !== null && (
1976
<Lightbox
1977
images={lightboxImages}
1978
index={lightboxIndex}
···
1980
onNavigate={(newIndex) => setLightboxIndex(newIndex)}
1981
post={postid}
1982
/>
1983
-
)}
1984
<img
1985
src={image.fullsize}
1986
alt={image.alt}
···
2013
}}
2014
className="border border-gray-200 dark:border-gray-800 was7"
2015
>
2016
-
{lightboxIndex !== null && (
2017
<Lightbox
2018
images={lightboxImages}
2019
index={lightboxIndex}
···
2021
onNavigate={(newIndex) => setLightboxIndex(newIndex)}
2022
post={postid}
2023
/>
2024
-
)}
2025
{images.map((img, i) => (
2026
<div
2027
key={i}
···
2063
}}
2064
className="border border-gray-200 dark:border-gray-800 was7"
2065
>
2066
-
{lightboxIndex !== null && (
2067
<Lightbox
2068
images={lightboxImages}
2069
index={lightboxIndex}
···
2071
onNavigate={(newIndex) => setLightboxIndex(newIndex)}
2072
post={postid}
2073
/>
2074
-
)}
2075
{/* Left: 1:1 */}
2076
<div
2077
style={{ flex: 1, aspectRatio: "1 / 1", position: "relative" }}
···
2148
}}
2149
className="border border-gray-200 dark:border-gray-800 was7"
2150
>
2151
-
{lightboxIndex !== null && (
2152
<Lightbox
2153
images={lightboxImages}
2154
index={lightboxIndex}
···
2156
onNavigate={(newIndex) => setLightboxIndex(newIndex)}
2157
post={postid}
2158
/>
2159
-
)}
2160
{images.map((img, i) => (
2161
<div
2162
key={i}
···
2245
}
2246
2247
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
}
2362
2363
function getDomain(url: string) {
···
3
import { useAtom } from "jotai";
4
import * as React from "react";
5
import { type SVGProps } from "react";
6
7
import { likedPostsAtom } from "~/utils/atoms";
8
import { useHydratedEmbed } from "~/utils/useHydrated";
9
import {
···
33
ref?: React.Ref<HTMLDivElement>;
34
dataIndexPropPass?: number;
35
nopics?: boolean;
36
+
lightboxCallback?: (d:LightboxProps) => void;
37
}
38
39
// export async function cachedGetRecord({
···
142
ref,
143
dataIndexPropPass,
144
nopics,
145
+
lightboxCallback,
146
}: UniversalPostRendererATURILoaderProps) {
147
// /*mass comment*/ console.log("atUri", atUri);
148
//const { get, set } = usePersistentStore();
···
421
ref={ref}
422
dataIndexPropPass={dataIndexPropPass}
423
nopics={nopics}
424
+
lightboxCallback={lightboxCallback}
425
/>
426
);
427
}
···
450
ref,
451
dataIndexPropPass,
452
nopics,
453
+
lightboxCallback,
454
}: {
455
postRecord: any;
456
profileRecord: any;
···
469
ref?: React.Ref<HTMLDivElement>;
470
dataIndexPropPass?: number;
471
nopics?: boolean;
472
+
lightboxCallback?: (d:LightboxProps) => void;
473
}) {
474
// /*mass comment*/ console.log(`received aturi: ${aturi} of post content: ${postRecord}`);
475
const navigate = useNavigate();
···
668
ref={ref}
669
dataIndexPropPass={dataIndexPropPass}
670
nopics={nopics}
671
+
lightboxCallback={lightboxCallback}
672
/>
673
</>
674
);
···
987
988
import defaultpfp from "~/../public/favicon.png";
989
import { useAuth } from "~/providers/UnifiedAuthProvider";
990
+
import type { LightboxProps } from "~/routes/profile.$did/post.$rkey.image.$i";
991
// import type { OutputSchema } from "@atproto/api/dist/client/types/app/bsky/feed/getFeed";
992
// import type {
993
// ViewRecord,
···
1115
ref,
1116
dataIndexPropPass,
1117
nopics,
1118
+
lightboxCallback
1119
}: {
1120
post: PostView;
1121
// optional for now because i havent ported every use to this yet
···
1139
ref?: React.Ref<HTMLDivElement>;
1140
dataIndexPropPass?: number;
1141
nopics?: boolean;
1142
+
lightboxCallback?: (d:LightboxProps) => void;
1143
}) {
1144
const parsed = new AtUri(post.uri);
1145
const navigate = useNavigate();
···
1521
navigate={navigate}
1522
postid={{ did: post.author.did, rkey: parsed.rkey }}
1523
nopics={nopics}
1524
+
lightboxCallback={lightboxCallback}
1525
/>
1526
) : null}
1527
{post.embed && depth > 0 && (
···
1728
navigate,
1729
postid,
1730
nopics,
1731
+
lightboxCallback
1732
}: {
1733
embed?: Embed;
1734
moderation?: ModerationDecision;
···
1739
navigate: (_: any) => void;
1740
postid?: { did: string; rkey: string };
1741
nopics?: boolean;
1742
+
lightboxCallback?: (d:LightboxProps) => void;
1743
}) {
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
+
}
1755
if (
1756
AppBskyEmbedRecordWithMedia.isView(embed) &&
1757
AppBskyEmbedRecord.isViewRecord(embed.record.record) &&
···
1787
navigate={navigate}
1788
postid={postid}
1789
nopics={nopics}
1790
+
lightboxCallback={lightboxCallback}
1791
/>
1792
{/* padding empty div of 8px height */}
1793
<div style={{ height: 12 }} />
···
1955
1956
// image embed
1957
// =
1958
+
if (AppBskyEmbedImages.isView(embed)) {
1959
const { images } = embed;
1960
1961
const lightboxImages = images.map((img) => ({
1962
src: img.fullsize,
1963
alt: img.alt,
1964
}));
1965
+
console.log("rendering images")
1966
+
if (lightboxCallback) {
1967
+
lightboxCallback({images: lightboxImages})
1968
+
console.log("rendering images")
1969
+
};
1970
+
1971
+
if (nopics) return;
1972
1973
if (images.length > 0) {
1974
// const items = embed.images.map(img => ({
···
2000
}}
2001
className="border border-gray-200 dark:border-gray-800 was7 bg-gray-200 dark:bg-gray-900"
2002
>
2003
+
{/* {lightboxIndex !== null && (
2004
<Lightbox
2005
images={lightboxImages}
2006
index={lightboxIndex}
···
2008
onNavigate={(newIndex) => setLightboxIndex(newIndex)}
2009
post={postid}
2010
/>
2011
+
)} */}
2012
<img
2013
src={image.fullsize}
2014
alt={image.alt}
···
2041
}}
2042
className="border border-gray-200 dark:border-gray-800 was7"
2043
>
2044
+
{/* {lightboxIndex !== null && (
2045
<Lightbox
2046
images={lightboxImages}
2047
index={lightboxIndex}
···
2049
onNavigate={(newIndex) => setLightboxIndex(newIndex)}
2050
post={postid}
2051
/>
2052
+
)} */}
2053
{images.map((img, i) => (
2054
<div
2055
key={i}
···
2091
}}
2092
className="border border-gray-200 dark:border-gray-800 was7"
2093
>
2094
+
{/* {lightboxIndex !== null && (
2095
<Lightbox
2096
images={lightboxImages}
2097
index={lightboxIndex}
···
2099
onNavigate={(newIndex) => setLightboxIndex(newIndex)}
2100
post={postid}
2101
/>
2102
+
)} */}
2103
{/* Left: 1:1 */}
2104
<div
2105
style={{ flex: 1, aspectRatio: "1 / 1", position: "relative" }}
···
2176
}}
2177
className="border border-gray-200 dark:border-gray-800 was7"
2178
>
2179
+
{/* {lightboxIndex !== null && (
2180
<Lightbox
2181
images={lightboxImages}
2182
index={lightboxIndex}
···
2184
onNavigate={(newIndex) => setLightboxIndex(newIndex)}
2185
post={postid}
2186
/>
2187
+
)} */}
2188
{images.map((img, i) => (
2189
<div
2190
key={i}
···
2273
}
2274
2275
return <div />;
2276
}
2277
2278
function getDomain(url: string) {
+36
-5
src/routeTree.gen.ts
+36
-5
src/routeTree.gen.ts
···
21
import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b'
22
import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a'
23
import { Route as ProfileDidPostRkeyRouteImport } from './routes/profile.$did/post.$rkey'
24
25
const SettingsRoute = SettingsRouteImport.update({
26
id: '/settings',
···
83
path: '/profile/$did/post/$rkey',
84
getParentRoute: () => rootRouteImport,
85
} as any)
86
87
export interface FileRoutesByFullPath {
88
'/': typeof IndexRoute
···
94
'/route-a': typeof PathlessLayoutNestedLayoutRouteARoute
95
'/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
96
'/profile/$did': typeof ProfileDidIndexRoute
97
-
'/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRoute
98
}
99
export interface FileRoutesByTo {
100
'/': typeof IndexRoute
···
106
'/route-a': typeof PathlessLayoutNestedLayoutRouteARoute
107
'/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
108
'/profile/$did': typeof ProfileDidIndexRoute
109
-
'/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRoute
110
}
111
export interface FileRoutesById {
112
__root__: typeof rootRouteImport
···
121
'/_pathlessLayout/_nested-layout/route-a': typeof PathlessLayoutNestedLayoutRouteARoute
122
'/_pathlessLayout/_nested-layout/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
123
'/profile/$did/': typeof ProfileDidIndexRoute
124
-
'/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRoute
125
}
126
export interface FileRouteTypes {
127
fileRoutesByFullPath: FileRoutesByFullPath
···
136
| '/route-b'
137
| '/profile/$did'
138
| '/profile/$did/post/$rkey'
139
fileRoutesByTo: FileRoutesByTo
140
to:
141
| '/'
···
148
| '/route-b'
149
| '/profile/$did'
150
| '/profile/$did/post/$rkey'
151
id:
152
| '__root__'
153
| '/'
···
162
| '/_pathlessLayout/_nested-layout/route-b'
163
| '/profile/$did/'
164
| '/profile/$did/post/$rkey'
165
fileRoutesById: FileRoutesById
166
}
167
export interface RootRouteChildren {
···
173
SettingsRoute: typeof SettingsRoute
174
CallbackIndexRoute: typeof CallbackIndexRoute
175
ProfileDidIndexRoute: typeof ProfileDidIndexRoute
176
-
ProfileDidPostRkeyRoute: typeof ProfileDidPostRkeyRoute
177
}
178
179
declare module '@tanstack/react-router' {
···
262
preLoaderRoute: typeof ProfileDidPostRkeyRouteImport
263
parentRoute: typeof rootRouteImport
264
}
265
}
266
}
267
···
295
PathlessLayoutRouteChildren,
296
)
297
298
const rootRouteChildren: RootRouteChildren = {
299
IndexRoute: IndexRoute,
300
PathlessLayoutRoute: PathlessLayoutRouteWithChildren,
···
304
SettingsRoute: SettingsRoute,
305
CallbackIndexRoute: CallbackIndexRoute,
306
ProfileDidIndexRoute: ProfileDidIndexRoute,
307
-
ProfileDidPostRkeyRoute: ProfileDidPostRkeyRoute,
308
}
309
export const routeTree = rootRouteImport
310
._addFileChildren(rootRouteChildren)
···
21
import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b'
22
import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a'
23
import { Route as ProfileDidPostRkeyRouteImport } from './routes/profile.$did/post.$rkey'
24
+
import { Route as ProfileDidPostRkeyImageIRouteImport } from './routes/profile.$did/post.$rkey.image.$i'
25
26
const SettingsRoute = SettingsRouteImport.update({
27
id: '/settings',
···
84
path: '/profile/$did/post/$rkey',
85
getParentRoute: () => rootRouteImport,
86
} as any)
87
+
const ProfileDidPostRkeyImageIRoute =
88
+
ProfileDidPostRkeyImageIRouteImport.update({
89
+
id: '/image/$i',
90
+
path: '/image/$i',
91
+
getParentRoute: () => ProfileDidPostRkeyRoute,
92
+
} as any)
93
94
export interface FileRoutesByFullPath {
95
'/': typeof IndexRoute
···
101
'/route-a': typeof PathlessLayoutNestedLayoutRouteARoute
102
'/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
103
'/profile/$did': typeof ProfileDidIndexRoute
104
+
'/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren
105
+
'/profile/$did/post/$rkey/image/$i': typeof ProfileDidPostRkeyImageIRoute
106
}
107
export interface FileRoutesByTo {
108
'/': typeof IndexRoute
···
114
'/route-a': typeof PathlessLayoutNestedLayoutRouteARoute
115
'/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
116
'/profile/$did': typeof ProfileDidIndexRoute
117
+
'/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren
118
+
'/profile/$did/post/$rkey/image/$i': typeof ProfileDidPostRkeyImageIRoute
119
}
120
export interface FileRoutesById {
121
__root__: typeof rootRouteImport
···
130
'/_pathlessLayout/_nested-layout/route-a': typeof PathlessLayoutNestedLayoutRouteARoute
131
'/_pathlessLayout/_nested-layout/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
132
'/profile/$did/': typeof ProfileDidIndexRoute
133
+
'/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren
134
+
'/profile/$did/post/$rkey/image/$i': typeof ProfileDidPostRkeyImageIRoute
135
}
136
export interface FileRouteTypes {
137
fileRoutesByFullPath: FileRoutesByFullPath
···
146
| '/route-b'
147
| '/profile/$did'
148
| '/profile/$did/post/$rkey'
149
+
| '/profile/$did/post/$rkey/image/$i'
150
fileRoutesByTo: FileRoutesByTo
151
to:
152
| '/'
···
159
| '/route-b'
160
| '/profile/$did'
161
| '/profile/$did/post/$rkey'
162
+
| '/profile/$did/post/$rkey/image/$i'
163
id:
164
| '__root__'
165
| '/'
···
174
| '/_pathlessLayout/_nested-layout/route-b'
175
| '/profile/$did/'
176
| '/profile/$did/post/$rkey'
177
+
| '/profile/$did/post/$rkey/image/$i'
178
fileRoutesById: FileRoutesById
179
}
180
export interface RootRouteChildren {
···
186
SettingsRoute: typeof SettingsRoute
187
CallbackIndexRoute: typeof CallbackIndexRoute
188
ProfileDidIndexRoute: typeof ProfileDidIndexRoute
189
+
ProfileDidPostRkeyRoute: typeof ProfileDidPostRkeyRouteWithChildren
190
}
191
192
declare module '@tanstack/react-router' {
···
275
preLoaderRoute: typeof ProfileDidPostRkeyRouteImport
276
parentRoute: typeof rootRouteImport
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
+
}
285
}
286
}
287
···
315
PathlessLayoutRouteChildren,
316
)
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
+
329
const rootRouteChildren: RootRouteChildren = {
330
IndexRoute: IndexRoute,
331
PathlessLayoutRoute: PathlessLayoutRouteWithChildren,
···
335
SettingsRoute: SettingsRoute,
336
CallbackIndexRoute: CallbackIndexRoute,
337
ProfileDidIndexRoute: ProfileDidIndexRoute,
338
+
ProfileDidPostRkeyRoute: ProfileDidPostRkeyRouteWithChildren,
339
}
340
export const routeTree = rootRouteImport
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
import { useQueryClient } from "@tanstack/react-query";
2
-
import { createFileRoute } from "@tanstack/react-router";
3
import React, { useLayoutEffect } from "react";
4
5
import { Header } from "~/components/Header";
···
11
useQueryIdentity,
12
useQueryPost,
13
} from "~/utils/useQuery";
14
15
//const HANDLE_DID_CACHE_TIMEOUT = 60 * 60 * 1000; // 1 hour
16
···
37
did,
38
rkey,
39
nopics,
40
}: {
41
did: string;
42
rkey: string;
43
-
nopics?: () => void;
44
}) {
45
//const { get, set } = usePersistentStore();
46
const queryClient = useQueryClient();
···
297
298
return (
299
<>
300
<Header
301
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
-
}
313
/>
314
315
{parentsLoading && (
···
342
detailed={true}
343
topReplyLine={parentsLoading || parents.length > 0}
344
nopics={!!nopics}
345
/>
346
</div>
347
<div
···
1
import { useQueryClient } from "@tanstack/react-query";
2
+
import { createFileRoute, Outlet } from "@tanstack/react-router";
3
import React, { useLayoutEffect } from "react";
4
5
import { Header } from "~/components/Header";
···
11
useQueryIdentity,
12
useQueryPost,
13
} from "~/utils/useQuery";
14
+
15
+
import type { LightboxProps } from "./post.$rkey.image.$i";
16
17
//const HANDLE_DID_CACHE_TIMEOUT = 60 * 60 * 1000; // 1 hour
18
···
39
did,
40
rkey,
41
nopics,
42
+
lightboxCallback
43
}: {
44
did: string;
45
rkey: string;
46
+
nopics?: boolean;
47
+
lightboxCallback?: (d:LightboxProps) => void;
48
}) {
49
//const { get, set } = usePersistentStore();
50
const queryClient = useQueryClient();
···
301
302
return (
303
<>
304
+
<Outlet />
305
<Header
306
title={`Post`}
307
+
backButtonCallback={() => {
308
+
if (window.history.length > 1) {
309
+
window.history.back();
310
+
} else {
311
+
window.location.assign("/");
312
+
}
313
+
}}
314
/>
315
316
{parentsLoading && (
···
343
detailed={true}
344
topReplyLine={parentsLoading || parents.length > 0}
345
nopics={!!nopics}
346
+
lightboxCallback={lightboxCallback}
347
/>
348
</div>
349
<div