forked from
pds.ls/pdsls
atmosphere explorer
1import { A } from "@solidjs/router";
2import { createSignal, For, JSX, onCleanup, onMount } from "solid-js";
3import { setOpenManager, setShowAddAccount } from "../auth/state";
4import { Button } from "../components/button";
5import { Favicon } from "../components/favicon";
6import { JSONValue } from "../components/json";
7import { SearchButton } from "../components/search";
8
9const SLIDES = ["Repository", "Record", "PDS"] as const;
10
11const slideLinks = [
12 "/at://did:plc:vwzwgnygau7ed7b7wt5ux7y2",
13 "/at://did:plc:ia76kvnndjutgedggx2ibrem/app.bsky.feed.post/3kenlltlvus2u",
14 "/npmx.social",
15] as const;
16
17const exampleRecord = {
18 text: "ma'am do you know where the petard is, i'd like to hoist myself with it",
19 $type: "app.bsky.feed.post",
20 langs: ["en"],
21 createdAt: "2023-11-20T21:44:21.000Z",
22};
23
24const exampleCollections = [
25 { authority: "app.bsky", nsids: ["actor.profile", "feed.post", "feed.like", "graph.follow"] },
26 { authority: "sh.tangled", nsids: ["actor.profile", "repo.pull"] },
27 { authority: "place.stream", nsids: ["chat.message"] },
28];
29
30const exampleRepos = [
31 "did:plc:ty2jdjtqqq4jn7kk7p3mpwae",
32 "did:plc:byfvayavc7z2ldyu6bu5myz2",
33 "did:plc:n34gdj7o3o6ktuxp5qfbgllu",
34 "did:plc:vh7y4mqklsu2uui5tlwl42dy",
35 "did:plc:uz76j2yr2ps7apdxtlgqljwk",
36];
37
38const ExplorerShowcase = () => {
39 const [slide, setSlide] = createSignal(0);
40
41 onMount(() => {
42 const id = setInterval(() => setSlide((s) => (s + 1) % SLIDES.length), 5000);
43 onCleanup(() => clearInterval(id));
44 });
45
46 return (
47 <div class="flex flex-col gap-1.5">
48 <A
49 href={slideLinks[slide()]}
50 class="relative block h-42 overflow-hidden rounded-lg border border-neutral-200 bg-neutral-50 transition-colors hover:border-neutral-300 dark:border-neutral-700 dark:bg-neutral-800 dark:hover:border-neutral-600"
51 >
52 {/* Collections slide */}
53 <div
54 class="pointer-events-none absolute inset-0 flex flex-col gap-1 overflow-hidden px-3 py-2 text-sm wrap-anywhere transition-opacity duration-700"
55 classList={{ "opacity-0": slide() !== 0 }}
56 >
57 <For each={exampleCollections}>
58 {({ authority, nsids }) => (
59 <div class="flex items-start gap-2">
60 <Favicon authority={authority} />
61 <div class="flex flex-col">
62 <For each={nsids}>
63 {(nsid) => (
64 <span>
65 <span class="text-neutral-500 dark:text-neutral-400">{authority}.</span>
66 <span>{nsid}</span>
67 </span>
68 )}
69 </For>
70 </div>
71 </div>
72 )}
73 </For>
74 </div>
75
76 {/* Record slide */}
77 <div
78 class="pointer-events-none absolute inset-0 overflow-hidden px-3 py-2 font-mono text-xs wrap-anywhere whitespace-pre-wrap transition-opacity duration-700 sm:text-sm"
79 classList={{ "opacity-0": slide() !== 1 }}
80 >
81 <JSONValue data={exampleRecord as any} repo="did:plc:ia76kvnndjutgedggx2ibrem" />
82 </div>
83
84 {/* Repos slide */}
85 <div
86 class="pointer-events-none absolute inset-0 overflow-hidden py-0.5 transition-opacity duration-700"
87 classList={{ "opacity-0": slide() !== 2 }}
88 >
89 <For each={exampleRepos}>
90 {(did) => (
91 <div class="flex min-w-0 items-center gap-2 p-1.5 font-mono text-sm">
92 <span class="flex shrink-0 items-center text-neutral-400 dark:text-neutral-500">
93 <span class="iconify lucide--chevron-right" />
94 </span>
95 <span class="truncate text-blue-500 dark:text-blue-400">{did}</span>
96 </div>
97 )}
98 </For>
99 </div>
100 </A>
101
102 {/* Slide indicator */}
103 <div class="flex items-center justify-between px-0.5">
104 <span class="text-xs text-neutral-400 dark:text-neutral-500">{SLIDES[slide()]}</span>
105 <div class="flex gap-1">
106 <For each={SLIDES}>
107 {(_, i) => (
108 <button
109 onClick={() => setSlide(i())}
110 class="h-1 rounded-full transition-all duration-300"
111 classList={{
112 "w-4 bg-neutral-400 dark:bg-neutral-500": slide() === i(),
113 "w-1.5 bg-neutral-300 dark:bg-neutral-600": slide() !== i(),
114 }}
115 />
116 )}
117 </For>
118 </div>
119 </div>
120 </div>
121 );
122};
123
124export const Home = () => {
125 const FooterLink = (props: {
126 href: string;
127 color: string;
128 darkColor?: string;
129 children: JSX.Element;
130 }) => (
131 <a
132 href={props.href}
133 class={`relative flex items-center gap-1.5 after:absolute after:bottom-0 after:left-0 after:h-px after:w-0 after:bg-current ${props.color} after:transition-[width] after:duration-300 after:ease-out hover:after:w-full ${props.darkColor ?? ""}`}
134 target="_blank"
135 >
136 {props.children}
137 </a>
138 );
139
140 return (
141 <div class="flex w-full flex-col gap-5 px-2 wrap-break-word">
142 {/* Welcome Section */}
143 <div class="flex flex-col gap-4">
144 <div class="flex flex-col gap-1">
145 <h1 class="text-lg font-medium">Atmosphere Explorer</h1>
146 <div class="text-sm text-neutral-600 dark:text-neutral-300">
147 <p>
148 Browse the public data on the{" "}
149 <a
150 href="https://atproto.com"
151 target="_blank"
152 class="underline decoration-neutral-400 transition-colors hover:text-blue-500 hover:decoration-blue-500 dark:decoration-neutral-500 dark:hover:text-blue-400"
153 >
154 AT Protocol
155 </a>
156 </p>
157 </div>
158 </div>
159
160 <ExplorerShowcase />
161
162 <div class="flex items-center gap-1.5 text-xs text-neutral-500 dark:text-neutral-400">
163 <SearchButton />
164 <span>to find any account</span>
165 </div>
166 <div class="flex items-center gap-1.5 text-xs text-neutral-500 dark:text-neutral-400">
167 <Button
168 onClick={() => {
169 setOpenManager(true);
170 setShowAddAccount(true);
171 }}
172 >
173 <span class="iconify lucide--user-round"></span>
174 Sign in
175 </Button>
176 <span>to manage records</span>
177 </div>
178 </div>
179
180 <div class="flex flex-col gap-4 text-sm">
181 <div class="flex flex-col gap-2">
182 <A
183 href="/jetstream"
184 class="group grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-0.5 text-neutral-700 transition-colors hover:text-blue-500 dark:text-neutral-300 dark:hover:text-blue-400"
185 >
186 <div class="iconify lucide--radio-tower" />
187 <span class="underline decoration-transparent group-hover:decoration-current">
188 Jetstream
189 </span>
190 <div />
191 <span class="text-xs text-neutral-500 dark:text-neutral-400">
192 Event stream with filtering
193 </span>
194 </A>
195 <A
196 href="/firehose"
197 class="group grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-0.5 text-neutral-700 transition-colors hover:text-blue-500 dark:text-neutral-300 dark:hover:text-blue-400"
198 >
199 <div class="iconify lucide--rss" />
200 <span class="underline decoration-transparent group-hover:decoration-current">
201 Firehose
202 </span>
203 <div />
204 <span class="text-xs text-neutral-500 dark:text-neutral-400">
205 Raw relay event stream
206 </span>
207 </A>
208 <A
209 href="/spacedust"
210 class="group grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-0.5 text-neutral-700 transition-colors hover:text-blue-500 dark:text-neutral-300 dark:hover:text-blue-400"
211 >
212 <div class="iconify lucide--orbit" />
213 <span class="underline decoration-transparent group-hover:decoration-current">
214 Spacedust
215 </span>
216 <div />
217 <span class="text-xs text-neutral-500 dark:text-neutral-400">
218 Interaction links stream
219 </span>
220 </A>
221 </div>
222
223 <div class="flex flex-col gap-2">
224 <A
225 href="/labels"
226 class="group grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-0.5 text-neutral-700 transition-colors hover:text-blue-500 dark:text-neutral-300 dark:hover:text-blue-400"
227 >
228 <div class="iconify lucide--tag" />
229 <span class="underline decoration-transparent group-hover:decoration-current">
230 Labels
231 </span>
232 <div />
233 <span class="text-xs text-neutral-500 dark:text-neutral-400">
234 Query labeler services
235 </span>
236 </A>
237 <A
238 href="/car"
239 class="group grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-0.5 text-neutral-700 transition-colors hover:text-blue-500 dark:text-neutral-300 dark:hover:text-blue-400"
240 >
241 <div class="iconify lucide--folder-archive" />
242 <span class="underline decoration-transparent group-hover:decoration-current">
243 Archive
244 </span>
245 <div />
246 <span class="text-xs text-neutral-500 dark:text-neutral-400">
247 Explore and unpack CAR files
248 </span>
249 </A>
250 </div>
251 </div>
252
253 <div class="flex justify-center gap-1.5 text-sm text-neutral-600 sm:gap-2 dark:text-neutral-300">
254 <FooterLink href="https://raycast.com/juliet_philippe/pdsls" color="after:text-[#FF6363]">
255 <span class="iconify-color i-raycast-light block dark:hidden"></span>
256 <span class="iconify-color i-raycast-dark hidden dark:block"></span>
257 Raycast
258 </FooterLink>
259 •
260 <FooterLink
261 href="https://bsky.app/profile/did:plc:6q5daed5gutiyerimlrnojnz"
262 color="after:text-[#0085ff]"
263 >
264 <span class="simple-icons--bluesky iconify text-[#0085ff]"></span>
265 Bluesky
266 </FooterLink>
267 •
268 <FooterLink
269 href="https://tangled.org/did:plc:6q5daed5gutiyerimlrnojnz/pdsls/"
270 color="after:text-black"
271 darkColor="dark:after:text-white"
272 >
273 <span class="iconify i-tangled text-black dark:text-white"></span>
274 Source
275 </FooterLink>
276 </div>
277 </div>
278 );
279};