+13
-16
src/components/tooltip.tsx
+13
-16
src/components/tooltip.tsx
···
2
2
3
3
const isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 1;
4
4
5
-
const Tooltip = (props: { text: string; children: JSX.Element }) => {
6
-
const width = (props.text.length - 1).toString();
7
-
return (
8
-
<div class="group/tooltip relative flex items-center">
9
-
{props.children}
10
-
<Show when={!isTouchDevice}>
11
-
<span
12
-
style={`transform: translate(-50%, 28px); min-width: ${width}ch`}
13
-
class={`left-50% border-0.5 dark:shadow-dark-900 pointer-events-none absolute z-10 hidden select-none whitespace-nowrap rounded border-neutral-300 bg-white p-1 text-center font-sans text-xs text-slate-900 shadow-md group-hover/tooltip:inline dark:border-neutral-600 dark:bg-neutral-800 dark:text-slate-100`}
14
-
>
15
-
{props.text}
16
-
</span>
17
-
</Show>
18
-
</div>
19
-
);
20
-
};
5
+
const Tooltip = (props: { text: string; children: JSX.Element }) => (
6
+
<div class="group/tooltip relative flex items-center">
7
+
{props.children}
8
+
<Show when={!isTouchDevice}>
9
+
<span
10
+
style={`transform: translate(-50%, 28px)`}
11
+
class={`left-50% border-0.5 dark:shadow-dark-900 pointer-events-none absolute z-10 hidden min-w-fit select-none whitespace-nowrap rounded border-neutral-300 bg-white p-1 text-center font-sans text-xs text-slate-900 shadow-md group-hover/tooltip:inline dark:border-neutral-600 dark:bg-neutral-800 dark:text-slate-100`}
12
+
>
13
+
{props.text}
14
+
</span>
15
+
</Show>
16
+
</div>
17
+
);
21
18
22
19
export default Tooltip;
+91
-60
src/views/repo.tsx
+91
-60
src/views/repo.tsx
···
21
21
import { localDateFromTimestamp } from "../utils/date.js";
22
22
23
23
type Tab = "collections" | "backlinks" | "doc" | "blobs";
24
+
type PlcEvent = "handle" | "rotation_key" | "service" | "verification_method";
24
25
25
-
const RepoView = () => {
26
-
const params = useParams();
27
-
const [error, setError] = createSignal<string>();
28
-
const [downloading, setDownloading] = createSignal(false);
29
-
const [didDoc, setDidDoc] = createSignal<DidDocument>();
30
-
const [backlinks, setBacklinks] = createSignal<{ links: LinkData; target: string }>();
31
-
const [nsids, setNsids] = createSignal<Record<string, { hidden: boolean; nsids: string[] }>>();
32
-
const [tab, setTab] = createSignal<Tab>("collections");
33
-
const [filter, setFilter] = createSignal<string>();
34
-
const [plcOps, setPlcOps] =
35
-
createSignal<[IndexedEntry<CompatibleOperationOrTombstone>, DiffEntry[]][]>();
36
-
const [showPlcLogs, setShowPlcLogs] = createSignal(false);
37
-
const [loading, setLoading] = createSignal(false);
38
-
let rpc: Client;
39
-
let pds: string;
40
-
const did = params.repo;
26
+
const PlcLogView = (props: {
27
+
plcOps: [IndexedEntry<CompatibleOperationOrTombstone>, DiffEntry[]][];
28
+
}) => {
29
+
const [activePlcEvent, setActivePlcEvent] = createSignal<PlcEvent | undefined>();
41
30
42
-
const RepoTab = (props: { tab: Tab; label: string }) => (
31
+
const FilterButton = (props: { icon: string; event: PlcEvent }) => (
43
32
<button
44
33
classList={{
45
-
"rounded-lg flex flex-1 py-1 justify-center": true,
46
-
"bg-zinc-200/70 dark:bg-dark-200 shadow-sm dark:shadow-dark-900": tab() === props.tab,
47
-
"bg-transparent hover:bg-zinc-200/50 dark:hover:bg-dark-300": tab() !== props.tab,
34
+
"rounded-full p-1.5": true,
35
+
"bg-neutral-700 dark:bg-neutral-200": activePlcEvent() === props.event,
48
36
}}
49
-
onclick={() => setTab(props.tab)}
37
+
onclick={() => setActivePlcEvent(activePlcEvent() === props.event ? undefined : props.event)}
50
38
>
51
-
{props.label}
39
+
<div
40
+
class={`${props.icon} text-xl ${activePlcEvent() === props.event ? "text-slate-100 dark:text-slate-900" : ""}`}
41
+
/>
52
42
</button>
53
43
);
54
44
···
64
54
} else if (diff.type === "identity_tombstoned") {
65
55
icon = "i-lucide-skull";
66
56
title = `Identity tombstoned`;
67
-
} else if (diff.type === "handle_added") {
57
+
} else if (diff.type === "handle_added" || diff.type === "handle_removed") {
68
58
icon = "i-lucide-at-sign";
69
-
title = "Alias added";
59
+
title = diff.type === "handle_added" ? "Alias added" : "Alias removed";
70
60
value = diff.handle;
71
61
} else if (diff.type === "handle_changed") {
72
62
icon = "i-lucide-at-sign";
73
63
title = "Alias updated";
74
64
value = `${diff.prev_handle} → ${diff.next_handle}`;
75
-
} else if (diff.type === "handle_removed") {
76
-
icon = "i-lucide-at-sign";
77
-
title = `Alias removed`;
78
-
value = diff.handle;
79
-
} else if (diff.type === "rotation_key_added") {
65
+
} else if (diff.type === "rotation_key_added" || diff.type === "rotation_key_removed") {
80
66
icon = "i-lucide-key-round";
81
-
title = `Rotation key added`;
82
-
value = diff.rotation_key;
83
-
} else if (diff.type === "rotation_key_removed") {
84
-
icon = "i-lucide-key-round";
85
-
title = `Rotation key removed`;
67
+
title = diff.type === "rotation_key_added" ? "Rotation key added" : "Rotation key removed";
86
68
value = diff.rotation_key;
87
-
} else if (diff.type === "service_added") {
69
+
} else if (diff.type === "service_added" || diff.type === "service_removed") {
88
70
icon = "i-lucide-server";
89
-
title = `Service ${diff.service_id} added`;
71
+
title = `Service ${diff.service_id} ${diff.type === "service_added" ? "added" : "removed"}`;
90
72
value = `${diff.service_endpoint}`;
91
73
} else if (diff.type === "service_changed") {
92
74
icon = "i-lucide-server";
93
75
title = `Service ${diff.service_id} updated`;
94
76
value = `${diff.prev_service_endpoint} → ${diff.next_service_endpoint}`;
95
-
} else if (diff.type === "service_removed") {
96
-
icon = "i-lucide-server";
97
-
title = `Service ${diff.service_id} removed`;
98
-
value = `${diff.service_endpoint}`;
99
-
} else if (diff.type === "verification_method_added") {
77
+
} else if (
78
+
diff.type === "verification_method_added" ||
79
+
diff.type === "verification_method_removed"
80
+
) {
100
81
icon = "i-lucide-shield-check";
101
-
title = `Verification method ${diff.method_id} added`;
82
+
title = `Verification method ${diff.method_id} ${diff.type === "verification_method_added" ? "added" : "removed"}`;
102
83
value = `${diff.method_key}`;
103
84
} else if (diff.type === "verification_method_changed") {
104
85
icon = "i-lucide-shield-check";
105
86
title = `Verification method ${diff.method_id} updated`;
106
87
value = `${diff.prev_method_key} → ${diff.next_method_key}`;
107
-
} else if (diff.type === "verification_method_removed") {
108
-
icon = "i-lucide-shield-check";
109
-
title = `Verification method ${diff.method_id} removed`;
110
-
value = `${diff.method_key}`;
111
88
}
112
89
113
90
return (
···
126
103
</div>
127
104
);
128
105
};
106
+
107
+
return (
108
+
<>
109
+
<div class="flex items-center gap-1">
110
+
<Tooltip text="Filter operations">
111
+
<div class="i-lucide-filter text-xl" />
112
+
</Tooltip>
113
+
<div class="dark:shadow-dark-900 flex w-fit items-center rounded-full bg-neutral-200 shadow-md dark:bg-neutral-700">
114
+
<FilterButton icon="i-lucide-at-sign" event="handle" />
115
+
<FilterButton icon="i-lucide-key-round" event="rotation_key" />
116
+
<FilterButton icon="i-lucide-server" event="service" />
117
+
<FilterButton icon="i-lucide-shield-check" event="verification_method" />
118
+
</div>
119
+
</div>
120
+
<div class="flex flex-col gap-1 text-sm">
121
+
<For each={props.plcOps}>
122
+
{([entry, diffs]) => (
123
+
<Show
124
+
when={!activePlcEvent() || diffs.find((d) => d.type.startsWith(activePlcEvent()!))}
125
+
>
126
+
<div class="flex flex-col">
127
+
<span class="text-neutral-500 dark:text-neutral-400">
128
+
{localDateFromTimestamp(new Date(entry.createdAt).getTime())}
129
+
</span>
130
+
{diffs.map((diff) => (
131
+
<Show when={!activePlcEvent() || diff.type.startsWith(activePlcEvent()!)}>
132
+
<DiffItem diff={diff} />
133
+
</Show>
134
+
))}
135
+
</div>
136
+
</Show>
137
+
)}
138
+
</For>
139
+
</div>
140
+
</>
141
+
);
142
+
};
143
+
144
+
const RepoView = () => {
145
+
const params = useParams();
146
+
const [error, setError] = createSignal<string>();
147
+
const [downloading, setDownloading] = createSignal(false);
148
+
const [didDoc, setDidDoc] = createSignal<DidDocument>();
149
+
const [backlinks, setBacklinks] = createSignal<{ links: LinkData; target: string }>();
150
+
const [nsids, setNsids] = createSignal<Record<string, { hidden: boolean; nsids: string[] }>>();
151
+
const [tab, setTab] = createSignal<Tab>("collections");
152
+
const [filter, setFilter] = createSignal<string>();
153
+
const [plcOps, setPlcOps] =
154
+
createSignal<[IndexedEntry<CompatibleOperationOrTombstone>, DiffEntry[]][]>();
155
+
const [showPlcLogs, setShowPlcLogs] = createSignal(false);
156
+
const [loading, setLoading] = createSignal(false);
157
+
let rpc: Client;
158
+
let pds: string;
159
+
const did = params.repo;
160
+
161
+
const RepoTab = (props: { tab: Tab; label: string }) => (
162
+
<button
163
+
classList={{
164
+
"rounded-lg flex flex-1 py-1 justify-center": true,
165
+
"bg-zinc-200/70 dark:bg-dark-200 shadow-sm dark:shadow-dark-900": tab() === props.tab,
166
+
"bg-transparent hover:bg-zinc-200/50 dark:hover:bg-dark-300": tab() !== props.tab,
167
+
}}
168
+
onclick={() => setTab(props.tab)}
169
+
>
170
+
{props.label}
171
+
</button>
172
+
);
129
173
130
174
const fetchRepo = async () => {
131
175
pds = await resolvePDS(did);
···
421
465
</Show>
422
466
</div>
423
467
<Show when={showPlcLogs()}>
424
-
<div class="flex flex-col gap-1 text-sm">
425
-
<For each={plcOps()}>
426
-
{([entry, diffs]) => (
427
-
<div class="flex flex-col">
428
-
<span class="text-neutral-500 dark:text-neutral-400">
429
-
{localDateFromTimestamp(new Date(entry.createdAt).getTime())}
430
-
</span>
431
-
{diffs.map((diff) => (
432
-
<DiffItem diff={diff} />
433
-
))}
434
-
</div>
435
-
)}
436
-
</For>
437
-
</div>
468
+
<PlcLogView plcOps={plcOps() ?? []} />
438
469
</Show>
439
470
</div>
440
471
)}