tangled
alpha
login
or
join now
bas.sh
/
pdsls
forked from
pds.ls/pdsls
0
fork
atom
atmosphere explorer
0
fork
atom
overview
issues
pulls
pipelines
new stream layout
handle.invalid
2 months ago
143d88d2
778c79d9
verified
This commit was signed with the committer's
known signature
.
handle.invalid
SSH Key Fingerprint:
SHA256:mBrT4x0JdzLpbVR95g1hjI1aaErfC02kmLRkPXwsYCk=
+155
-56
1 changed file
expand all
collapse all
unified
split
src
views
stream
index.tsx
+155
-56
src/views/stream/index.tsx
···
3
3
import { A, useLocation, useSearchParams } from "@solidjs/router";
4
4
import { createSignal, For, onCleanup, onMount, Show } from "solid-js";
5
5
import { Button } from "../../components/button";
6
6
+
import DidHoverCard from "../../components/hover-card/did";
6
7
import { JSONValue } from "../../components/json";
7
7
-
import { StickyOverlay } from "../../components/sticky";
8
8
import { TextInput } from "../../components/text-input";
9
9
+
import { addToClipboard } from "../../utils/copy";
10
10
+
import { localDateFromTimestamp } from "../../utils/date";
9
11
import { StreamStats, StreamStatsPanel } from "./stats";
10
12
11
13
const LIMIT = 20;
12
14
type Parameter = { name: string; param: string | string[] | undefined };
13
15
16
16
+
const StreamRecordItem = (props: { record: any; streamType: "jetstream" | "firehose" }) => {
17
17
+
const [expanded, setExpanded] = createSignal(false);
18
18
+
19
19
+
const getBasicInfo = () => {
20
20
+
const rec = props.record;
21
21
+
if (props.streamType === "jetstream") {
22
22
+
const collection = rec.commit?.collection || rec.kind;
23
23
+
const rkey = rec.commit?.rkey;
24
24
+
const action = rec.commit?.operation;
25
25
+
const time = rec.time_us ? localDateFromTimestamp(rec.time_us / 1000) : undefined;
26
26
+
return { type: rec.kind, did: rec.did, collection, rkey, action, time };
27
27
+
} else {
28
28
+
const type = rec.$type?.split("#").pop() || rec.$type;
29
29
+
const did = rec.repo ?? rec.did;
30
30
+
const pathParts = rec.op?.path?.split("/") || [];
31
31
+
const collection = pathParts[0];
32
32
+
const rkey = pathParts[1];
33
33
+
const time = rec.time ? localDateFromTimestamp(Date.parse(rec.time)) : undefined;
34
34
+
return { type, did, collection, rkey, action: rec.op?.action, time };
35
35
+
}
36
36
+
};
37
37
+
38
38
+
const info = getBasicInfo();
39
39
+
40
40
+
const typeColors: Record<string, string> = {
41
41
+
create: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300",
42
42
+
update: "bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-300",
43
43
+
delete: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300",
44
44
+
identity: "bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300",
45
45
+
account: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300",
46
46
+
sync: "bg-pink-100 text-pink-700 dark:bg-pink-900/30 dark:text-pink-300",
47
47
+
};
48
48
+
49
49
+
const copyRecord = (e: MouseEvent) => {
50
50
+
e.stopPropagation();
51
51
+
addToClipboard(JSON.stringify(props.record, null, 2));
52
52
+
};
53
53
+
54
54
+
return (
55
55
+
<div class="flex flex-col gap-2">
56
56
+
<div class="flex items-start gap-1">
57
57
+
<button
58
58
+
type="button"
59
59
+
onclick={() => setExpanded(!expanded())}
60
60
+
class="dark:hover:bg-dark-200 flex min-w-0 flex-1 items-start gap-2 rounded p-1 text-left hover:bg-neutral-200/70"
61
61
+
>
62
62
+
<span class="mt-0.5 shrink-0 text-neutral-400 dark:text-neutral-500">
63
63
+
{expanded() ?
64
64
+
<span class="iconify lucide--chevron-down"></span>
65
65
+
: <span class="iconify lucide--chevron-right"></span>}
66
66
+
</span>
67
67
+
<div class="flex min-w-0 flex-1 flex-col gap-0.5">
68
68
+
<div class="flex flex-wrap items-center gap-x-1.5 gap-y-0.5 sm:gap-x-2">
69
69
+
<span
70
70
+
class={`rounded px-1.5 py-0.5 text-xs font-medium ${typeColors[info.type === "commit" ? info.action : info.type] || "bg-neutral-200 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300"}`}
71
71
+
>
72
72
+
{info.type === "commit" ? info.action : info.type}
73
73
+
</span>
74
74
+
<Show when={info.collection && info.collection !== info.type}>
75
75
+
<span class="text-neutral-600 dark:text-neutral-300">{info.collection}</span>
76
76
+
</Show>
77
77
+
<Show when={info.rkey}>
78
78
+
<span class="text-neutral-400 dark:text-neutral-500">{info.rkey}</span>
79
79
+
</Show>
80
80
+
</div>
81
81
+
<div class="flex flex-col gap-x-2 gap-y-0.5 text-xs text-neutral-500 sm:flex-row sm:items-center dark:text-neutral-400">
82
82
+
<Show when={info.did}>
83
83
+
<span class="w-fit" onclick={(e) => e.stopPropagation()}>
84
84
+
<DidHoverCard newTab did={info.did} />
85
85
+
</span>
86
86
+
</Show>
87
87
+
<Show when={info.time}>
88
88
+
<span>{info.time}</span>
89
89
+
</Show>
90
90
+
</div>
91
91
+
</div>
92
92
+
</button>
93
93
+
<Show when={expanded()}>
94
94
+
<button
95
95
+
type="button"
96
96
+
onclick={copyRecord}
97
97
+
class="flex size-6 shrink-0 items-center justify-center rounded text-neutral-500 transition-colors hover:bg-neutral-200 hover:text-neutral-600 active:bg-neutral-300 sm:size-7 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:active:bg-neutral-600"
98
98
+
>
99
99
+
<span class="iconify lucide--copy"></span>
100
100
+
</button>
101
101
+
</Show>
102
102
+
</div>
103
103
+
<Show when={expanded()}>
104
104
+
<div class="ml-6.5">
105
105
+
<div class="w-full text-xs wrap-anywhere whitespace-pre-wrap md:w-2xl">
106
106
+
<JSONValue newTab data={props.record} repo={info.did} hideBlobs />
107
107
+
</div>
108
108
+
</div>
109
109
+
</Show>
110
110
+
</div>
111
111
+
);
112
112
+
};
113
113
+
14
114
const StreamView = () => {
15
115
const [searchParams, setSearchParams] = useSearchParams();
16
116
const [parameters, setParameters] = createSignal<Parameter[]>([]);
17
117
const streamType = useLocation().pathname === "/firehose" ? "firehose" : "jetstream";
18
18
-
const [records, setRecords] = createSignal<Array<any>>([]);
118
118
+
const [records, setRecords] = createSignal<any[]>([]);
19
119
const [connected, setConnected] = createSignal(false);
20
120
const [paused, setPaused] = createSignal(false);
21
121
const [notice, setNotice] = createSignal("");
···
266
366
return (
267
367
<>
268
368
<Title>{streamType === "firehose" ? "Firehose" : "Jetstream"} - PDSls</Title>
269
269
-
<div class="flex w-full flex-col items-center">
369
369
+
<div class="flex w-full flex-col items-center gap-2">
270
370
<div class="flex gap-4 font-medium">
271
371
<A
272
372
class="flex items-center gap-1 border-b-2"
···
284
384
</A>
285
385
</div>
286
386
<Show when={!connected()}>
287
287
-
<form ref={formRef} class="mt-4 mb-4 flex w-full flex-col gap-1.5 px-2 text-sm">
387
387
+
<form ref={formRef} class="flex w-full flex-col gap-1.5 p-2 text-sm">
288
388
<label class="flex items-center justify-end gap-x-1">
289
389
<span class="min-w-20">Instance</span>
290
390
<TextInput
···
350
450
</form>
351
451
</Show>
352
452
<Show when={connected()}>
353
353
-
<StickyOverlay>
354
354
-
<div class="flex w-full flex-col gap-2 p-1">
355
355
-
<div class="flex flex-col gap-1 text-sm wrap-anywhere">
356
356
-
<div class="font-semibold">Parameters</div>
357
357
-
<For each={parameters()}>
358
358
-
{(param) => (
359
359
-
<Show when={param.param}>
360
360
-
<div class="text-sm">
361
361
-
<div class="text-xs text-neutral-500 dark:text-neutral-400">
362
362
-
{param.name}
363
363
-
</div>
364
364
-
<div class="text-neutral-700 dark:text-neutral-300">{param.param}</div>
365
365
-
</div>
366
366
-
</Show>
367
367
-
)}
368
368
-
</For>
369
369
-
</div>
370
370
-
<StreamStatsPanel stats={stats()} currentTime={currentTime()} />
371
371
-
<div class="flex justify-end gap-2">
372
372
-
<button
373
373
-
type="button"
374
374
-
ontouchstart={(e) => {
375
375
-
e.preventDefault();
376
376
-
requestAnimationFrame(() => togglePause());
377
377
-
}}
378
378
-
onclick={togglePause}
379
379
-
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"
380
380
-
>
381
381
-
{paused() ? "Resume" : "Pause"}
382
382
-
</button>
383
383
-
<button
384
384
-
type="button"
385
385
-
ontouchstart={(e) => {
386
386
-
e.preventDefault();
387
387
-
requestAnimationFrame(() => disconnect());
388
388
-
}}
389
389
-
onclick={disconnect}
390
390
-
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"
391
391
-
>
392
392
-
Disconnect
393
393
-
</button>
394
394
-
</div>
453
453
+
<div class="flex w-full flex-col gap-2 p-2">
454
454
+
<div class="flex flex-col gap-1 text-sm wrap-anywhere">
455
455
+
<div class="font-semibold">Parameters</div>
456
456
+
<For each={parameters()}>
457
457
+
{(param) => (
458
458
+
<Show when={param.param}>
459
459
+
<div class="text-sm">
460
460
+
<div class="text-xs text-neutral-500 dark:text-neutral-400">{param.name}</div>
461
461
+
<div class="text-neutral-700 dark:text-neutral-300">{param.param}</div>
462
462
+
</div>
463
463
+
</Show>
464
464
+
)}
465
465
+
</For>
466
466
+
</div>
467
467
+
<StreamStatsPanel stats={stats()} currentTime={currentTime()} />
468
468
+
<div class="flex justify-end gap-2">
469
469
+
<button
470
470
+
type="button"
471
471
+
ontouchstart={(e) => {
472
472
+
e.preventDefault();
473
473
+
requestAnimationFrame(() => togglePause());
474
474
+
}}
475
475
+
onclick={togglePause}
476
476
+
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"
477
477
+
>
478
478
+
{paused() ? "Resume" : "Pause"}
479
479
+
</button>
480
480
+
<button
481
481
+
type="button"
482
482
+
ontouchstart={(e) => {
483
483
+
e.preventDefault();
484
484
+
requestAnimationFrame(() => disconnect());
485
485
+
}}
486
486
+
onclick={disconnect}
487
487
+
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"
488
488
+
>
489
489
+
Disconnect
490
490
+
</button>
395
491
</div>
396
396
-
</StickyOverlay>
492
492
+
</div>
397
493
</Show>
398
494
<Show when={notice().length}>
399
495
<div class="text-red-500 dark:text-red-400">{notice()}</div>
400
496
</Show>
401
401
-
<div class="flex w-full flex-col gap-2 divide-y-[0.5px] divide-neutral-500 font-mono text-xs wrap-anywhere whitespace-pre-wrap sm:text-sm md:w-3xl">
402
402
-
<For each={records().toReversed()}>
403
403
-
{(rec) => (
404
404
-
<div class="pb-2">
405
405
-
<JSONValue data={rec} repo={rec.did ?? rec.repo} hideBlobs />
406
406
-
</div>
407
407
-
)}
408
408
-
</For>
409
409
-
</div>
497
497
+
<Show when={connected() || records().length > 0}>
498
498
+
<div class="flex min-h-280 w-full flex-col gap-2 font-mono text-xs [overflow-anchor:auto] sm:text-sm">
499
499
+
<For each={records().toReversed()}>
500
500
+
{(rec) => (
501
501
+
<div class="[overflow-anchor:none]">
502
502
+
<StreamRecordItem record={rec} streamType={streamType} />
503
503
+
</div>
504
504
+
)}
505
505
+
</For>
506
506
+
<div class="h-px [overflow-anchor:auto]" />
507
507
+
</div>
508
508
+
</Show>
410
509
</div>
411
510
</>
412
511
);