Fork of atp.tools as a universal profile for people on the ATmosphere
3
fork

Configure Feed

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

add first layer of pds view (will clean up soon)

+252 -9
+7 -2
src/components/json/appBskyEmbedImages.tsx
··· 2 2 import { X } from "lucide-react"; 3 3 import { useState } from "preact/hooks"; 4 4 5 - export const getBlueskyCdnLink = (did: string, cid: string, ext: string) => { 6 - return `https://cdn.bsky.app/img/feed_fullsize/plain/${did}/${cid}@${ext}`; 5 + export const getBlueskyCdnLink = ( 6 + did: string, 7 + cid: string, 8 + ext: string, 9 + type: string = "feed_fullsize", 10 + ) => { 11 + return `https://cdn.bsky.app/img/${type}/plain/${did}/${cid}@${ext}`; 7 12 }; 8 13 9 14 export const AppBskyEmbedImagesLayout = ({
+16 -2
src/components/smartSearchBar.tsx
··· 40 40 e.preventDefault(); 41 41 if (input.trim()) { 42 42 // replace at:// with / to match the route correctly 43 - navigate({ to: `/at:/${input.replace("at:/", "")}` }); 43 + if (input.startsWith("pds/") || input.startsWith("https:/")) { 44 + navigate({ 45 + to: "/pds/$url", 46 + params: { 47 + url: input.replace("https:/", "").replace("pds/", ""), 48 + }, 49 + }); 50 + } else { 51 + navigate({ 52 + to: `/at:/${input.replace("at:/", "")}`, 53 + }); 54 + } 44 55 setOpen(false); 45 56 } 46 57 }; ··· 81 92 </DialogTrigger> 82 93 83 94 <DialogContent className="sm:max-w-[800px] p-0 border-0 rounded-full bg-transparent"> 84 - <form onSubmit={handleSubmit} className="relative backdrop-blur-3xl rounded-full"> 95 + <form 96 + onSubmit={handleSubmit} 97 + className="relative backdrop-blur-3xl rounded-full" 98 + > 85 99 <Search className="absolute left-4 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" /> 86 100 <Input 87 101 type="text"
+1
src/components/views/app-bsky/actorProfile.tsx
··· 44 44 repoData?.did!, 45 45 profile?.banner?.ref.$link, 46 46 "jpeg", 47 + "banner", 47 48 )} 48 49 className="w-full rounded-lg -z-10 border object-cover -mb-16" 49 50 />
+26
src/routeTree.gen.ts
··· 14 14 15 15 import { Route as rootRoute } from './routes/__root' 16 16 import { Route as FirehoseIndexImport } from './routes/firehose/index' 17 + import { Route as PdsUrlIndexImport } from './routes/pds/$url.index' 17 18 import { Route as AtHandleIndexImport } from './routes/at:/$handle.index' 18 19 19 20 // Create Virtual Routes ··· 51 52 const FirehoseIndexRoute = FirehoseIndexImport.update({ 52 53 id: '/firehose/', 53 54 path: '/firehose/', 55 + getParentRoute: () => rootRoute, 56 + } as any) 57 + 58 + const PdsUrlIndexRoute = PdsUrlIndexImport.update({ 59 + id: '/pds/$url/', 60 + path: '/pds/$url/', 54 61 getParentRoute: () => rootRoute, 55 62 } as any) 56 63 ··· 118 125 preLoaderRoute: typeof AtHandleIndexImport 119 126 parentRoute: typeof rootRoute 120 127 } 128 + '/pds/$url/': { 129 + id: '/pds/$url/' 130 + path: '/pds/$url' 131 + fullPath: '/pds/$url' 132 + preLoaderRoute: typeof PdsUrlIndexImport 133 + parentRoute: typeof rootRoute 134 + } 121 135 '/at:/$handle/$collection/$rkey': { 122 136 id: '/at:/$handle/$collection/$rkey' 123 137 path: '/at:/$handle/$collection/$rkey' ··· 143 157 '/counter': typeof CounterLazyRoute 144 158 '/firehose': typeof FirehoseIndexRoute 145 159 '/at:/$handle': typeof AtHandleIndexRoute 160 + '/pds/$url': typeof PdsUrlIndexRoute 146 161 '/at:/$handle/$collection/$rkey': typeof AtHandleCollectionRkeyLazyRoute 147 162 '/at:/$handle/$collection': typeof AtHandleCollectionIndexLazyRoute 148 163 } ··· 153 168 '/counter': typeof CounterLazyRoute 154 169 '/firehose': typeof FirehoseIndexRoute 155 170 '/at:/$handle': typeof AtHandleIndexRoute 171 + '/pds/$url': typeof PdsUrlIndexRoute 156 172 '/at:/$handle/$collection/$rkey': typeof AtHandleCollectionRkeyLazyRoute 157 173 '/at:/$handle/$collection': typeof AtHandleCollectionIndexLazyRoute 158 174 } ··· 164 180 '/counter': typeof CounterLazyRoute 165 181 '/firehose/': typeof FirehoseIndexRoute 166 182 '/at:/$handle/': typeof AtHandleIndexRoute 183 + '/pds/$url/': typeof PdsUrlIndexRoute 167 184 '/at:/$handle/$collection/$rkey': typeof AtHandleCollectionRkeyLazyRoute 168 185 '/at:/$handle/$collection/': typeof AtHandleCollectionIndexLazyRoute 169 186 } ··· 176 193 | '/counter' 177 194 | '/firehose' 178 195 | '/at:/$handle' 196 + | '/pds/$url' 179 197 | '/at:/$handle/$collection/$rkey' 180 198 | '/at:/$handle/$collection' 181 199 fileRoutesByTo: FileRoutesByTo ··· 185 203 | '/counter' 186 204 | '/firehose' 187 205 | '/at:/$handle' 206 + | '/pds/$url' 188 207 | '/at:/$handle/$collection/$rkey' 189 208 | '/at:/$handle/$collection' 190 209 id: ··· 194 213 | '/counter' 195 214 | '/firehose/' 196 215 | '/at:/$handle/' 216 + | '/pds/$url/' 197 217 | '/at:/$handle/$collection/$rkey' 198 218 | '/at:/$handle/$collection/' 199 219 fileRoutesById: FileRoutesById ··· 205 225 CounterLazyRoute: typeof CounterLazyRoute 206 226 FirehoseIndexRoute: typeof FirehoseIndexRoute 207 227 AtHandleIndexRoute: typeof AtHandleIndexRoute 228 + PdsUrlIndexRoute: typeof PdsUrlIndexRoute 208 229 AtHandleCollectionRkeyLazyRoute: typeof AtHandleCollectionRkeyLazyRoute 209 230 AtHandleCollectionIndexLazyRoute: typeof AtHandleCollectionIndexLazyRoute 210 231 } ··· 215 236 CounterLazyRoute: CounterLazyRoute, 216 237 FirehoseIndexRoute: FirehoseIndexRoute, 217 238 AtHandleIndexRoute: AtHandleIndexRoute, 239 + PdsUrlIndexRoute: PdsUrlIndexRoute, 218 240 AtHandleCollectionRkeyLazyRoute: AtHandleCollectionRkeyLazyRoute, 219 241 AtHandleCollectionIndexLazyRoute: AtHandleCollectionIndexLazyRoute, 220 242 } ··· 234 256 "/counter", 235 257 "/firehose/", 236 258 "/at:/$handle/", 259 + "/pds/$url/", 237 260 "/at:/$handle/$collection/$rkey", 238 261 "/at:/$handle/$collection/" 239 262 ] ··· 252 275 }, 253 276 "/at:/$handle/": { 254 277 "filePath": "at:/$handle.index.tsx" 278 + }, 279 + "/pds/$url/": { 280 + "filePath": "pds/$url.index.tsx" 255 281 }, 256 282 "/at:/$handle/$collection/$rkey": { 257 283 "filePath": "at:/$handle/$collection.$rkey.lazy.tsx"
+3 -3
src/routes/at:/$handle/$collection.index.lazy.tsx
··· 33 33 fetchMore: async () => {}, 34 34 }); 35 35 36 - const fetchRepoData = async (cursor?: string) => { 36 + async function fetchRepoData(cursor?: string) { 37 37 try { 38 38 if (state.isLoading) return; 39 39 ··· 70 70 error: err instanceof Error ? err : new Error("An error occurred"), 71 71 })); 72 72 } 73 - }; 73 + } 74 74 75 75 const fetchMore = async (cursor: string) => { 76 76 if (cursor && !state.isLoading) { ··· 168 168 </div> 169 169 )} 170 170 {!isLoading && !cursor && ( 171 - <div className="text-center text-sm text-muted-foreground mx-10"> 171 + <div className="text-center text-sm text-muted-foreground mx-10 mt-2"> 172 172 that's all, folks! 173 173 </div> 174 174 )}
+32 -2
src/routes/index.lazy.tsx
··· 48 48 <Link 49 49 key="jay" 50 50 to="/at:/$handle" 51 - params={{ handle: "jay.bsky.social" }} 51 + params={{ handle: "jay.bsky.team" }} 52 + className="text-blue-500" 53 + > 54 + <div className="bg-muted text-muted-foreground rounded-full px-3 py-1 hover:bg-muted/80 transition-colors"> 55 + at://jay.bsky.team 56 + </div> 57 + </Link>, 58 + <Link 59 + key="apenwarr" 60 + to="/at:/$handle" 61 + params={{ handle: "apenwarr.ca" }} 52 62 className="text-blue-500" 53 63 > 54 64 <div className="bg-muted text-muted-foreground rounded-full px-3 py-1 hover:bg-muted/80 transition-colors"> 55 - at://jay.bsky.social 65 + at://apenwarr.ca 66 + </div> 67 + </Link>, 68 + <Link 69 + key="mraow-pds" 70 + to="/pds/$url" 71 + params={{ url: "mraow.party" }} 72 + className="text-blue-500" 73 + > 74 + <div className="bg-muted text-muted-foreground rounded-full px-3 py-1 hover:bg-muted/80 transition-colors"> 75 + pds/mraow.party 76 + </div> 77 + </Link>, 78 + <Link 79 + key="millipds-test" 80 + to="/at:/$handle" 81 + params={{ handle: "david.dev.retr0.id" }} 82 + className="text-blue-500" 83 + > 84 + <div className="bg-muted text-muted-foreground rounded-full px-3 py-1 hover:bg-muted/80 transition-colors"> 85 + at://david.dev.retr0.id 56 86 </div> 57 87 </Link>, 58 88 <Link
+167
src/routes/pds/$url.index.tsx
··· 1 + import ShowError from "@/components/error"; 2 + import { Loader } from "@/components/ui/loader"; 3 + import { useDocumentTitle } from "@/hooks/useDocumentTitle"; 4 + import { QtClient, useXrpc } from "@/providers/qtprovider"; 5 + import { ComAtprotoSyncListRepos } from "@atcute/client/lexicons"; 6 + import { createFileRoute, Link } from "@tanstack/react-router"; 7 + import { ShieldX } from "lucide-react"; 8 + import { useState, useEffect, useRef } from "preact/compat"; 9 + 10 + interface PdsData { 11 + records?: ComAtprotoSyncListRepos.Output["repos"]; 12 + cursor?: string; 13 + health?: PdsHealth; 14 + isLoading: boolean; 15 + error: Error | null; 16 + fetchMore: (cursor: string) => Promise<void>; 17 + } 18 + 19 + interface PdsHealth { 20 + version: string; 21 + } 22 + 23 + function useRepoData(baseUrl: string): PdsData { 24 + const xrpc = useXrpc(); 25 + const [state, setState] = useState<PdsData>({ 26 + isLoading: true, 27 + error: null, 28 + fetchMore: async () => {}, 29 + }); 30 + 31 + useDocumentTitle(baseUrl ? `${baseUrl} | atp.tools` : "atp.tools"); 32 + const abortController = new AbortController(); 33 + 34 + async function fetchRepoData(cursor?: string) { 35 + try { 36 + setState((prev) => ({ ...prev, isLoading: true })); 37 + 38 + // we dont use the main authenticated client here 39 + const rpc = new QtClient(new URL("https://" + baseUrl)); 40 + // get the PDS 41 + const response = await rpc 42 + .getXrpcClient() 43 + .get("com.atproto.sync.listRepos", { 44 + params: { limit: 1000, cursor }, 45 + signal: abortController.signal, 46 + }); 47 + 48 + const health = await rpc 49 + .getXrpcClient() 50 + .request({ nsid: "_health", type: "get" }); 51 + 52 + setState((prev) => ({ 53 + ...prev, 54 + records: cursor 55 + ? [...(prev.records || []), ...response.data.repos] 56 + : response.data.repos, 57 + cursor: response.data.cursor, 58 + health: health.data, 59 + isLoading: false, 60 + error: null, 61 + })); 62 + 63 + // todo: actual errors 64 + } catch (err: any) { 65 + if (err.name === "AbortError") return; 66 + 67 + setState((prev) => ({ 68 + ...prev, 69 + isLoading: false, 70 + error: err instanceof Error ? err : new Error("An error occurred"), 71 + })); 72 + } 73 + } 74 + 75 + useEffect(() => { 76 + fetchRepoData(); 77 + 78 + return () => { 79 + abortController.abort(); 80 + }; 81 + }, [baseUrl, xrpc]); 82 + 83 + const fetchMore = async (cursor: string) => { 84 + if (cursor && !state.isLoading) { 85 + await fetchRepoData(cursor); 86 + } 87 + }; 88 + 89 + return { ...state, fetchMore }; 90 + } 91 + 92 + export const Route = createFileRoute("/pds/$url/")({ 93 + component: RouteComponent, 94 + }); 95 + 96 + function RouteComponent() { 97 + const { url } = Route.useParams(); 98 + const { cursor, records, fetchMore, isLoading, error } = useRepoData(url); 99 + 100 + useDocumentTitle(records ? `${url} (PDS) | atp.tools` : "atp.tools"); 101 + 102 + const loaderRef = useRef<HTMLDivElement>(null); 103 + useEffect(() => { 104 + if (!loaderRef.current) return; 105 + 106 + const observer = new IntersectionObserver( 107 + (entries) => { 108 + const target = entries[0]; 109 + if (target.isIntersecting && !isLoading && cursor) { 110 + fetchMore(cursor); 111 + } 112 + }, 113 + { threshold: 0.1, rootMargin: "50px" }, 114 + ); 115 + 116 + observer.observe(loaderRef.current); 117 + return () => observer.disconnect(); 118 + }, [cursor, isLoading, fetchMore]); 119 + 120 + if (error) { 121 + return <ShowError error={error} />; 122 + } 123 + 124 + if ((isLoading && !cursor) || !records) { 125 + return <Loader className="max-h-[calc(100vh-5rem)] h-screen" />; 126 + } 127 + 128 + return ( 129 + <div className="flex flex-row justify-center w-full"> 130 + <div className="max-w-2xl w-screen p-4 md:mt-16 space-y-2"> 131 + <div> 132 + PDS: {url.includes("bsky.network") && "🍄"} {url} 133 + </div> 134 + 135 + <div> 136 + <h2 className="text-xl font-bold">Repositories (accounts)</h2> 137 + <ul> 138 + {records?.map((c) => ( 139 + <li key={c.did} className="text-blue-500"> 140 + <Link 141 + to="/at:/$handle" 142 + params={{ 143 + handle: c.did, 144 + }} 145 + > 146 + {c.did}{" "} 147 + {!c.active && <ShieldX className="inline text-red-500" />} 148 + </Link> 149 + </li> 150 + ))} 151 + </ul> 152 + <div 153 + ref={loaderRef} 154 + className="flex flex-row justify-center h-10 -pt-16" 155 + > 156 + {isLoading && <Loader className="max-h-16 h-screen" />} 157 + {!isLoading && !cursor && ( 158 + <div className="text-center text-sm text-muted-foreground mx-10 mt-2"> 159 + that's all, folks! 160 + </div> 161 + )} 162 + </div> 163 + </div> 164 + </div> 165 + </div> 166 + ); 167 + }