-1
index.html
-1
index.html
···
10
10
<meta property="og:description" content="Browse the public data on atproto" />
11
11
<meta property="description" content="Browse the public data on atproto" />
12
12
<link rel="manifest" href="/manifest.json" />
13
-
<title>PDSls</title>
14
13
<link rel="preconnect" href="https://fonts.bunny.net" />
15
14
<link href="https://fonts.bunny.net/css?family=roboto-mono:400" rel="stylesheet" />
16
15
<link href="https://fonts.cdnfonts.com/css/pecita" rel="stylesheet" />
+87
-86
src/layout.tsx
+87
-86
src/layout.tsx
···
1
1
import { Handle } from "@atcute/lexicons";
2
-
import { Meta, MetaProvider } from "@solidjs/meta";
2
+
import { Meta, MetaProvider, Title } from "@solidjs/meta";
3
3
import { A, RouteSectionProps, useLocation, useNavigate } from "@solidjs/router";
4
4
import { createEffect, ErrorBoundary, onCleanup, onMount, Show, Suspense } from "solid-js";
5
5
import { AccountManager } from "./auth/account.jsx";
···
118
118
});
119
119
120
120
return (
121
-
<div id="main" class="mx-auto mb-8 flex max-w-lg flex-col items-center p-3">
122
-
<MetaProvider>
123
-
<Show when={location.pathname !== "/"}>
124
-
<Meta name="robots" content="noindex, nofollow" />
125
-
</Show>
126
-
</MetaProvider>
127
-
<header
128
-
class={`dark:shadow-dark-700 mb-3 flex w-full items-center justify-between rounded-xl border-[0.5px] border-neutral-300 bg-neutral-50 bg-size-[95%] bg-right bg-no-repeat p-2 pl-3 shadow-xs [--header-bg:#fafafa] [--trans-blue:#5BCEFA90] [--trans-pink:#F5A9B890] [--trans-white:#FFFFFF90] dark:border-neutral-700 dark:bg-neutral-800 dark:[--header-bg:#262626] dark:[--trans-blue:#5BCEFAa0] dark:[--trans-pink:#F5A9B8a0] dark:[--trans-white:#FFFFFFa0] ${localStorage.getItem("hrt") === "true" ? "bg-[linear-gradient(to_left,transparent_10%,var(--header-bg)_85%),linear-gradient(to_bottom,var(--trans-blue)_0%,var(--trans-blue)_20%,var(--trans-pink)_20%,var(--trans-pink)_40%,var(--trans-white)_40%,var(--trans-white)_60%,var(--trans-pink)_60%,var(--trans-pink)_80%,var(--trans-blue)_80%,var(--trans-blue)_100%)]" : ""}`}
129
-
style={{
130
-
"background-image":
131
-
props.params.repo && props.params.repo in headers ?
132
-
`linear-gradient(to left, transparent 10%, var(--header-bg) 85%), url(/headers/${headers[props.params.repo]})`
133
-
: undefined,
134
-
}}
135
-
>
136
-
<A
137
-
href="/"
138
-
style='font-feature-settings: "cv05"'
139
-
class="relative flex items-center gap-1 text-xl font-semibold"
121
+
<MetaProvider>
122
+
<Title>PDSls</Title>
123
+
<Show when={location.pathname !== "/"}>
124
+
<Meta name="robots" content="noindex, nofollow" />
125
+
</Show>
126
+
<div id="main" class="mx-auto mb-8 flex max-w-lg flex-col items-center p-3">
127
+
<header
128
+
class={`dark:shadow-dark-700 mb-3 flex w-full items-center justify-between rounded-xl border-[0.5px] border-neutral-300 bg-neutral-50 bg-size-[95%] bg-right bg-no-repeat p-2 pl-3 shadow-xs [--header-bg:#fafafa] [--trans-blue:#5BCEFA90] [--trans-pink:#F5A9B890] [--trans-white:#FFFFFF90] dark:border-neutral-700 dark:bg-neutral-800 dark:[--header-bg:#262626] dark:[--trans-blue:#5BCEFAa0] dark:[--trans-pink:#F5A9B8a0] dark:[--trans-white:#FFFFFFa0] ${localStorage.getItem("hrt") === "true" ? "bg-[linear-gradient(to_left,transparent_10%,var(--header-bg)_85%),linear-gradient(to_bottom,var(--trans-blue)_0%,var(--trans-blue)_20%,var(--trans-pink)_20%,var(--trans-pink)_40%,var(--trans-white)_40%,var(--trans-white)_60%,var(--trans-pink)_60%,var(--trans-pink)_80%,var(--trans-blue)_80%,var(--trans-blue)_100%)]" : ""}`}
129
+
style={{
130
+
"background-image":
131
+
props.params.repo && props.params.repo in headers ?
132
+
`linear-gradient(to left, transparent 10%, var(--header-bg) 85%), url(/headers/${headers[props.params.repo]})`
133
+
: undefined,
134
+
}}
140
135
>
141
-
<span class="iconify tabler--binary-tree-filled text-[#76c4e5]"></span>
142
-
<span>PDSls</span>
143
-
<Show when={localStorage.getItem("hrt") === "true"}>
144
-
<img
145
-
src="/ribbon.webp"
146
-
alt=""
147
-
class="pointer-events-none absolute -top-3 -right-4 w-8 rotate-15"
148
-
/>
136
+
<A
137
+
href="/"
138
+
style='font-feature-settings: "cv05"'
139
+
class="relative flex items-center gap-1 text-xl font-semibold"
140
+
>
141
+
<span class="iconify tabler--binary-tree-filled text-[#76c4e5]"></span>
142
+
<span>PDSls</span>
143
+
<Show when={localStorage.getItem("hrt") === "true"}>
144
+
<img
145
+
src="/ribbon.webp"
146
+
alt=""
147
+
class="pointer-events-none absolute -top-3 -right-4 w-8 rotate-15"
148
+
/>
149
+
</Show>
150
+
</A>
151
+
<div class="relative flex items-center gap-0.5 rounded-lg bg-neutral-50/60 px-1 py-0.5 dark:bg-neutral-800/60">
152
+
<SearchButton />
153
+
<Show when={hasUserScope("create")}>
154
+
<RecordEditor create={true} />
155
+
</Show>
156
+
<AccountManager />
157
+
<MenuProvider>
158
+
<DropdownMenu icon="lucide--menu text-lg" buttonClass="rounded-lg p-1.5">
159
+
<NavMenu href="/jetstream" label="Jetstream" icon="lucide--radio-tower" />
160
+
<NavMenu href="/firehose" label="Firehose" icon="lucide--antenna" />
161
+
<NavMenu href="/labels" label="Labels" icon="lucide--tag" />
162
+
<NavMenu href="/settings" label="Settings" icon="lucide--settings" />
163
+
<MenuSeparator />
164
+
<NavMenu
165
+
href="https://bsky.app/profile/did:plc:6q5daed5gutiyerimlrnojnz"
166
+
label="Bluesky"
167
+
icon="simple-icons--bluesky text-[#0085ff]"
168
+
newTab
169
+
/>
170
+
<NavMenu
171
+
href="https://tangled.org/@pdsls.dev/pdsls/"
172
+
label="Source"
173
+
icon="lucide--code"
174
+
newTab
175
+
/>
176
+
</DropdownMenu>
177
+
</MenuProvider>
178
+
</div>
179
+
</header>
180
+
<div class="flex w-full flex-col items-center gap-3 text-pretty">
181
+
<Show when={showSearch() || location.pathname === "/"}>
182
+
<Search />
183
+
</Show>
184
+
<Show when={props.params.pds}>
185
+
<NavBar params={props.params} />
149
186
</Show>
150
-
</A>
151
-
<div class="relative flex items-center gap-0.5 rounded-lg bg-neutral-50/60 px-1 py-0.5 dark:bg-neutral-800/60">
152
-
<SearchButton />
153
-
<Show when={hasUserScope("create")}>
154
-
<RecordEditor create={true} />
187
+
<Show keyed when={location.pathname}>
188
+
<ErrorBoundary
189
+
fallback={(err) => <div class="mt-3 wrap-anywhere">Error: {err.message}</div>}
190
+
>
191
+
<Suspense
192
+
fallback={
193
+
<span class="iconify lucide--loader-circle mt-3 animate-spin text-xl"></span>
194
+
}
195
+
>
196
+
{props.children}
197
+
</Suspense>
198
+
</ErrorBoundary>
155
199
</Show>
156
-
<AccountManager />
157
-
<MenuProvider>
158
-
<DropdownMenu icon="lucide--menu text-lg" buttonClass="rounded-lg p-1.5">
159
-
<NavMenu href="/jetstream" label="Jetstream" icon="lucide--radio-tower" />
160
-
<NavMenu href="/firehose" label="Firehose" icon="lucide--antenna" />
161
-
<NavMenu href="/labels" label="Labels" icon="lucide--tag" />
162
-
<NavMenu href="/settings" label="Settings" icon="lucide--settings" />
163
-
<MenuSeparator />
164
-
<NavMenu
165
-
href="https://bsky.app/profile/did:plc:6q5daed5gutiyerimlrnojnz"
166
-
label="Bluesky"
167
-
icon="simple-icons--bluesky text-[#0085ff]"
168
-
newTab
169
-
/>
170
-
<NavMenu
171
-
href="https://tangled.org/@pdsls.dev/pdsls/"
172
-
label="Source"
173
-
icon="lucide--code"
174
-
newTab
175
-
/>
176
-
</DropdownMenu>
177
-
</MenuProvider>
178
200
</div>
179
-
</header>
180
-
<div class="flex w-full flex-col items-center gap-3 text-pretty">
181
-
<Show when={showSearch() || location.pathname === "/"}>
182
-
<Search />
183
-
</Show>
184
-
<Show when={props.params.pds}>
185
-
<NavBar params={props.params} />
186
-
</Show>
187
-
<Show keyed when={location.pathname}>
188
-
<ErrorBoundary
189
-
fallback={(err) => <div class="mt-3 wrap-anywhere">Error: {err.message}</div>}
190
-
>
191
-
<Suspense
192
-
fallback={
193
-
<span class="iconify lucide--loader-circle mt-3 animate-spin text-xl"></span>
194
-
}
195
-
>
196
-
{props.children}
197
-
</Suspense>
198
-
</ErrorBoundary>
201
+
<NotificationContainer />
202
+
<Show
203
+
when={localStorage.plcDirectory && localStorage.plcDirectory !== "https://plc.directory"}
204
+
>
205
+
<div class="dark:bg-dark-500 fixed right-0 bottom-0 left-0 z-10 flex items-center justify-center bg-neutral-100 px-3 py-1 text-xs">
206
+
<span>
207
+
PLC directory: <span class="font-medium">{localStorage.plcDirectory}</span>
208
+
</span>
209
+
</div>
199
210
</Show>
200
211
</div>
201
-
<NotificationContainer />
202
-
<Show
203
-
when={localStorage.plcDirectory && localStorage.plcDirectory !== "https://plc.directory"}
204
-
>
205
-
<div class="dark:bg-dark-500 fixed right-0 bottom-0 left-0 z-10 flex items-center justify-center bg-neutral-100 px-3 py-1 text-xs">
206
-
<span>
207
-
PLC directory: <span class="font-medium">{localStorage.plcDirectory}</span>
208
-
</span>
209
-
</div>
210
-
</Show>
211
-
</div>
212
+
</MetaProvider>
212
213
);
213
214
};
214
215
+157
-153
src/views/collection.tsx
+157
-153
src/views/collection.tsx
···
2
2
import { Client, simpleFetchHandler } from "@atcute/client";
3
3
import { $type, ActorIdentifier, InferXRPCBodyOutput } from "@atcute/lexicons";
4
4
import * as TID from "@atcute/tid";
5
+
import { Title } from "@solidjs/meta";
5
6
import { A, useBeforeLeave, useParams } from "@solidjs/router";
6
7
import {
7
8
createEffect,
···
250
251
);
251
252
252
253
return (
253
-
<Show when={records.length || response()}>
254
-
<div class="-mt-2 flex w-full flex-col items-center">
255
-
<StickyOverlay>
256
-
<div class="flex w-full flex-col gap-2">
257
-
<div class="flex items-center gap-1.5">
258
-
<Show when={agent() && agent()?.sub === did && hasUserScope("delete")}>
259
-
<div class="flex items-center">
260
-
<Tooltip
261
-
text={batchDelete() ? "Cancel" : "Delete"}
262
-
children={
263
-
<button
264
-
onclick={() => {
265
-
setRecords({ from: 0, to: records.length - 1 }, "toDelete", false);
266
-
setLastSelected(undefined);
267
-
setBatchDelete(!batchDelete());
268
-
}}
269
-
class="flex items-center rounded-md p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
270
-
>
271
-
<span
272
-
class={`iconify ${batchDelete() ? "lucide--circle-x" : "lucide--trash-2"} `}
273
-
></span>
274
-
</button>
275
-
}
276
-
/>
277
-
<Show when={batchDelete()}>
254
+
<>
255
+
<Title>{params.collection} - PDSls</Title>
256
+
<Show when={records.length || response()}>
257
+
<div class="-mt-2 flex w-full flex-col items-center">
258
+
<StickyOverlay>
259
+
<div class="flex w-full flex-col gap-2">
260
+
<div class="flex items-center gap-1.5">
261
+
<Show when={agent() && agent()?.sub === did && hasUserScope("delete")}>
262
+
<div class="flex items-center">
278
263
<Tooltip
279
-
text="Select all"
264
+
text={batchDelete() ? "Cancel" : "Delete"}
280
265
children={
281
266
<button
282
-
onclick={() => selectAll()}
267
+
onclick={() => {
268
+
setRecords({ from: 0, to: records.length - 1 }, "toDelete", false);
269
+
setLastSelected(undefined);
270
+
setBatchDelete(!batchDelete());
271
+
}}
283
272
class="flex items-center rounded-md p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
284
273
>
285
-
<span class="iconify lucide--copy-check"></span>
274
+
<span
275
+
class={`iconify ${batchDelete() ? "lucide--circle-x" : "lucide--trash-2"} `}
276
+
></span>
286
277
</button>
287
278
}
288
279
/>
289
-
<Show when={hasUserScope("create")}>
280
+
<Show when={batchDelete()}>
281
+
<Tooltip
282
+
text="Select all"
283
+
children={
284
+
<button
285
+
onclick={() => selectAll()}
286
+
class="flex items-center rounded-md p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
287
+
>
288
+
<span class="iconify lucide--copy-check"></span>
289
+
</button>
290
+
}
291
+
/>
292
+
<Show when={hasUserScope("create")}>
293
+
<Tooltip
294
+
text="Recreate"
295
+
children={
296
+
<button
297
+
onclick={() => {
298
+
setRecreate(true);
299
+
setOpenDelete(true);
300
+
}}
301
+
class="flex items-center rounded-md p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
302
+
>
303
+
<span class="iconify lucide--recycle text-green-500 dark:text-green-400"></span>
304
+
</button>
305
+
}
306
+
/>
307
+
</Show>
290
308
<Tooltip
291
-
text="Recreate"
309
+
text="Delete"
292
310
children={
293
311
<button
294
312
onclick={() => {
295
-
setRecreate(true);
313
+
setRecreate(false);
296
314
setOpenDelete(true);
297
315
}}
298
316
class="flex items-center rounded-md p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
299
317
>
300
-
<span class="iconify lucide--recycle text-green-500 dark:text-green-400"></span>
318
+
<span class="iconify lucide--trash-2 text-red-500 dark:text-red-400"></span>
301
319
</button>
302
320
}
303
321
/>
304
322
</Show>
305
-
<Tooltip
306
-
text="Delete"
307
-
children={
308
-
<button
309
-
onclick={() => {
310
-
setRecreate(false);
311
-
setOpenDelete(true);
312
-
}}
313
-
class="flex items-center rounded-md p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
323
+
</div>
324
+
<Modal open={openDelete()} onClose={() => setOpenDelete(false)}>
325
+
<div class="dark:bg-dark-300 dark:shadow-dark-700 absolute top-70 left-[50%] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0">
326
+
<h2 class="mb-2 font-semibold">
327
+
{recreate() ? "Recreate" : "Delete"}{" "}
328
+
{records.filter((r) => r.toDelete).length} records?
329
+
</h2>
330
+
<div class="flex justify-end gap-2">
331
+
<Button onClick={() => setOpenDelete(false)}>Cancel</Button>
332
+
<Button
333
+
onClick={deleteRecords}
334
+
class={`dark:shadow-dark-700 rounded-lg px-2 py-1.5 text-xs text-white shadow-xs select-none ${recreate() ? "bg-green-500 hover:bg-green-400 dark:bg-green-600 dark:hover:bg-green-500" : "bg-red-500 hover:bg-red-400 active:bg-red-400"}`}
314
335
>
315
-
<span class="iconify lucide--trash-2 text-red-500 dark:text-red-400"></span>
316
-
</button>
317
-
}
318
-
/>
319
-
</Show>
320
-
</div>
321
-
<Modal open={openDelete()} onClose={() => setOpenDelete(false)}>
322
-
<div class="dark:bg-dark-300 dark:shadow-dark-700 absolute top-70 left-[50%] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0">
323
-
<h2 class="mb-2 font-semibold">
324
-
{recreate() ? "Recreate" : "Delete"}{" "}
325
-
{records.filter((r) => r.toDelete).length} records?
326
-
</h2>
327
-
<div class="flex justify-end gap-2">
328
-
<Button onClick={() => setOpenDelete(false)}>Cancel</Button>
329
-
<Button
330
-
onClick={deleteRecords}
331
-
class={`dark:shadow-dark-700 rounded-lg px-2 py-1.5 text-xs text-white shadow-xs select-none ${recreate() ? "bg-green-500 hover:bg-green-400 dark:bg-green-600 dark:hover:bg-green-500" : "bg-red-500 hover:bg-red-400 active:bg-red-400"}`}
332
-
>
333
-
{recreate() ? "Recreate" : "Delete"}
334
-
</Button>
336
+
{recreate() ? "Recreate" : "Delete"}
337
+
</Button>
338
+
</div>
335
339
</div>
340
+
</Modal>
341
+
</Show>
342
+
<TextInput
343
+
name="Filter"
344
+
placeholder="Filter by substring"
345
+
onInput={(e) => setFilter(e.currentTarget.value)}
346
+
class="grow"
347
+
/>
348
+
<Tooltip text="Jetstream">
349
+
<A
350
+
href={`/jetstream?collections=${params.collection}&dids=${params.repo}`}
351
+
class="flex items-center rounded-md p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
352
+
>
353
+
<span class="iconify lucide--radio-tower"></span>
354
+
</A>
355
+
</Tooltip>
356
+
</div>
357
+
<Show when={records.length > 1}>
358
+
<div class="flex items-center justify-between gap-x-2">
359
+
<Button
360
+
onClick={() => {
361
+
setReverse(!reverse());
362
+
setCursor(undefined);
363
+
clearCollectionCache(cacheKey());
364
+
refetch();
365
+
}}
366
+
classList={{
367
+
"text-blue-500! dark:text-blue-400! border-blue-500! dark:border-blue-400!":
368
+
reverse(),
369
+
}}
370
+
>
371
+
<span
372
+
class={`iconify ${reverse() ? "lucide--arrow-down-wide-narrow" : "lucide--arrow-up-narrow-wide"}`}
373
+
></span>
374
+
Reverse
375
+
</Button>
376
+
<div>
377
+
<Show when={batchDelete()}>
378
+
<span>{records.filter((rec) => rec.toDelete).length}</span>
379
+
<span>/</span>
380
+
</Show>
381
+
<span>{filter() ? filteredRecords().length : records.length} records</span>
336
382
</div>
337
-
</Modal>
383
+
<div class="flex w-20 items-center justify-end">
384
+
<Show when={cursor()}>
385
+
<Show when={!response.loading}>
386
+
<Button onClick={() => refetch()}>Load More</Button>
387
+
</Show>
388
+
<Show when={response.loading}>
389
+
<div class="iconify lucide--loader-circle w-20 animate-spin text-xl" />
390
+
</Show>
391
+
</Show>
392
+
</div>
393
+
</div>
338
394
</Show>
339
-
<TextInput
340
-
name="Filter"
341
-
placeholder="Filter by substring"
342
-
onInput={(e) => setFilter(e.currentTarget.value)}
343
-
class="grow"
344
-
/>
345
-
<Tooltip text="Jetstream">
346
-
<A
347
-
href={`/jetstream?collections=${params.collection}&dids=${params.repo}`}
348
-
class="flex items-center rounded-md p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
349
-
>
350
-
<span class="iconify lucide--radio-tower"></span>
351
-
</A>
352
-
</Tooltip>
353
395
</div>
354
-
<Show when={records.length > 1}>
355
-
<div class="flex items-center justify-between gap-x-2">
356
-
<Button
357
-
onClick={() => {
358
-
setReverse(!reverse());
359
-
setCursor(undefined);
360
-
clearCollectionCache(cacheKey());
361
-
refetch();
362
-
}}
363
-
classList={{
364
-
"text-blue-500! dark:text-blue-400! border-blue-500! dark:border-blue-400!":
365
-
reverse(),
366
-
}}
367
-
>
368
-
<span
369
-
class={`iconify ${reverse() ? "lucide--arrow-down-wide-narrow" : "lucide--arrow-up-narrow-wide"}`}
370
-
></span>
371
-
Reverse
372
-
</Button>
373
-
<div>
374
-
<Show when={batchDelete()}>
375
-
<span>{records.filter((rec) => rec.toDelete).length}</span>
376
-
<span>/</span>
377
-
</Show>
378
-
<span>{filter() ? filteredRecords().length : records.length} records</span>
379
-
</div>
380
-
<div class="flex w-20 items-center justify-end">
381
-
<Show when={cursor()}>
382
-
<Show when={!response.loading}>
383
-
<Button onClick={() => refetch()}>Load More</Button>
396
+
</StickyOverlay>
397
+
<div class="flex max-w-full flex-col px-2 font-mono">
398
+
<For each={filteredRecords()}>
399
+
{(record, index) => {
400
+
const rounding = () => {
401
+
const recs = filteredRecords();
402
+
const prevSelected = recs[index() - 1]?.toDelete;
403
+
const nextSelected = recs[index() + 1]?.toDelete;
404
+
return `${!prevSelected ? "rounded-t" : ""} ${!nextSelected ? "rounded-b" : ""}`;
405
+
};
406
+
return (
407
+
<>
408
+
<Show when={batchDelete()}>
409
+
<div
410
+
class={`select-none ${
411
+
record.toDelete ?
412
+
`bg-blue-200 hover:bg-blue-300/80 active:bg-blue-300 dark:bg-blue-700/30 dark:hover:bg-blue-700/50 dark:active:bg-blue-700/70 ${rounding()}`
413
+
: "rounded hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
414
+
}`}
415
+
onclick={(e) => {
416
+
handleSelectionClick(e, index());
417
+
setRecords(index(), "toDelete", !record.toDelete);
418
+
}}
419
+
>
420
+
<RecordLink record={record} />
421
+
</div>
384
422
</Show>
385
-
<Show when={response.loading}>
386
-
<div class="iconify lucide--loader-circle w-20 animate-spin text-xl" />
423
+
<Show when={!batchDelete()}>
424
+
<A
425
+
href={`/at://${did}/${params.collection}/${record.rkey}`}
426
+
class="rounded select-none hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
427
+
>
428
+
<RecordLink record={record} />
429
+
</A>
387
430
</Show>
388
-
</Show>
389
-
</div>
390
-
</div>
391
-
</Show>
431
+
</>
432
+
);
433
+
}}
434
+
</For>
392
435
</div>
393
-
</StickyOverlay>
394
-
<div class="flex max-w-full flex-col px-2 font-mono">
395
-
<For each={filteredRecords()}>
396
-
{(record, index) => {
397
-
const rounding = () => {
398
-
const recs = filteredRecords();
399
-
const prevSelected = recs[index() - 1]?.toDelete;
400
-
const nextSelected = recs[index() + 1]?.toDelete;
401
-
return `${!prevSelected ? "rounded-t" : ""} ${!nextSelected ? "rounded-b" : ""}`;
402
-
};
403
-
return (
404
-
<>
405
-
<Show when={batchDelete()}>
406
-
<div
407
-
class={`select-none ${
408
-
record.toDelete ?
409
-
`bg-blue-200 hover:bg-blue-300/80 active:bg-blue-300 dark:bg-blue-700/30 dark:hover:bg-blue-700/50 dark:active:bg-blue-700/70 ${rounding()}`
410
-
: "rounded hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
411
-
}`}
412
-
onclick={(e) => {
413
-
handleSelectionClick(e, index());
414
-
setRecords(index(), "toDelete", !record.toDelete);
415
-
}}
416
-
>
417
-
<RecordLink record={record} />
418
-
</div>
419
-
</Show>
420
-
<Show when={!batchDelete()}>
421
-
<A
422
-
href={`/at://${did}/${params.collection}/${record.rkey}`}
423
-
class="rounded select-none hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
424
-
>
425
-
<RecordLink record={record} />
426
-
</A>
427
-
</Show>
428
-
</>
429
-
);
430
-
}}
431
-
</For>
432
436
</div>
433
-
</div>
434
-
</Show>
437
+
</Show>
438
+
</>
435
439
);
436
440
};
437
441
+114
-110
src/views/labels.tsx
+114
-110
src/views/labels.tsx
···
2
2
import { Client, simpleFetchHandler } from "@atcute/client";
3
3
import { isAtprotoDid } from "@atcute/identity";
4
4
import { Handle } from "@atcute/lexicons";
5
+
import { Title } from "@solidjs/meta";
5
6
import { A, useSearchParams } from "@solidjs/router";
6
7
import { createMemo, createSignal, For, onMount, Show } from "solid-js";
7
8
import { Button } from "../components/button.jsx";
···
194
195
};
195
196
196
197
return (
197
-
<div class="flex w-full flex-col items-center">
198
-
<form
199
-
ref={formRef}
200
-
class="flex w-full max-w-3xl flex-col gap-y-2 px-3 pb-2"
201
-
onSubmit={(e) => {
202
-
e.preventDefault();
203
-
handleSearch();
204
-
}}
205
-
>
206
-
<div class="flex flex-col gap-y-1.5">
207
-
<label class="flex w-full flex-col gap-y-1">
208
-
<span class="text-sm font-medium text-neutral-700 dark:text-neutral-300">
209
-
Labeler DID/Handle
210
-
</span>
211
-
<TextInput
212
-
name="did"
213
-
value={didInput()}
214
-
onInput={(e) => setDidInput(e.currentTarget.value)}
215
-
placeholder="did:plc:..."
216
-
class="w-full"
217
-
/>
218
-
</label>
219
-
220
-
<label class="flex w-full flex-col gap-y-1">
221
-
<span class="text-sm font-medium text-neutral-700 dark:text-neutral-300">
222
-
URI Patterns (comma-separated)
223
-
</span>
224
-
<textarea
225
-
id="uriPatterns"
226
-
name="uriPatterns"
227
-
spellcheck={false}
228
-
rows={2}
229
-
value={searchParams.uriPatterns ?? "*"}
230
-
placeholder="at://did:web:example.com/app.bsky.feed.post/*"
231
-
class="dark:bg-dark-100 grow rounded-lg bg-white px-2 py-1.5 text-sm outline-1 outline-neutral-200 focus:outline-[1.5px] focus:outline-neutral-600 dark:outline-neutral-600 dark:focus:outline-neutral-400"
232
-
/>
233
-
</label>
234
-
</div>
235
-
236
-
<Button
237
-
type="submit"
238
-
disabled={loading()}
239
-
class="dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 box-border flex h-7 w-fit items-center justify-center gap-1 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 px-2 py-1.5 text-xs shadow-xs select-none hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800"
198
+
<>
199
+
<Title>Labels - PDSls</Title>
200
+
<div class="flex w-full flex-col items-center">
201
+
<form
202
+
ref={formRef}
203
+
class="flex w-full max-w-3xl flex-col gap-y-2 px-3 pb-2"
204
+
onSubmit={(e) => {
205
+
e.preventDefault();
206
+
handleSearch();
207
+
}}
240
208
>
241
-
<span class="iconify lucide--search" />
242
-
<span>Search Labels</span>
243
-
</Button>
209
+
<div class="flex flex-col gap-y-1.5">
210
+
<label class="flex w-full flex-col gap-y-1">
211
+
<span class="text-sm font-medium text-neutral-700 dark:text-neutral-300">
212
+
Labeler DID/Handle
213
+
</span>
214
+
<TextInput
215
+
name="did"
216
+
value={didInput()}
217
+
onInput={(e) => setDidInput(e.currentTarget.value)}
218
+
placeholder="did:plc:..."
219
+
class="w-full"
220
+
/>
221
+
</label>
244
222
245
-
<Show when={error()}>
246
-
<div class="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800 dark:border-red-800 dark:bg-red-900/20 dark:text-red-300">
247
-
{error()}
223
+
<label class="flex w-full flex-col gap-y-1">
224
+
<span class="text-sm font-medium text-neutral-700 dark:text-neutral-300">
225
+
URI Patterns (comma-separated)
226
+
</span>
227
+
<textarea
228
+
id="uriPatterns"
229
+
name="uriPatterns"
230
+
spellcheck={false}
231
+
rows={2}
232
+
value={searchParams.uriPatterns ?? "*"}
233
+
placeholder="at://did:web:example.com/app.bsky.feed.post/*"
234
+
class="dark:bg-dark-100 grow rounded-lg bg-white px-2 py-1.5 text-sm outline-1 outline-neutral-200 focus:outline-[1.5px] focus:outline-neutral-600 dark:outline-neutral-600 dark:focus:outline-neutral-400"
235
+
/>
236
+
</label>
248
237
</div>
249
-
</Show>
250
-
</form>
251
238
252
-
<Show when={hasSearched()}>
253
-
<StickyOverlay>
254
-
<div class="flex w-full items-center gap-x-2">
255
-
<TextInput
256
-
placeholder="Filter labels (* for partial, -exclude)"
257
-
name="filter"
258
-
value={filter()}
259
-
onInput={(e) => setFilter(e.currentTarget.value)}
260
-
class="min-w-0 grow text-sm placeholder:text-xs"
261
-
/>
262
-
<div class="flex shrink-0 items-center gap-x-2 text-sm">
263
-
<Show when={labels().length > 0}>
264
-
<span class="whitespace-nowrap text-neutral-600 dark:text-neutral-400">
265
-
{filteredLabels().length}/{labels().length}
266
-
</span>
267
-
</Show>
239
+
<Button
240
+
type="submit"
241
+
disabled={loading()}
242
+
class="dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 box-border flex h-7 w-fit items-center justify-center gap-1 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 px-2 py-1.5 text-xs shadow-xs select-none hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800"
243
+
>
244
+
<span class="iconify lucide--search" />
245
+
<span>Search Labels</span>
246
+
</Button>
268
247
269
-
<Show when={cursor()}>
270
-
<Button
271
-
onClick={handleLoadMore}
272
-
disabled={loading()}
273
-
class="dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 box-border flex h-7 w-20 items-center justify-center gap-1 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 px-2 py-1.5 text-xs shadow-xs select-none hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800"
274
-
>
275
-
<Show
276
-
when={!loading()}
277
-
fallback={<span class="iconify lucide--loader-circle animate-spin" />}
278
-
>
279
-
Load More
280
-
</Show>
281
-
</Button>
282
-
</Show>
248
+
<Show when={error()}>
249
+
<div class="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800 dark:border-red-800 dark:bg-red-900/20 dark:text-red-300">
250
+
{error()}
283
251
</div>
284
-
</div>
285
-
</StickyOverlay>
252
+
</Show>
253
+
</form>
286
254
287
-
<div class="w-full max-w-3xl px-3 py-2">
288
-
<Show when={loading() && labels().length === 0}>
289
-
<div class="flex flex-col items-center justify-center py-12 text-center">
290
-
<span class="iconify lucide--loader-circle mb-3 animate-spin text-4xl text-neutral-400" />
291
-
<p class="text-sm text-neutral-600 dark:text-neutral-400">Loading labels...</p>
255
+
<Show when={hasSearched()}>
256
+
<StickyOverlay>
257
+
<div class="flex w-full items-center gap-x-2">
258
+
<TextInput
259
+
placeholder="Filter labels (* for partial, -exclude)"
260
+
name="filter"
261
+
value={filter()}
262
+
onInput={(e) => setFilter(e.currentTarget.value)}
263
+
class="min-w-0 grow text-sm placeholder:text-xs"
264
+
/>
265
+
<div class="flex shrink-0 items-center gap-x-2 text-sm">
266
+
<Show when={labels().length > 0}>
267
+
<span class="whitespace-nowrap text-neutral-600 dark:text-neutral-400">
268
+
{filteredLabels().length}/{labels().length}
269
+
</span>
270
+
</Show>
271
+
272
+
<Show when={cursor()}>
273
+
<Button
274
+
onClick={handleLoadMore}
275
+
disabled={loading()}
276
+
class="dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 box-border flex h-7 w-20 items-center justify-center gap-1 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 px-2 py-1.5 text-xs shadow-xs select-none hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800"
277
+
>
278
+
<Show
279
+
when={!loading()}
280
+
fallback={<span class="iconify lucide--loader-circle animate-spin" />}
281
+
>
282
+
Load More
283
+
</Show>
284
+
</Button>
285
+
</Show>
286
+
</div>
292
287
</div>
293
-
</Show>
288
+
</StickyOverlay>
294
289
295
-
<Show when={!loading() || labels().length > 0}>
296
-
<Show when={filteredLabels().length > 0}>
297
-
<div class="grid gap-2">
298
-
<For each={filteredLabels()}>{(label) => <LabelCard label={label} />}</For>
290
+
<div class="w-full max-w-3xl px-3 py-2">
291
+
<Show when={loading() && labels().length === 0}>
292
+
<div class="flex flex-col items-center justify-center py-12 text-center">
293
+
<span class="iconify lucide--loader-circle mb-3 animate-spin text-4xl text-neutral-400" />
294
+
<p class="text-sm text-neutral-600 dark:text-neutral-400">Loading labels...</p>
299
295
</div>
300
296
</Show>
301
297
302
-
<Show when={labels().length > 0 && filteredLabels().length === 0}>
303
-
<div class="flex flex-col items-center justify-center py-8 text-center">
304
-
<span class="iconify lucide--search-x mb-2 text-3xl text-neutral-400" />
305
-
<p class="text-sm text-neutral-600 dark:text-neutral-400">
306
-
No labels match your filter
307
-
</p>
308
-
</div>
309
-
</Show>
298
+
<Show when={!loading() || labels().length > 0}>
299
+
<Show when={filteredLabels().length > 0}>
300
+
<div class="grid gap-2">
301
+
<For each={filteredLabels()}>{(label) => <LabelCard label={label} />}</For>
302
+
</div>
303
+
</Show>
304
+
305
+
<Show when={labels().length > 0 && filteredLabels().length === 0}>
306
+
<div class="flex flex-col items-center justify-center py-8 text-center">
307
+
<span class="iconify lucide--search-x mb-2 text-3xl text-neutral-400" />
308
+
<p class="text-sm text-neutral-600 dark:text-neutral-400">
309
+
No labels match your filter
310
+
</p>
311
+
</div>
312
+
</Show>
310
313
311
-
<Show when={labels().length === 0 && !loading()}>
312
-
<div class="flex flex-col items-center justify-center py-8 text-center">
313
-
<span class="iconify lucide--tags mb-2 text-3xl text-neutral-400" />
314
-
<p class="text-sm text-neutral-600 dark:text-neutral-400">No labels found</p>
315
-
</div>
314
+
<Show when={labels().length === 0 && !loading()}>
315
+
<div class="flex flex-col items-center justify-center py-8 text-center">
316
+
<span class="iconify lucide--tags mb-2 text-3xl text-neutral-400" />
317
+
<p class="text-sm text-neutral-600 dark:text-neutral-400">No labels found</p>
318
+
</div>
319
+
</Show>
316
320
</Show>
317
-
</Show>
318
-
</div>
319
-
</Show>
320
-
</div>
321
+
</div>
322
+
</Show>
323
+
</div>
324
+
</>
321
325
);
322
326
};
+105
-98
src/views/pds.tsx
+105
-98
src/views/pds.tsx
···
2
2
import { Client, simpleFetchHandler } from "@atcute/client";
3
3
import { InferXRPCBodyOutput } from "@atcute/lexicons";
4
4
import * as TID from "@atcute/tid";
5
+
import { Title } from "@solidjs/meta";
5
6
import { A, useLocation, useParams } from "@solidjs/router";
6
7
import { createResource, createSignal, For, Show } from "solid-js";
7
8
import { Button } from "../components/button";
···
155
156
);
156
157
157
158
return (
158
-
<Show when={repos() || response()}>
159
-
<div class="flex w-full flex-col px-2">
160
-
<div class="mb-3 flex gap-4 text-sm sm:text-base">
161
-
<Tab tab="repos" label="Repositories" />
162
-
<Tab tab="info" label="Info" />
163
-
<Tab tab="firehose" label="Firehose" />
164
-
</div>
165
-
<Show when={!location.hash || location.hash === "#repos"}>
166
-
<div class="flex flex-col divide-y-[0.5px] divide-neutral-300 pb-20 dark:divide-neutral-700">
167
-
<For each={repos()}>{(repo) => <RepoCard {...repo} />}</For>
159
+
<>
160
+
<Title>{params.pds} - PDSls</Title>
161
+
<Show when={repos() || response()}>
162
+
<div class="flex w-full flex-col px-2">
163
+
<div class="mb-3 flex gap-4 text-sm sm:text-base">
164
+
<Tab tab="repos" label="Repositories" />
165
+
<Tab tab="info" label="Info" />
166
+
<Tab tab="firehose" label="Firehose" />
168
167
</div>
169
-
</Show>
170
-
<div class="flex flex-col gap-2">
171
-
<Show when={location.hash === "#info"}>
172
-
<Show when={version()}>
173
-
{(version) => (
174
-
<div class="flex flex-col">
175
-
<span class="font-semibold">Version</span>
176
-
<span class="text-sm text-neutral-700 dark:text-neutral-300">{version()}</span>
177
-
</div>
178
-
)}
179
-
</Show>
180
-
<Show when={serverInfos()}>
181
-
{(server) => (
182
-
<>
168
+
<Show when={!location.hash || location.hash === "#repos"}>
169
+
<div class="flex flex-col divide-y-[0.5px] divide-neutral-300 pb-20 dark:divide-neutral-700">
170
+
<For each={repos()}>{(repo) => <RepoCard {...repo} />}</For>
171
+
</div>
172
+
</Show>
173
+
<div class="flex flex-col gap-2">
174
+
<Show when={location.hash === "#info"}>
175
+
<Show when={version()}>
176
+
{(version) => (
183
177
<div class="flex flex-col">
184
-
<span class="font-semibold">DID</span>
185
-
<span class="text-sm">{server().did}</span>
178
+
<span class="font-semibold">Version</span>
179
+
<span class="text-sm text-neutral-700 dark:text-neutral-300">{version()}</span>
186
180
</div>
187
-
<div class="flex items-center gap-1">
188
-
<span class="font-semibold">Invite Code Required</span>
189
-
<span
190
-
classList={{
191
-
"iconify lucide--check text-green-500 dark:text-green-400":
192
-
server().inviteCodeRequired === true,
193
-
"iconify lucide--x text-red-500 dark:text-red-400":
194
-
!server().inviteCodeRequired,
195
-
}}
196
-
></span>
197
-
</div>
198
-
<Show when={server().phoneVerificationRequired}>
199
-
<div class="flex items-center gap-1">
200
-
<span class="font-semibold">Phone Verification Required</span>
201
-
<span class="iconify lucide--check text-green-500 dark:text-green-400"></span>
202
-
</div>
203
-
</Show>
204
-
<Show when={server().availableUserDomains.length}>
205
-
<div class="flex flex-col">
206
-
<span class="font-semibold">Available User Domains</span>
207
-
<For each={server().availableUserDomains}>
208
-
{(domain) => <span class="text-sm wrap-anywhere">{domain}</span>}
209
-
</For>
210
-
</div>
211
-
</Show>
212
-
<Show when={server().links?.privacyPolicy}>
181
+
)}
182
+
</Show>
183
+
<Show when={serverInfos()}>
184
+
{(server) => (
185
+
<>
213
186
<div class="flex flex-col">
214
-
<span class="font-semibold">Privacy Policy</span>
215
-
<a
216
-
href={server().links?.privacyPolicy}
217
-
class="text-sm hover:underline"
218
-
target="_blank"
219
-
rel="noopener"
220
-
>
221
-
{server().links?.privacyPolicy}
222
-
</a>
187
+
<span class="font-semibold">DID</span>
188
+
<span class="text-sm">{server().did}</span>
223
189
</div>
224
-
</Show>
225
-
<Show when={server().links?.termsOfService}>
226
-
<div class="flex flex-col">
227
-
<span class="font-semibold">Terms of Service</span>
228
-
<a
229
-
href={server().links?.termsOfService}
230
-
class="text-sm hover:underline"
231
-
target="_blank"
232
-
rel="noopener"
233
-
>
234
-
{server().links?.termsOfService}
235
-
</a>
236
-
</div>
237
-
</Show>
238
-
<Show when={server().contact?.email}>
239
-
<div class="flex flex-col">
240
-
<span class="font-semibold">Contact</span>
241
-
<a href={`mailto:${server().contact?.email}`} class="text-sm hover:underline">
242
-
{server().contact?.email}
243
-
</a>
190
+
<div class="flex items-center gap-1">
191
+
<span class="font-semibold">Invite Code Required</span>
192
+
<span
193
+
classList={{
194
+
"iconify lucide--check text-green-500 dark:text-green-400":
195
+
server().inviteCodeRequired === true,
196
+
"iconify lucide--x text-red-500 dark:text-red-400":
197
+
!server().inviteCodeRequired,
198
+
}}
199
+
></span>
244
200
</div>
245
-
</Show>
246
-
</>
247
-
)}
248
-
</Show>
249
-
</Show>
250
-
</div>
251
-
</div>
252
-
<Show when={!location.hash || location.hash === "#repos"}>
253
-
<div class="dark:bg-dark-500 fixed bottom-0 z-5 flex w-screen justify-center bg-neutral-100 pt-2 pb-4">
254
-
<div class="flex flex-col items-center gap-1 pb-2">
255
-
<p>{repos()?.length} loaded</p>
256
-
<Show when={!response.loading && cursor()}>
257
-
<Button onClick={() => refetch()}>Load More</Button>
258
-
</Show>
259
-
<Show when={response.loading}>
260
-
<span class="iconify lucide--loader-circle animate-spin py-3.5 text-xl"></span>
201
+
<Show when={server().phoneVerificationRequired}>
202
+
<div class="flex items-center gap-1">
203
+
<span class="font-semibold">Phone Verification Required</span>
204
+
<span class="iconify lucide--check text-green-500 dark:text-green-400"></span>
205
+
</div>
206
+
</Show>
207
+
<Show when={server().availableUserDomains.length}>
208
+
<div class="flex flex-col">
209
+
<span class="font-semibold">Available User Domains</span>
210
+
<For each={server().availableUserDomains}>
211
+
{(domain) => <span class="text-sm wrap-anywhere">{domain}</span>}
212
+
</For>
213
+
</div>
214
+
</Show>
215
+
<Show when={server().links?.privacyPolicy}>
216
+
<div class="flex flex-col">
217
+
<span class="font-semibold">Privacy Policy</span>
218
+
<a
219
+
href={server().links?.privacyPolicy}
220
+
class="text-sm hover:underline"
221
+
target="_blank"
222
+
rel="noopener"
223
+
>
224
+
{server().links?.privacyPolicy}
225
+
</a>
226
+
</div>
227
+
</Show>
228
+
<Show when={server().links?.termsOfService}>
229
+
<div class="flex flex-col">
230
+
<span class="font-semibold">Terms of Service</span>
231
+
<a
232
+
href={server().links?.termsOfService}
233
+
class="text-sm hover:underline"
234
+
target="_blank"
235
+
rel="noopener"
236
+
>
237
+
{server().links?.termsOfService}
238
+
</a>
239
+
</div>
240
+
</Show>
241
+
<Show when={server().contact?.email}>
242
+
<div class="flex flex-col">
243
+
<span class="font-semibold">Contact</span>
244
+
<a
245
+
href={`mailto:${server().contact?.email}`}
246
+
class="text-sm hover:underline"
247
+
>
248
+
{server().contact?.email}
249
+
</a>
250
+
</div>
251
+
</Show>
252
+
</>
253
+
)}
254
+
</Show>
261
255
</Show>
262
256
</div>
263
257
</div>
258
+
<Show when={!location.hash || location.hash === "#repos"}>
259
+
<div class="dark:bg-dark-500 fixed bottom-0 z-5 flex w-screen justify-center bg-neutral-100 pt-2 pb-4">
260
+
<div class="flex flex-col items-center gap-1 pb-2">
261
+
<p>{repos()?.length} loaded</p>
262
+
<Show when={!response.loading && cursor()}>
263
+
<Button onClick={() => refetch()}>Load More</Button>
264
+
</Show>
265
+
<Show when={response.loading}>
266
+
<span class="iconify lucide--loader-circle animate-spin py-3.5 text-xl"></span>
267
+
</Show>
268
+
</div>
269
+
</div>
270
+
</Show>
264
271
</Show>
265
-
</Show>
272
+
</>
266
273
);
267
274
};
268
275
+176
-170
src/views/record.tsx
+176
-170
src/views/record.tsx
···
6
6
import { ActorIdentifier, is, Nsid } from "@atcute/lexicons";
7
7
import { AtprotoDid, Did, isNsid } from "@atcute/lexicons/syntax";
8
8
import { verifyRecord } from "@atcute/repo";
9
+
import { Title } from "@solidjs/meta";
9
10
import { A, useLocation, useNavigate, useParams } from "@solidjs/router";
10
11
import { createResource, createSignal, ErrorBoundary, Show, Suspense } from "solid-js";
11
12
import { hasUserScope } from "../auth/scope-utils";
···
379
380
};
380
381
381
382
return (
382
-
<Show when={record()} keyed>
383
-
<div class="flex w-full flex-col items-center">
384
-
<div class="mb-3 flex w-full justify-between px-2 text-sm sm:text-base">
385
-
<div class="flex items-center gap-4">
386
-
<RecordTab tab="record" label="Record" />
387
-
<RecordTab tab="schema" label="Schema" />
388
-
<RecordTab tab="backlinks" label="Backlinks" />
389
-
<RecordTab tab="info" label="Info" error />
390
-
</div>
391
-
<div class="flex gap-0.5">
392
-
<Show when={agent() && agent()?.sub === record()?.uri.split("/")[2]}>
393
-
<Show when={hasUserScope("update")}>
394
-
<RecordEditor create={false} record={record()?.value} refetch={refetch} />
395
-
</Show>
396
-
<Show when={hasUserScope("delete")}>
397
-
<Tooltip text="Delete">
398
-
<button
399
-
class="flex items-center rounded-sm p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
400
-
onclick={() => setOpenDelete(true)}
401
-
>
402
-
<span class="iconify lucide--trash-2"></span>
403
-
</button>
404
-
</Tooltip>
405
-
<Modal open={openDelete()} onClose={() => setOpenDelete(false)}>
406
-
<div class="dark:bg-dark-300 dark:shadow-dark-700 absolute top-70 left-[50%] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0">
407
-
<h2 class="mb-2 font-semibold">Delete this record?</h2>
408
-
<div class="flex justify-end gap-2">
409
-
<Button onClick={() => setOpenDelete(false)}>Cancel</Button>
410
-
<Button
411
-
onClick={deleteRecord}
412
-
class="dark:shadow-dark-700 rounded-lg bg-red-500 px-2 py-1.5 text-xs text-white shadow-xs select-none hover:bg-red-400 active:bg-red-400"
413
-
>
414
-
Delete
415
-
</Button>
383
+
<>
384
+
<Title>
385
+
{params.collection}/{params.rkey} - PDSls
386
+
</Title>
387
+
<Show when={record()} keyed>
388
+
<div class="flex w-full flex-col items-center">
389
+
<div class="mb-3 flex w-full justify-between px-2 text-sm sm:text-base">
390
+
<div class="flex items-center gap-4">
391
+
<RecordTab tab="record" label="Record" />
392
+
<RecordTab tab="schema" label="Schema" />
393
+
<RecordTab tab="backlinks" label="Backlinks" />
394
+
<RecordTab tab="info" label="Info" error />
395
+
</div>
396
+
<div class="flex gap-0.5">
397
+
<Show when={agent() && agent()?.sub === record()?.uri.split("/")[2]}>
398
+
<Show when={hasUserScope("update")}>
399
+
<RecordEditor create={false} record={record()?.value} refetch={refetch} />
400
+
</Show>
401
+
<Show when={hasUserScope("delete")}>
402
+
<Tooltip text="Delete">
403
+
<button
404
+
class="flex items-center rounded-sm p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
405
+
onclick={() => setOpenDelete(true)}
406
+
>
407
+
<span class="iconify lucide--trash-2"></span>
408
+
</button>
409
+
</Tooltip>
410
+
<Modal open={openDelete()} onClose={() => setOpenDelete(false)}>
411
+
<div class="dark:bg-dark-300 dark:shadow-dark-700 absolute top-70 left-[50%] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0">
412
+
<h2 class="mb-2 font-semibold">Delete this record?</h2>
413
+
<div class="flex justify-end gap-2">
414
+
<Button onClick={() => setOpenDelete(false)}>Cancel</Button>
415
+
<Button
416
+
onClick={deleteRecord}
417
+
class="dark:shadow-dark-700 rounded-lg bg-red-500 px-2 py-1.5 text-xs text-white shadow-xs select-none hover:bg-red-400 active:bg-red-400"
418
+
>
419
+
Delete
420
+
</Button>
421
+
</div>
416
422
</div>
417
-
</div>
418
-
</Modal>
423
+
</Modal>
424
+
</Show>
419
425
</Show>
420
-
</Show>
421
-
<MenuProvider>
422
-
<DropdownMenu icon="lucide--ellipsis" buttonClass="rounded-sm p-1.5">
423
-
<CopyMenu
424
-
content={JSON.stringify(record()?.value, null, 2)}
425
-
label="Copy record"
426
-
icon="lucide--copy"
427
-
/>
428
-
<CopyMenu
429
-
content={`at://${params.repo}/${params.collection}/${params.rkey}`}
430
-
label="Copy AT URI"
431
-
icon="lucide--copy"
432
-
/>
433
-
<Show when={record()?.cid}>
434
-
{(cid) => <CopyMenu content={cid()} label="Copy CID" icon="lucide--copy" />}
435
-
</Show>
436
-
<MenuSeparator />
437
-
<Show when={externalLink()}>
438
-
{(externalLink) => (
439
-
<NavMenu
440
-
href={externalLink()?.link}
441
-
icon={`${externalLink().icon ?? "lucide--app-window"}`}
442
-
label={`Open on ${externalLink().label}`}
443
-
newTab
444
-
/>
445
-
)}
446
-
</Show>
447
-
<NavMenu
448
-
href={`https://${pds()}/xrpc/com.atproto.repo.getRecord?repo=${params.repo}&collection=${params.collection}&rkey=${params.rkey}`}
449
-
icon="lucide--external-link"
450
-
label="Record on PDS"
451
-
newTab
452
-
/>
453
-
</DropdownMenu>
454
-
</MenuProvider>
455
-
</div>
456
-
</div>
457
-
<Show when={!location.hash || location.hash === "#record"}>
458
-
<div class="w-max max-w-screen min-w-full px-4 font-mono text-xs wrap-anywhere whitespace-pre-wrap sm:px-2 sm:text-sm md:max-w-3xl">
459
-
<JSONValue data={record()?.value as any} repo={record()!.uri.split("/")[2]} />
426
+
<MenuProvider>
427
+
<DropdownMenu icon="lucide--ellipsis" buttonClass="rounded-sm p-1.5">
428
+
<CopyMenu
429
+
content={JSON.stringify(record()?.value, null, 2)}
430
+
label="Copy record"
431
+
icon="lucide--copy"
432
+
/>
433
+
<CopyMenu
434
+
content={`at://${params.repo}/${params.collection}/${params.rkey}`}
435
+
label="Copy AT URI"
436
+
icon="lucide--copy"
437
+
/>
438
+
<Show when={record()?.cid}>
439
+
{(cid) => <CopyMenu content={cid()} label="Copy CID" icon="lucide--copy" />}
440
+
</Show>
441
+
<MenuSeparator />
442
+
<Show when={externalLink()}>
443
+
{(externalLink) => (
444
+
<NavMenu
445
+
href={externalLink()?.link}
446
+
icon={`${externalLink().icon ?? "lucide--app-window"}`}
447
+
label={`Open on ${externalLink().label}`}
448
+
newTab
449
+
/>
450
+
)}
451
+
</Show>
452
+
<NavMenu
453
+
href={`https://${pds()}/xrpc/com.atproto.repo.getRecord?repo=${params.repo}&collection=${params.collection}&rkey=${params.rkey}`}
454
+
icon="lucide--external-link"
455
+
label="Record on PDS"
456
+
newTab
457
+
/>
458
+
</DropdownMenu>
459
+
</MenuProvider>
460
+
</div>
460
461
</div>
461
-
</Show>
462
-
<Show when={location.hash === "#schema" || location.hash.startsWith("#schema:")}>
463
-
<Show when={lexiconNotFound() === true}>
464
-
<span class="w-full px-2 text-sm">Lexicon schema could not be resolved.</span>
462
+
<Show when={!location.hash || location.hash === "#record"}>
463
+
<div class="w-max max-w-screen min-w-full px-4 font-mono text-xs wrap-anywhere whitespace-pre-wrap sm:px-2 sm:text-sm md:max-w-3xl">
464
+
<JSONValue data={record()?.value as any} repo={record()!.uri.split("/")[2]} />
465
+
</div>
465
466
</Show>
466
-
<Show when={lexiconNotFound() === undefined}>
467
-
<span class="w-full px-2 text-sm">Resolving lexicon schema...</span>
467
+
<Show when={location.hash === "#schema" || location.hash.startsWith("#schema:")}>
468
+
<Show when={lexiconNotFound() === true}>
469
+
<span class="w-full px-2 text-sm">Lexicon schema could not be resolved.</span>
470
+
</Show>
471
+
<Show when={lexiconNotFound() === undefined}>
472
+
<span class="w-full px-2 text-sm">Resolving lexicon schema...</span>
473
+
</Show>
474
+
<Show when={schema() || params.collection === "com.atproto.lexicon.schema"}>
475
+
<ErrorBoundary fallback={(err) => <div>Error: {err.message}</div>}>
476
+
<LexiconSchemaView schema={schema()?.rawSchema ?? (record()?.value as any)} />
477
+
</ErrorBoundary>
478
+
</Show>
468
479
</Show>
469
-
<Show when={schema() || params.collection === "com.atproto.lexicon.schema"}>
470
-
<ErrorBoundary fallback={(err) => <div>Error: {err.message}</div>}>
471
-
<LexiconSchemaView schema={schema()?.rawSchema ?? (record()?.value as any)} />
480
+
<Show when={location.hash === "#backlinks"}>
481
+
<ErrorBoundary
482
+
fallback={(err) => <div class="wrap-break-word">Error: {err.message}</div>}
483
+
>
484
+
<Suspense
485
+
fallback={
486
+
<div class="iconify lucide--loader-circle animate-spin self-center text-xl" />
487
+
}
488
+
>
489
+
<div class="w-full px-2">
490
+
<Backlinks target={`at://${did}/${params.collection}/${params.rkey}`} />
491
+
</div>
492
+
</Suspense>
472
493
</ErrorBoundary>
473
494
</Show>
474
-
</Show>
475
-
<Show when={location.hash === "#backlinks"}>
476
-
<ErrorBoundary
477
-
fallback={(err) => <div class="wrap-break-word">Error: {err.message}</div>}
478
-
>
479
-
<Suspense
480
-
fallback={
481
-
<div class="iconify lucide--loader-circle animate-spin self-center text-xl" />
482
-
}
483
-
>
484
-
<div class="w-full px-2">
485
-
<Backlinks target={`at://${did}/${params.collection}/${params.rkey}`} />
486
-
</div>
487
-
</Suspense>
488
-
</ErrorBoundary>
489
-
</Show>
490
-
<Show when={location.hash === "#info"}>
491
-
<div class="flex w-full flex-col gap-2 px-2 text-sm">
492
-
<div>
493
-
<p class="font-semibold">AT URI</p>
494
-
<div class="truncate text-xs">{record()?.uri}</div>
495
-
</div>
496
-
<Show when={record()?.cid}>
495
+
<Show when={location.hash === "#info"}>
496
+
<div class="flex w-full flex-col gap-2 px-2 text-sm">
497
497
<div>
498
-
<p class="font-semibold">CID</p>
499
-
<div class="truncate text-left text-xs" dir="rtl">
500
-
{record()?.cid}
501
-
</div>
498
+
<p class="font-semibold">AT URI</p>
499
+
<div class="truncate text-xs">{record()?.uri}</div>
502
500
</div>
503
-
</Show>
504
-
<div>
505
-
<div class="flex items-center gap-1">
506
-
<p class="font-semibold">Record verification</p>
507
-
<span
508
-
classList={{
509
-
"iconify lucide--check text-green-500 dark:text-green-400":
510
-
validRecord() === true,
511
-
"iconify lucide--x text-red-500 dark:text-red-400": validRecord() === false,
512
-
"iconify lucide--loader-circle animate-spin": validRecord() === undefined,
513
-
}}
514
-
></span>
515
-
</div>
516
-
<Show when={validRecord() === false}>
517
-
<div class="text-xs wrap-break-word">{verifyError()}</div>
501
+
<Show when={record()?.cid}>
502
+
<div>
503
+
<p class="font-semibold">CID</p>
504
+
<div class="truncate text-left text-xs" dir="rtl">
505
+
{record()?.cid}
506
+
</div>
507
+
</div>
518
508
</Show>
519
-
</div>
520
-
<div>
521
-
<div class="flex items-center gap-1">
522
-
<p class="font-semibold">Schema validation</p>
523
-
<span
524
-
classList={{
525
-
"iconify lucide--check text-green-500 dark:text-green-400":
526
-
validSchema() === true,
527
-
"iconify lucide--x text-red-500 dark:text-red-400": validSchema() === false,
528
-
"iconify lucide--loader-circle animate-spin":
529
-
validSchema() === undefined && remoteValidation(),
530
-
}}
531
-
></span>
509
+
<div>
510
+
<div class="flex items-center gap-1">
511
+
<p class="font-semibold">Record verification</p>
512
+
<span
513
+
classList={{
514
+
"iconify lucide--check text-green-500 dark:text-green-400":
515
+
validRecord() === true,
516
+
"iconify lucide--x text-red-500 dark:text-red-400": validRecord() === false,
517
+
"iconify lucide--loader-circle animate-spin": validRecord() === undefined,
518
+
}}
519
+
></span>
520
+
</div>
521
+
<Show when={validRecord() === false}>
522
+
<div class="text-xs wrap-break-word">{verifyError()}</div>
523
+
</Show>
532
524
</div>
533
-
<Show when={validSchema() === false}>
534
-
<div class="text-xs wrap-break-word">{validationError()}</div>
535
-
</Show>
536
-
<Show
537
-
when={
538
-
!remoteValidation() &&
539
-
validSchema() === undefined &&
540
-
params.collection &&
541
-
!(params.collection in lexicons)
542
-
}
543
-
>
544
-
<Button onClick={() => validateRemoteSchema(record()!.value)}>
545
-
Validate via resolution
546
-
</Button>
547
-
</Show>
548
-
</div>
549
-
<Show when={lexiconUri()}>
550
525
<div>
551
-
<p class="font-semibold">Lexicon schema</p>
552
-
<div class="truncate text-xs">
553
-
<A
554
-
href={`/${lexiconUri()}`}
555
-
class="text-blue-400 hover:underline active:underline"
556
-
>
557
-
{lexiconUri()}
558
-
</A>
526
+
<div class="flex items-center gap-1">
527
+
<p class="font-semibold">Schema validation</p>
528
+
<span
529
+
classList={{
530
+
"iconify lucide--check text-green-500 dark:text-green-400":
531
+
validSchema() === true,
532
+
"iconify lucide--x text-red-500 dark:text-red-400": validSchema() === false,
533
+
"iconify lucide--loader-circle animate-spin":
534
+
validSchema() === undefined && remoteValidation(),
535
+
}}
536
+
></span>
559
537
</div>
538
+
<Show when={validSchema() === false}>
539
+
<div class="text-xs wrap-break-word">{validationError()}</div>
540
+
</Show>
541
+
<Show
542
+
when={
543
+
!remoteValidation() &&
544
+
validSchema() === undefined &&
545
+
params.collection &&
546
+
!(params.collection in lexicons)
547
+
}
548
+
>
549
+
<Button onClick={() => validateRemoteSchema(record()!.value)}>
550
+
Validate via resolution
551
+
</Button>
552
+
</Show>
560
553
</div>
561
-
</Show>
562
-
</div>
563
-
</Show>
564
-
</div>
565
-
</Show>
554
+
<Show when={lexiconUri()}>
555
+
<div>
556
+
<p class="font-semibold">Lexicon schema</p>
557
+
<div class="truncate text-xs">
558
+
<A
559
+
href={`/${lexiconUri()}`}
560
+
class="text-blue-400 hover:underline active:underline"
561
+
>
562
+
{lexiconUri()}
563
+
</A>
564
+
</div>
565
+
</div>
566
+
</Show>
567
+
</div>
568
+
</Show>
569
+
</div>
570
+
</Show>
571
+
</>
566
572
);
567
573
};
+311
-292
src/views/repo.tsx
+311
-292
src/views/repo.tsx
···
1
1
import { Client, simpleFetchHandler } from "@atcute/client";
2
2
import { DidDocument } from "@atcute/identity";
3
3
import { ActorIdentifier, Did, Handle, Nsid } from "@atcute/lexicons";
4
+
import { Title } from "@solidjs/meta";
4
5
import { A, useLocation, useNavigate, useParams } from "@solidjs/router";
5
6
import {
6
7
createEffect,
···
273
274
setDownloading(false);
274
275
};
275
276
277
+
const getTitle = () => {
278
+
const doc = didDoc();
279
+
const handle = doc?.alsoKnownAs
280
+
?.find((alias) => alias.startsWith("at://"))
281
+
?.replace("at://", "");
282
+
return `${handle || params.repo} - PDSls`;
283
+
};
284
+
276
285
return (
277
-
<Show when={repo()}>
278
-
<div class="flex w-full flex-col gap-3 wrap-break-word">
279
-
<div class="flex justify-between px-2 text-sm sm:text-base">
280
-
<div class="flex items-center gap-3 sm:gap-4">
281
-
<Show when={!error()}>
282
-
<RepoTab tab="collections" label="Collections" />
283
-
</Show>
284
-
<RepoTab tab="identity" label="Identity" />
285
-
<Show when={did.startsWith("did:plc")}>
286
-
<RepoTab tab="logs" label="Logs" />
287
-
</Show>
288
-
<Show when={!error()}>
289
-
<RepoTab tab="blobs" label="Blobs" />
290
-
</Show>
291
-
<RepoTab tab="backlinks" label="Backlinks" />
292
-
</div>
293
-
<div class="flex gap-1">
294
-
<Show when={error() && error() !== "Missing PDS"}>
295
-
<div class="flex items-center gap-1 text-red-500 dark:text-red-400">
296
-
<span class="iconify lucide--alert-triangle"></span>
297
-
<span>{error()}</span>
298
-
</div>
299
-
</Show>
300
-
<MenuProvider>
301
-
<DropdownMenu icon="lucide--ellipsis" buttonClass="rounded-sm p-1.5">
302
-
<Show
303
-
when={!error() && (!location.hash || location.hash.startsWith("#collections"))}
304
-
>
305
-
<ActionMenu
306
-
label="Filter collections"
307
-
icon="lucide--filter"
308
-
onClick={() => setShowFilter(!showFilter())}
309
-
/>
310
-
</Show>
311
-
<CopyMenu content={params.repo!} label="Copy DID" icon="lucide--copy" />
312
-
<NavMenu
313
-
href={`/jetstream?dids=${params.repo}`}
314
-
label="Jetstream"
315
-
icon="lucide--radio-tower"
316
-
/>
317
-
<Show when={params.repo && params.repo in labelerCache}>
286
+
<>
287
+
<Title>{getTitle()}</Title>
288
+
<Show when={repo()}>
289
+
<div class="flex w-full flex-col gap-3 wrap-break-word">
290
+
<div class="flex justify-between px-2 text-sm sm:text-base">
291
+
<div class="flex items-center gap-3 sm:gap-4">
292
+
<Show when={!error()}>
293
+
<RepoTab tab="collections" label="Collections" />
294
+
</Show>
295
+
<RepoTab tab="identity" label="Identity" />
296
+
<Show when={did.startsWith("did:plc")}>
297
+
<RepoTab tab="logs" label="Logs" />
298
+
</Show>
299
+
<Show when={!error()}>
300
+
<RepoTab tab="blobs" label="Blobs" />
301
+
</Show>
302
+
<RepoTab tab="backlinks" label="Backlinks" />
303
+
</div>
304
+
<div class="flex gap-1">
305
+
<Show when={error() && error() !== "Missing PDS"}>
306
+
<div class="flex items-center gap-1 text-red-500 dark:text-red-400">
307
+
<span class="iconify lucide--alert-triangle"></span>
308
+
<span>{error()}</span>
309
+
</div>
310
+
</Show>
311
+
<MenuProvider>
312
+
<DropdownMenu icon="lucide--ellipsis" buttonClass="rounded-sm p-1.5">
313
+
<Show
314
+
when={!error() && (!location.hash || location.hash.startsWith("#collections"))}
315
+
>
316
+
<ActionMenu
317
+
label="Filter collections"
318
+
icon="lucide--filter"
319
+
onClick={() => setShowFilter(!showFilter())}
320
+
/>
321
+
</Show>
322
+
<CopyMenu content={params.repo!} label="Copy DID" icon="lucide--copy" />
318
323
<NavMenu
319
-
href={`/labels?did=${params.repo}&uriPatterns=*`}
320
-
label="Labels"
321
-
icon="lucide--tag"
324
+
href={`/jetstream?dids=${params.repo}`}
325
+
label="Jetstream"
326
+
icon="lucide--radio-tower"
322
327
/>
323
-
</Show>
324
-
<Show when={error()?.length === 0 || error() === undefined}>
325
-
<ActionMenu
326
-
label="Export repo"
327
-
icon={downloading() ? "lucide--loader-circle animate-spin" : "lucide--download"}
328
-
onClick={() => downloadRepo()}
329
-
/>
330
-
</Show>
331
-
<MenuSeparator />
332
-
<NavMenu
333
-
href={
334
-
did.startsWith("did:plc") ?
335
-
`${localStorage.plcDirectory ?? "https://plc.directory"}/${did}`
336
-
: `https://${did.split("did:web:")[1]}/.well-known/did.json`
337
-
}
338
-
newTab
339
-
label="DID document"
340
-
icon="lucide--external-link"
341
-
/>
342
-
<Show when={did.startsWith("did:plc")}>
328
+
<Show when={params.repo && params.repo in labelerCache}>
329
+
<NavMenu
330
+
href={`/labels?did=${params.repo}&uriPatterns=*`}
331
+
label="Labels"
332
+
icon="lucide--tag"
333
+
/>
334
+
</Show>
335
+
<Show when={error()?.length === 0 || error() === undefined}>
336
+
<ActionMenu
337
+
label="Export repo"
338
+
icon={
339
+
downloading() ? "lucide--loader-circle animate-spin" : "lucide--download"
340
+
}
341
+
onClick={() => downloadRepo()}
342
+
/>
343
+
</Show>
344
+
<MenuSeparator />
343
345
<NavMenu
344
-
href={`${localStorage.plcDirectory ?? "https://plc.directory"}/${did}/log/audit`}
346
+
href={
347
+
did.startsWith("did:plc") ?
348
+
`${localStorage.plcDirectory ?? "https://plc.directory"}/${did}`
349
+
: `https://${did.split("did:web:")[1]}/.well-known/did.json`
350
+
}
345
351
newTab
346
-
label="Audit log"
352
+
label="DID document"
347
353
icon="lucide--external-link"
348
354
/>
349
-
</Show>
350
-
</DropdownMenu>
351
-
</MenuProvider>
355
+
<Show when={did.startsWith("did:plc")}>
356
+
<NavMenu
357
+
href={`${localStorage.plcDirectory ?? "https://plc.directory"}/${did}/log/audit`}
358
+
newTab
359
+
label="Audit log"
360
+
icon="lucide--external-link"
361
+
/>
362
+
</Show>
363
+
</DropdownMenu>
364
+
</MenuProvider>
365
+
</div>
352
366
</div>
353
-
</div>
354
-
<div class="flex w-full flex-col gap-1 px-2">
355
-
<Show when={location.hash === "#logs"}>
356
-
<ErrorBoundary
357
-
fallback={(err) => <div class="wrap-break-word">Error: {err.message}</div>}
358
-
>
359
-
<Suspense
360
-
fallback={
361
-
<div class="iconify lucide--loader-circle mt-2 animate-spin self-center text-xl" />
362
-
}
367
+
<div class="flex w-full flex-col gap-1 px-2">
368
+
<Show when={location.hash === "#logs"}>
369
+
<ErrorBoundary
370
+
fallback={(err) => <div class="wrap-break-word">Error: {err.message}</div>}
363
371
>
364
-
<PlcLogView did={did} />
365
-
</Suspense>
366
-
</ErrorBoundary>
367
-
</Show>
368
-
<Show when={location.hash === "#backlinks"}>
369
-
<ErrorBoundary
370
-
fallback={(err) => <div class="wrap-break-word">Error: {err.message}</div>}
371
-
>
372
-
<Suspense
373
-
fallback={
374
-
<div class="iconify lucide--loader-circle mt-2 animate-spin self-center text-xl" />
375
-
}
372
+
<Suspense
373
+
fallback={
374
+
<div class="iconify lucide--loader-circle mt-2 animate-spin self-center text-xl" />
375
+
}
376
+
>
377
+
<PlcLogView did={did} />
378
+
</Suspense>
379
+
</ErrorBoundary>
380
+
</Show>
381
+
<Show when={location.hash === "#backlinks"}>
382
+
<ErrorBoundary
383
+
fallback={(err) => <div class="wrap-break-word">Error: {err.message}</div>}
376
384
>
377
-
<Backlinks target={did} />
378
-
</Suspense>
379
-
</ErrorBoundary>
380
-
</Show>
381
-
<Show when={location.hash === "#blobs"}>
382
-
<ErrorBoundary
383
-
fallback={(err) => <div class="wrap-break-word">Error: {err.message}</div>}
384
-
>
385
-
<Suspense
386
-
fallback={
387
-
<div class="iconify lucide--loader-circle mt-2 animate-spin self-center text-xl" />
388
-
}
385
+
<Suspense
386
+
fallback={
387
+
<div class="iconify lucide--loader-circle mt-2 animate-spin self-center text-xl" />
388
+
}
389
+
>
390
+
<Backlinks target={did} />
391
+
</Suspense>
392
+
</ErrorBoundary>
393
+
</Show>
394
+
<Show when={location.hash === "#blobs"}>
395
+
<ErrorBoundary
396
+
fallback={(err) => <div class="wrap-break-word">Error: {err.message}</div>}
389
397
>
390
-
<BlobView pds={pds!} repo={did} />
391
-
</Suspense>
392
-
</ErrorBoundary>
393
-
</Show>
394
-
<Show when={nsids() && (!location.hash || location.hash.startsWith("#collections"))}>
395
-
<Show when={showFilter()}>
396
-
<TextInput
397
-
name="filter"
398
-
placeholder="Filter collections"
399
-
onInput={(e) => setFilter(e.currentTarget.value.toLowerCase())}
400
-
class="grow"
401
-
ref={(node) => {
402
-
onMount(() => node.focus());
403
-
}}
404
-
/>
398
+
<Suspense
399
+
fallback={
400
+
<div class="iconify lucide--loader-circle mt-2 animate-spin self-center text-xl" />
401
+
}
402
+
>
403
+
<BlobView pds={pds!} repo={did} />
404
+
</Suspense>
405
+
</ErrorBoundary>
405
406
</Show>
406
-
<div class="flex flex-col text-sm wrap-anywhere" classList={{ "-mt-1": !showFilter() }}>
407
-
<Show
408
-
when={Object.keys(nsids() ?? {}).length != 0}
409
-
fallback={<span class="mt-3 text-center text-base">No collections found.</span>}
407
+
<Show when={nsids() && (!location.hash || location.hash.startsWith("#collections"))}>
408
+
<Show when={showFilter()}>
409
+
<TextInput
410
+
name="filter"
411
+
placeholder="Filter collections"
412
+
onInput={(e) => setFilter(e.currentTarget.value.toLowerCase())}
413
+
class="grow"
414
+
ref={(node) => {
415
+
onMount(() => node.focus());
416
+
}}
417
+
/>
418
+
</Show>
419
+
<div
420
+
class="flex flex-col text-sm wrap-anywhere"
421
+
classList={{ "-mt-1": !showFilter() }}
410
422
>
411
-
<For
412
-
each={Object.keys(nsids() ?? {}).filter((authority) =>
413
-
filter() ?
414
-
authority.includes(filter()!) ||
415
-
nsids()?.[authority].nsids.some((nsid) =>
416
-
`${authority}.${nsid}`.includes(filter()!),
417
-
)
418
-
: true,
419
-
)}
423
+
<Show
424
+
when={Object.keys(nsids() ?? {}).length != 0}
425
+
fallback={<span class="mt-3 text-center text-base">No collections found.</span>}
420
426
>
421
-
{(authority) => {
422
-
const reversedDomain = authority.split(".").reverse().join(".");
423
-
const [faviconLoaded, setFaviconLoaded] = createSignal(false);
427
+
<For
428
+
each={Object.keys(nsids() ?? {}).filter((authority) =>
429
+
filter() ?
430
+
authority.includes(filter()!) ||
431
+
nsids()?.[authority].nsids.some((nsid) =>
432
+
`${authority}.${nsid}`.includes(filter()!),
433
+
)
434
+
: true,
435
+
)}
436
+
>
437
+
{(authority) => {
438
+
const reversedDomain = authority.split(".").reverse().join(".");
439
+
const [faviconLoaded, setFaviconLoaded] = createSignal(false);
424
440
425
-
const isHighlighted = () => location.hash === `#collections:${authority}`;
441
+
const isHighlighted = () => location.hash === `#collections:${authority}`;
426
442
427
-
return (
428
-
<div
429
-
id={`collection-${authority}`}
430
-
class="group flex items-start gap-2 rounded-lg p-1 transition-colors"
431
-
classList={{
432
-
"dark:hover:bg-dark-200 hover:bg-neutral-200": !isHighlighted(),
433
-
"bg-blue-100 dark:bg-blue-500/25": isHighlighted(),
434
-
}}
435
-
>
436
-
<a
437
-
href={`#collections:${authority}`}
438
-
class="relative flex h-5 w-4 shrink-0 items-center justify-center hover:opacity-70"
443
+
return (
444
+
<div
445
+
id={`collection-${authority}`}
446
+
class="group flex items-start gap-2 rounded-lg p-1 transition-colors"
447
+
classList={{
448
+
"dark:hover:bg-dark-200 hover:bg-neutral-200": !isHighlighted(),
449
+
"bg-blue-100 dark:bg-blue-500/25": isHighlighted(),
450
+
}}
439
451
>
440
-
<span class="absolute top-1/2 -left-5 flex -translate-y-1/2 items-center text-base opacity-0 transition-opacity group-hover:opacity-100">
441
-
<span class="iconify lucide--link absolute -left-2 w-7"></span>
442
-
</span>
443
-
<Show when={!faviconLoaded()}>
444
-
<span class="iconify lucide--globe size-4 text-neutral-400 dark:text-neutral-500" />
445
-
</Show>
446
-
<img
447
-
src={
448
-
["bsky.app", "bsky.chat"].includes(reversedDomain) ?
449
-
"https://web-cdn.bsky.app/static/apple-touch-icon.png"
450
-
: `https://${reversedDomain}/favicon.ico`
451
-
}
452
-
alt={`${reversedDomain} favicon`}
453
-
class="h-4 w-4"
454
-
classList={{ hidden: !faviconLoaded() }}
455
-
onLoad={() => setFaviconLoaded(true)}
456
-
onError={() => setFaviconLoaded(false)}
457
-
/>
458
-
</a>
459
-
<div class="flex flex-1 flex-col">
460
-
<For
461
-
each={nsids()?.[authority].nsids.filter((nsid) =>
462
-
filter() ? `${authority}.${nsid}`.includes(filter()!) : true,
463
-
)}
452
+
<a
453
+
href={`#collections:${authority}`}
454
+
class="relative flex h-5 w-4 shrink-0 items-center justify-center hover:opacity-70"
464
455
>
465
-
{(nsid) => (
466
-
<A
467
-
href={`/at://${did}/${authority}.${nsid}`}
468
-
class="hover:underline active:underline"
469
-
>
470
-
<span>{authority}</span>
471
-
<span class="text-neutral-500 dark:text-neutral-400">.{nsid}</span>
472
-
</A>
473
-
)}
474
-
</For>
456
+
<span class="absolute top-1/2 -left-5 flex -translate-y-1/2 items-center text-base opacity-0 transition-opacity group-hover:opacity-100">
457
+
<span class="iconify lucide--link absolute -left-2 w-7"></span>
458
+
</span>
459
+
<Show when={!faviconLoaded()}>
460
+
<span class="iconify lucide--globe size-4 text-neutral-400 dark:text-neutral-500" />
461
+
</Show>
462
+
<img
463
+
src={
464
+
["bsky.app", "bsky.chat"].includes(reversedDomain) ?
465
+
"https://web-cdn.bsky.app/static/apple-touch-icon.png"
466
+
: `https://${reversedDomain}/favicon.ico`
467
+
}
468
+
alt={`${reversedDomain} favicon`}
469
+
class="h-4 w-4"
470
+
classList={{ hidden: !faviconLoaded() }}
471
+
onLoad={() => setFaviconLoaded(true)}
472
+
onError={() => setFaviconLoaded(false)}
473
+
/>
474
+
</a>
475
+
<div class="flex flex-1 flex-col">
476
+
<For
477
+
each={nsids()?.[authority].nsids.filter((nsid) =>
478
+
filter() ? `${authority}.${nsid}`.includes(filter()!) : true,
479
+
)}
480
+
>
481
+
{(nsid) => (
482
+
<A
483
+
href={`/at://${did}/${authority}.${nsid}`}
484
+
class="hover:underline active:underline"
485
+
>
486
+
<span>{authority}</span>
487
+
<span class="text-neutral-500 dark:text-neutral-400">
488
+
.{nsid}
489
+
</span>
490
+
</A>
491
+
)}
492
+
</For>
493
+
</div>
475
494
</div>
495
+
);
496
+
}}
497
+
</For>
498
+
</Show>
499
+
</div>
500
+
</Show>
501
+
<Show when={location.hash === "#identity" || (error() && !location.hash)}>
502
+
<Show when={didDoc()}>
503
+
{(didDocument) => (
504
+
<div class="flex flex-col gap-3 wrap-anywhere">
505
+
{/* ID Section */}
506
+
<div>
507
+
<div class="font-semibold">DID</div>
508
+
<div class="text-sm text-neutral-700 dark:text-neutral-300">
509
+
{didDocument().id}
476
510
</div>
477
-
);
478
-
}}
479
-
</For>
480
-
</Show>
481
-
</div>
482
-
</Show>
483
-
<Show when={location.hash === "#identity" || (error() && !location.hash)}>
484
-
<Show when={didDoc()}>
485
-
{(didDocument) => (
486
-
<div class="flex flex-col gap-3 wrap-anywhere">
487
-
{/* ID Section */}
488
-
<div>
489
-
<div class="font-semibold">DID</div>
490
-
<div class="text-sm text-neutral-700 dark:text-neutral-300">
491
-
{didDocument().id}
492
511
</div>
493
-
</div>
494
512
495
-
{/* Aliases Section */}
496
-
<div>
497
-
<p class="font-semibold">Aliases</p>
498
-
<For each={didDocument().alsoKnownAs}>
499
-
{(alias) => (
500
-
<div class="flex items-center gap-1 text-sm text-neutral-700 dark:text-neutral-300">
501
-
<span>{alias}</span>
502
-
<Show when={alias.startsWith("at://")}>
503
-
<Tooltip
504
-
text={
505
-
validHandles[alias] === true ? "Valid handle"
506
-
: validHandles[alias] === undefined ?
507
-
"Validating"
508
-
: "Invalid handle"
509
-
}
510
-
>
511
-
<span
512
-
classList={{
513
-
"iconify lucide--check text-green-600 dark:text-green-400":
514
-
validHandles[alias] === true,
515
-
"iconify lucide--x text-red-500 dark:text-red-400":
516
-
validHandles[alias] === false,
517
-
"iconify lucide--loader-circle animate-spin":
518
-
validHandles[alias] === undefined,
519
-
}}
520
-
></span>
521
-
</Tooltip>
522
-
</Show>
523
-
</div>
524
-
)}
525
-
</For>
526
-
</div>
527
-
528
-
{/* Services Section */}
529
-
<div>
530
-
<p class="font-semibold">Services</p>
531
-
<div class="flex flex-col gap-1">
532
-
<For each={didDocument().service}>
533
-
{(service) => (
534
-
<div class="grid grid-cols-[auto_1fr] items-center gap-x-1 text-sm text-neutral-700 dark:text-neutral-300">
535
-
<span class="iconify lucide--hash"></span>
536
-
<span>{service.id.split("#")[1]}</span>
537
-
<span></span>
538
-
<a
539
-
class="w-fit underline hover:text-blue-400"
540
-
href={service.serviceEndpoint.toString()}
541
-
target="_blank"
542
-
rel="noopener"
543
-
>
544
-
{service.serviceEndpoint.toString()}
545
-
</a>
513
+
{/* Aliases Section */}
514
+
<div>
515
+
<p class="font-semibold">Aliases</p>
516
+
<For each={didDocument().alsoKnownAs}>
517
+
{(alias) => (
518
+
<div class="flex items-center gap-1 text-sm text-neutral-700 dark:text-neutral-300">
519
+
<span>{alias}</span>
520
+
<Show when={alias.startsWith("at://")}>
521
+
<Tooltip
522
+
text={
523
+
validHandles[alias] === true ? "Valid handle"
524
+
: validHandles[alias] === undefined ?
525
+
"Validating"
526
+
: "Invalid handle"
527
+
}
528
+
>
529
+
<span
530
+
classList={{
531
+
"iconify lucide--check text-green-600 dark:text-green-400":
532
+
validHandles[alias] === true,
533
+
"iconify lucide--x text-red-500 dark:text-red-400":
534
+
validHandles[alias] === false,
535
+
"iconify lucide--loader-circle animate-spin":
536
+
validHandles[alias] === undefined,
537
+
}}
538
+
></span>
539
+
</Tooltip>
540
+
</Show>
546
541
</div>
547
542
)}
548
543
</For>
549
544
</div>
550
-
</div>
551
545
552
-
{/* Verification Methods Section */}
553
-
<div>
554
-
<p class="font-semibold">Verification Methods</p>
555
-
<div class="flex flex-col gap-1">
556
-
<For each={didDocument().verificationMethod}>
557
-
{(verif) => (
558
-
<Show when={verif.publicKeyMultibase}>
559
-
{(key) => (
560
-
<div class="grid grid-cols-[auto_1fr] items-center gap-x-1 text-sm text-neutral-700 dark:text-neutral-300">
561
-
<span class="iconify lucide--hash"></span>
562
-
<div class="flex items-center gap-2">
563
-
<span>{verif.id.split("#")[1]}</span>
564
-
<div class="flex items-center gap-1 text-neutral-500 dark:text-neutral-400">
565
-
<span class="iconify lucide--key-round"></span>
566
-
<span>{detectKeyType(key())}</span>
567
-
</div>
568
-
</div>
569
-
<span></span>
570
-
<div class="font-mono break-all">{key()}</div>
571
-
</div>
572
-
)}
573
-
</Show>
574
-
)}
575
-
</For>
576
-
</div>
577
-
</div>
578
-
579
-
{/* Rotation Keys Section */}
580
-
<Show when={rotationKeys().length > 0}>
546
+
{/* Services Section */}
581
547
<div>
582
-
<p class="font-semibold">Rotation Keys</p>
548
+
<p class="font-semibold">Services</p>
583
549
<div class="flex flex-col gap-1">
584
-
<For each={rotationKeys()}>
585
-
{(key) => (
550
+
<For each={didDocument().service}>
551
+
{(service) => (
586
552
<div class="grid grid-cols-[auto_1fr] items-center gap-x-1 text-sm text-neutral-700 dark:text-neutral-300">
587
-
<span class="iconify lucide--key-round text-neutral-500 dark:text-neutral-400"></span>
588
-
<span class="text-neutral-500 dark:text-neutral-400">
589
-
{detectDidKeyType(key)}
590
-
</span>
553
+
<span class="iconify lucide--hash"></span>
554
+
<span>{service.id.split("#")[1]}</span>
591
555
<span></span>
592
-
<div class="font-mono break-all">{key.replace("did:key:", "")}</div>
556
+
<a
557
+
class="w-fit underline hover:text-blue-400"
558
+
href={service.serviceEndpoint.toString()}
559
+
target="_blank"
560
+
rel="noopener"
561
+
>
562
+
{service.serviceEndpoint.toString()}
563
+
</a>
593
564
</div>
594
565
)}
595
566
</For>
596
567
</div>
597
568
</div>
598
-
</Show>
599
-
</div>
600
-
)}
569
+
570
+
{/* Verification Methods Section */}
571
+
<div>
572
+
<p class="font-semibold">Verification Methods</p>
573
+
<div class="flex flex-col gap-1">
574
+
<For each={didDocument().verificationMethod}>
575
+
{(verif) => (
576
+
<Show when={verif.publicKeyMultibase}>
577
+
{(key) => (
578
+
<div class="grid grid-cols-[auto_1fr] items-center gap-x-1 text-sm text-neutral-700 dark:text-neutral-300">
579
+
<span class="iconify lucide--hash"></span>
580
+
<div class="flex items-center gap-2">
581
+
<span>{verif.id.split("#")[1]}</span>
582
+
<div class="flex items-center gap-1 text-neutral-500 dark:text-neutral-400">
583
+
<span class="iconify lucide--key-round"></span>
584
+
<span>{detectKeyType(key())}</span>
585
+
</div>
586
+
</div>
587
+
<span></span>
588
+
<div class="font-mono break-all">{key()}</div>
589
+
</div>
590
+
)}
591
+
</Show>
592
+
)}
593
+
</For>
594
+
</div>
595
+
</div>
596
+
597
+
{/* Rotation Keys Section */}
598
+
<Show when={rotationKeys().length > 0}>
599
+
<div>
600
+
<p class="font-semibold">Rotation Keys</p>
601
+
<div class="flex flex-col gap-1">
602
+
<For each={rotationKeys()}>
603
+
{(key) => (
604
+
<div class="grid grid-cols-[auto_1fr] items-center gap-x-1 text-sm text-neutral-700 dark:text-neutral-300">
605
+
<span class="iconify lucide--key-round text-neutral-500 dark:text-neutral-400"></span>
606
+
<span class="text-neutral-500 dark:text-neutral-400">
607
+
{detectDidKeyType(key)}
608
+
</span>
609
+
<span></span>
610
+
<div class="font-mono break-all">{key.replace("did:key:", "")}</div>
611
+
</div>
612
+
)}
613
+
</For>
614
+
</div>
615
+
</div>
616
+
</Show>
617
+
</div>
618
+
)}
619
+
</Show>
601
620
</Show>
602
-
</Show>
621
+
</div>
603
622
</div>
604
-
</div>
605
-
</Show>
623
+
</Show>
624
+
</>
606
625
);
607
626
};
+2
src/views/settings.tsx
+2
src/views/settings.tsx
···
1
+
import { Title } from "@solidjs/meta";
1
2
import { createSignal } from "solid-js";
2
3
import { TextInput } from "../components/text-input.jsx";
3
4
import { ThemeSelection } from "../components/theme.jsx";
···
7
8
const Settings = () => {
8
9
return (
9
10
<div class="flex w-full flex-col gap-2 px-2">
11
+
<Title>Settings - PDSls</Title>
10
12
<div class="text-lg font-semibold">Settings</div>
11
13
<div class="flex flex-col gap-3">
12
14
<div class="flex flex-col gap-1">
+123
-119
src/views/stream.tsx
+123
-119
src/views/stream.tsx
···
1
1
import { Firehose } from "@skyware/firehose";
2
+
import { Title } from "@solidjs/meta";
2
3
import { A, useLocation, useSearchParams } from "@solidjs/router";
3
4
import { createSignal, For, onCleanup, onMount, Show } from "solid-js";
4
5
import { Button } from "../components/button";
···
169
170
});
170
171
171
172
return (
172
-
<div class="flex w-full flex-col items-center">
173
-
<div class="mb-1 flex gap-4 font-medium">
174
-
<A
175
-
class="flex items-center gap-1 border-b-2"
176
-
inactiveClass="border-transparent text-neutral-600 dark:text-neutral-400 hover:border-neutral-400 dark:hover:border-neutral-600"
177
-
href="/jetstream"
178
-
>
179
-
Jetstream
180
-
</A>
181
-
<A
182
-
class="flex items-center gap-1 border-b-2"
183
-
inactiveClass="border-transparent text-neutral-600 dark:text-neutral-400 hover:border-neutral-400 dark:hover:border-neutral-600"
184
-
href="/firehose"
185
-
>
186
-
Firehose
187
-
</A>
188
-
</div>
189
-
<StickyOverlay>
190
-
<form ref={formRef} class="flex w-full flex-col gap-1.5 text-sm">
191
-
<Show when={!connected()}>
192
-
<label class="flex items-center justify-end gap-x-1">
193
-
<span class="min-w-20">Instance</span>
194
-
<TextInput
195
-
name="instance"
196
-
value={
197
-
searchParams.instance ??
198
-
(streamType === "jetstream" ?
199
-
"wss://jetstream1.us-east.bsky.network/subscribe"
200
-
: "wss://bsky.network")
201
-
}
202
-
class="grow"
203
-
/>
204
-
</label>
205
-
<Show when={streamType === "jetstream"}>
173
+
<>
174
+
<Title>{streamType === "firehose" ? "Firehose" : "Jetstream"} - PDSls</Title>
175
+
<div class="flex w-full flex-col items-center">
176
+
<div class="mb-1 flex gap-4 font-medium">
177
+
<A
178
+
class="flex items-center gap-1 border-b-2"
179
+
inactiveClass="border-transparent text-neutral-600 dark:text-neutral-400 hover:border-neutral-400 dark:hover:border-neutral-600"
180
+
href="/jetstream"
181
+
>
182
+
Jetstream
183
+
</A>
184
+
<A
185
+
class="flex items-center gap-1 border-b-2"
186
+
inactiveClass="border-transparent text-neutral-600 dark:text-neutral-400 hover:border-neutral-400 dark:hover:border-neutral-600"
187
+
href="/firehose"
188
+
>
189
+
Firehose
190
+
</A>
191
+
</div>
192
+
<StickyOverlay>
193
+
<form ref={formRef} class="flex w-full flex-col gap-1.5 text-sm">
194
+
<Show when={!connected()}>
206
195
<label class="flex items-center justify-end gap-x-1">
207
-
<span class="min-w-20">Collections</span>
208
-
<textarea
209
-
name="collections"
210
-
spellcheck={false}
211
-
placeholder="Comma-separated list of collections"
212
-
value={searchParams.collections ?? ""}
213
-
class="dark:bg-dark-100 grow rounded-lg bg-white px-2 py-1 outline-1 outline-neutral-200 focus:outline-[1.5px] focus:outline-neutral-600 dark:outline-neutral-600 dark:focus:outline-neutral-400"
196
+
<span class="min-w-20">Instance</span>
197
+
<TextInput
198
+
name="instance"
199
+
value={
200
+
searchParams.instance ??
201
+
(streamType === "jetstream" ?
202
+
"wss://jetstream1.us-east.bsky.network/subscribe"
203
+
: "wss://bsky.network")
204
+
}
205
+
class="grow"
214
206
/>
215
207
</label>
216
-
</Show>
217
-
<Show when={streamType === "jetstream"}>
208
+
<Show when={streamType === "jetstream"}>
209
+
<label class="flex items-center justify-end gap-x-1">
210
+
<span class="min-w-20">Collections</span>
211
+
<textarea
212
+
name="collections"
213
+
spellcheck={false}
214
+
placeholder="Comma-separated list of collections"
215
+
value={searchParams.collections ?? ""}
216
+
class="dark:bg-dark-100 grow rounded-lg bg-white px-2 py-1 outline-1 outline-neutral-200 focus:outline-[1.5px] focus:outline-neutral-600 dark:outline-neutral-600 dark:focus:outline-neutral-400"
217
+
/>
218
+
</label>
219
+
</Show>
220
+
<Show when={streamType === "jetstream"}>
221
+
<label class="flex items-center justify-end gap-x-1">
222
+
<span class="min-w-20">DIDs</span>
223
+
<textarea
224
+
name="dids"
225
+
spellcheck={false}
226
+
placeholder="Comma-separated list of DIDs"
227
+
value={searchParams.dids ?? ""}
228
+
class="dark:bg-dark-100 grow rounded-lg bg-white px-2 py-1 outline-1 outline-neutral-200 focus:outline-[1.5px] focus:outline-neutral-600 dark:outline-neutral-600 dark:focus:outline-neutral-400"
229
+
/>
230
+
</label>
231
+
</Show>
218
232
<label class="flex items-center justify-end gap-x-1">
219
-
<span class="min-w-20">DIDs</span>
220
-
<textarea
221
-
name="dids"
222
-
spellcheck={false}
223
-
placeholder="Comma-separated list of DIDs"
224
-
value={searchParams.dids ?? ""}
225
-
class="dark:bg-dark-100 grow rounded-lg bg-white px-2 py-1 outline-1 outline-neutral-200 focus:outline-[1.5px] focus:outline-neutral-600 dark:outline-neutral-600 dark:focus:outline-neutral-400"
233
+
<span class="min-w-20">Cursor</span>
234
+
<TextInput
235
+
name="cursor"
236
+
placeholder="Leave empty for live-tail"
237
+
value={searchParams.cursor ?? ""}
238
+
class="grow"
226
239
/>
227
240
</label>
228
-
</Show>
229
-
<label class="flex items-center justify-end gap-x-1">
230
-
<span class="min-w-20">Cursor</span>
231
-
<TextInput
232
-
name="cursor"
233
-
placeholder="Leave empty for live-tail"
234
-
value={searchParams.cursor ?? ""}
235
-
class="grow"
236
-
/>
237
-
</label>
238
-
<Show when={streamType === "jetstream"}>
239
-
<div class="flex items-center justify-end gap-x-1">
240
-
<input
241
-
type="checkbox"
242
-
name="allEvents"
243
-
id="allEvents"
244
-
checked={searchParams.allEvents === "on" ? true : false}
245
-
/>
246
-
<label for="allEvents" class="select-none">
247
-
Show account and identity events
248
-
</label>
249
-
</div>
241
+
<Show when={streamType === "jetstream"}>
242
+
<div class="flex items-center justify-end gap-x-1">
243
+
<input
244
+
type="checkbox"
245
+
name="allEvents"
246
+
id="allEvents"
247
+
checked={searchParams.allEvents === "on" ? true : false}
248
+
/>
249
+
<label for="allEvents" class="select-none">
250
+
Show account and identity events
251
+
</label>
252
+
</div>
253
+
</Show>
250
254
</Show>
251
-
</Show>
252
-
<Show when={connected()}>
253
-
<div class="flex flex-col gap-1 wrap-anywhere">
254
-
<For each={parameters()}>
255
-
{(param) => (
256
-
<Show when={param.param}>
257
-
<div class="flex">
258
-
<div class="min-w-24 font-semibold">{param.name}</div>
259
-
{param.param}
260
-
</div>
261
-
</Show>
262
-
)}
263
-
</For>
264
-
</div>
265
-
</Show>
266
-
<div class="flex justify-end">
267
255
<Show when={connected()}>
268
-
<button
269
-
type="button"
270
-
onmousedown={(e) => {
271
-
e.preventDefault();
272
-
disconnect();
273
-
}}
274
-
ontouchstart={(e) => {
275
-
e.preventDefault();
276
-
disconnect();
277
-
}}
278
-
class="dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 box-border flex h-7 items-center gap-1 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 px-2 py-1.5 text-xs shadow-xs select-none hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800"
279
-
>
280
-
Disconnect
281
-
</button>
282
-
</Show>
283
-
<Show when={!connected()}>
284
-
<Button onClick={() => connectSocket(new FormData(formRef))}>Connect</Button>
256
+
<div class="flex flex-col gap-1 wrap-anywhere">
257
+
<For each={parameters()}>
258
+
{(param) => (
259
+
<Show when={param.param}>
260
+
<div class="flex">
261
+
<div class="min-w-24 font-semibold">{param.name}</div>
262
+
{param.param}
263
+
</div>
264
+
</Show>
265
+
)}
266
+
</For>
267
+
</div>
285
268
</Show>
286
-
</div>
287
-
</form>
288
-
</StickyOverlay>
289
-
<Show when={notice().length}>
290
-
<div class="text-red-500 dark:text-red-400">{notice()}</div>
291
-
</Show>
292
-
<div class="flex w-full flex-col gap-2 divide-y-[0.5px] divide-neutral-500 font-mono text-sm wrap-anywhere whitespace-pre-wrap md:w-3xl">
293
-
<For each={records().toReversed()}>
294
-
{(rec) => (
295
-
<div class="pb-2">
296
-
<JSONValue data={rec} repo={rec.did ?? rec.repo} />
269
+
<div class="flex justify-end">
270
+
<Show when={connected()}>
271
+
<button
272
+
type="button"
273
+
onmousedown={(e) => {
274
+
e.preventDefault();
275
+
disconnect();
276
+
}}
277
+
ontouchstart={(e) => {
278
+
e.preventDefault();
279
+
disconnect();
280
+
}}
281
+
class="dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 box-border flex h-7 items-center gap-1 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 px-2 py-1.5 text-xs shadow-xs select-none hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800"
282
+
>
283
+
Disconnect
284
+
</button>
285
+
</Show>
286
+
<Show when={!connected()}>
287
+
<Button onClick={() => connectSocket(new FormData(formRef))}>Connect</Button>
288
+
</Show>
297
289
</div>
298
-
)}
299
-
</For>
290
+
</form>
291
+
</StickyOverlay>
292
+
<Show when={notice().length}>
293
+
<div class="text-red-500 dark:text-red-400">{notice()}</div>
294
+
</Show>
295
+
<div class="flex w-full flex-col gap-2 divide-y-[0.5px] divide-neutral-500 font-mono text-sm wrap-anywhere whitespace-pre-wrap md:w-3xl">
296
+
<For each={records().toReversed()}>
297
+
{(rec) => (
298
+
<div class="pb-2">
299
+
<JSONValue data={rec} repo={rec.did ?? rec.repo} />
300
+
</div>
301
+
)}
302
+
</For>
303
+
</div>
300
304
</div>
301
-
</div>
305
+
</>
302
306
);
303
307
};
304
308