tangled
alpha
login
or
join now
futur.blue
/
pdsls
forked from
pds.ls/pdsls
0
fork
atom
this repo has no description
0
fork
atom
overview
issues
pulls
pipelines
new
handle.invalid
4 months ago
bc07dfe1
7acbe3a1
verified
This commit was signed with the committer's
known signature
.
handle.invalid
SSH Key Fingerprint:
SHA256:mBrT4x0JdzLpbVR95g1hjI1aaErfC02kmLRkPXwsYCk=
+48
-64
4 changed files
expand all
collapse all
unified
split
src
components
navbar.tsx
index.tsx
layout.tsx
views
labels.tsx
+3
-24
src/components/navbar.tsx
···
1
1
-
import { A, Params, useLocation } from "@solidjs/router";
1
1
+
import { A, Params } from "@solidjs/router";
2
2
import { createEffect, createSignal, Show } from "solid-js";
3
3
import { isTouchDevice } from "../layout";
4
4
-
import { didDocCache, labelerCache } from "../utils/api";
4
4
+
import { didDocCache } from "../utils/api";
5
5
import { addToClipboard } from "../utils/copy";
6
6
import Tooltip from "./tooltip";
7
7
···
29
29
};
30
30
31
31
export const NavBar = (props: { params: Params }) => {
32
32
-
const location = useLocation();
33
32
const [handle, setHandle] = createSignal(props.params.repo);
34
33
const [showHandle, setShowHandle] = createSignal(localStorage.showHandle === "true");
35
34
···
93
92
<Tooltip text="Repository">
94
93
<span class="iconify lucide--book-user shrink-0 text-neutral-500 transition-colors duration-200 group-hover:text-neutral-700 dark:text-neutral-400 dark:group-hover:text-neutral-200"></span>
95
94
</Tooltip>
96
96
-
{props.params.collection || location.pathname.includes("/labels") ?
95
95
+
{props.params.collection ?
97
96
<A
98
97
end
99
98
href={`/at://${props.params.repo}`}
···
124
123
</Tooltip>
125
124
<CopyButton content={props.params.repo} label="Copy DID" />
126
125
</div>
127
127
-
</div>
128
128
-
</Show>
129
129
-
130
130
-
{/* Labels Level */}
131
131
-
<Show
132
132
-
when={
133
133
-
!props.params.collection &&
134
134
-
(props.params.repo in labelerCache || location.pathname.endsWith("/labels"))
135
135
-
}
136
136
-
>
137
137
-
<div class="group flex items-center gap-2 rounded-md border-[0.5px] border-transparent bg-transparent px-2 transition-all duration-200 hover:border-neutral-300 hover:bg-neutral-50/40 dark:hover:border-neutral-600 dark:hover:bg-neutral-800/40">
138
138
-
<span class="iconify lucide--tag text-neutral-500 transition-colors duration-200 group-hover:text-neutral-700 dark:text-neutral-400 dark:group-hover:text-neutral-200"></span>
139
139
-
<A
140
140
-
end
141
141
-
href={`/at://${props.params.repo}/labels`}
142
142
-
class="py-0.5 font-medium"
143
143
-
inactiveClass="text-blue-400 grow hover:text-blue-500 transition-colors duration-150 dark:hover:text-blue-300"
144
144
-
>
145
145
-
labels
146
146
-
</A>
147
126
</div>
148
127
</Show>
149
128
+1
-1
src/index.tsx
···
17
17
<Router root={Layout}>
18
18
<Route path="/" component={Home} />
19
19
<Route path={["/jetstream", "/firehose"]} component={StreamView} />
20
20
+
<Route path="/labels" component={LabelView} />
20
21
<Route path="/settings" component={Settings} />
21
22
<Route path="/:pds" component={PdsView} />
22
23
<Route path="/:pds/:repo" component={RepoView} />
23
23
-
<Route path="/:pds/:repo/labels" component={LabelView} />
24
24
<Route path="/:pds/:repo/:collection" component={CollectionView} />
25
25
<Route path="/:pds/:repo/:collection/:rkey" component={RecordView} />
26
26
</Router>
+1
src/layout.tsx
···
145
145
>
146
146
<NavMenu href="/jetstream" label="Jetstream" />
147
147
<NavMenu href="/firehose" label="Firehose" />
148
148
+
<NavMenu href="/labels" label="Labels" />
148
149
<NavMenu href="/settings" label="Settings" />
149
150
<NavMenu
150
151
href="https://bsky.app/profile/did:plc:6q5daed5gutiyerimlrnojnz"
+43
-39
src/views/labels.tsx
···
1
1
import { ComAtprotoLabelDefs } from "@atcute/atproto";
2
2
import { Client, CredentialManager } from "@atcute/client";
3
3
-
import { A, useParams, useSearchParams } from "@solidjs/router";
4
4
-
import { createResource, createSignal, For, onMount, Show } from "solid-js";
3
3
+
import { A, useSearchParams } from "@solidjs/router";
4
4
+
import { createSignal, For, onMount, Show } from "solid-js";
5
5
import { Button } from "../components/button.jsx";
6
6
import { StickyOverlay } from "../components/sticky.jsx";
7
7
import { TextInput } from "../components/text-input.jsx";
8
8
import { labelerCache, resolvePDS } from "../utils/api.js";
9
9
import { localDateFromTimestamp } from "../utils/date.js";
10
10
11
11
-
const LabelView = () => {
12
12
-
const params = useParams();
11
11
+
export const LabelView = () => {
13
12
const [searchParams, setSearchParams] = useSearchParams();
14
13
const [cursor, setCursor] = createSignal<string>();
15
14
const [labels, setLabels] = createSignal<ComAtprotoLabelDefs.Label[]>([]);
16
15
const [filter, setFilter] = createSignal<string>();
17
16
const [labelCount, setLabelCount] = createSignal(0);
18
18
-
const did = params.repo;
17
17
+
const [loading, setLoading] = createSignal(false);
19
18
let rpc: Client;
19
19
+
let formRef!: HTMLFormElement;
20
20
21
21
onMount(async () => {
22
22
+
const formData = new FormData();
23
23
+
if (searchParams.did) formData.append("did", searchParams.did.toString());
24
24
+
if (searchParams.did) fetchLabels(formData);
25
25
+
});
26
26
+
27
27
+
const fetchLabels = async (formData: FormData, reset?: boolean) => {
28
28
+
if (reset) {
29
29
+
setLabels([]);
30
30
+
setCursor(undefined);
31
31
+
}
32
32
+
33
33
+
const did = formData.get("did")?.toString();
34
34
+
if (!did) return;
22
35
await resolvePDS(did);
23
36
rpc = new Client({
24
37
handler: new CredentialManager({ service: labelerCache[did] }),
25
38
});
26
26
-
refetch();
27
27
-
});
28
39
29
29
-
const fetchLabels = async () => {
30
30
-
const uriPatterns = (document.getElementById("patterns") as HTMLInputElement).value;
40
40
+
const uriPatterns = formData.get("uriPatterns")?.toString();
31
41
if (!uriPatterns) return;
42
42
+
43
43
+
setSearchParams({
44
44
+
did: formData.get("did")?.toString(),
45
45
+
uriPatterns: formData.get("uriPatterns")?.toString(),
46
46
+
});
47
47
+
48
48
+
setLoading(true);
32
49
const res = await rpc.get("com.atproto.label.queryLabels", {
33
50
params: {
34
51
uriPatterns: uriPatterns.toString().trim().split(","),
···
36
53
cursor: cursor(),
37
54
},
38
55
});
56
56
+
setLoading(false);
39
57
if (!res.ok) throw new Error(res.data.error);
40
58
setCursor(res.data.labels.length < 50 ? undefined : res.data.cursor);
41
59
setLabels(labels().concat(res.data.labels) ?? res.data.labels);
42
60
return res.data.labels;
43
61
};
44
62
45
45
-
const [response, { refetch }] = createResource(fetchLabels);
46
46
-
47
47
-
const initQuery = async () => {
48
48
-
setLabels([]);
49
49
-
setCursor("");
50
50
-
setSearchParams({
51
51
-
uriPatterns: (document.getElementById("patterns") as HTMLInputElement).value,
52
52
-
});
53
53
-
refetch();
54
54
-
};
55
55
-
56
63
const filterLabels = () => {
57
64
const newFilter = labels().filter((label) => (filter() ? filter() === label.val : true));
58
65
setLabelCount(newFilter.length);
···
61
68
62
69
return (
63
70
<div class="flex w-full flex-col items-center">
64
64
-
<form
65
65
-
class="flex w-full flex-col items-center gap-y-1 px-2"
66
66
-
onsubmit={(e) => {
67
67
-
e.preventDefault();
68
68
-
initQuery();
69
69
-
}}
70
70
-
>
71
71
-
<label for="patterns" class="ml-2 w-full text-sm">
71
71
+
<form ref={formRef} class="flex w-full flex-col items-center gap-y-1 px-2">
72
72
+
<label class="flex w-full items-center gap-x-2 px-1">
73
73
+
<span class="">DID</span>
74
74
+
<TextInput name="did" value={searchParams.did ?? ""} class="grow" />
75
75
+
</label>
76
76
+
<label for="uriPatterns" class="ml-2 w-full text-sm">
72
77
URI Patterns (comma-separated)
73
78
</label>
74
79
<div class="flex w-full items-center gap-x-1 px-1">
75
80
<textarea
76
76
-
id="patterns"
77
77
-
name="patterns"
81
81
+
id="uriPatterns"
82
82
+
name="uriPatterns"
78
83
spellcheck={false}
79
84
rows={2}
80
85
value={searchParams.uriPatterns ?? "*"}
81
86
class="dark:bg-dark-100 dark:shadow-dark-700 grow rounded-lg border-[0.5px] border-neutral-300 bg-white px-2 py-1 text-sm shadow-xs focus:outline-[1px] focus:outline-neutral-600 dark:border-neutral-600 dark:focus:outline-neutral-400"
82
87
/>
83
88
<div class="flex justify-center">
84
84
-
<Show when={!response.loading}>
89
89
+
<Show when={!loading()}>
85
90
<button
86
86
-
type="submit"
91
91
+
type="button"
92
92
+
onClick={() => fetchLabels(new FormData(formRef), true)}
87
93
class="flex items-center rounded-lg p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
88
94
>
89
95
<span class="iconify lucide--search text-lg"></span>
90
96
</button>
91
97
</Show>
92
92
-
<Show when={response.loading}>
98
98
+
<Show when={loading()}>
93
99
<div class="m-1 flex items-center">
94
100
<span class="iconify lucide--loader-circle animate-spin text-lg"></span>
95
101
</div>
···
114
120
</Show>
115
121
<Show when={cursor()}>
116
122
<div class="flex h-8 w-22 items-center justify-center text-nowrap">
117
117
-
<Show when={!response.loading}>
118
118
-
<Button onClick={() => refetch()}>Load More</Button>
123
123
+
<Show when={!loading()}>
124
124
+
<Button onClick={() => fetchLabels(new FormData(formRef))}>Load More</Button>
119
125
</Show>
120
120
-
<Show when={response.loading}>
126
126
+
<Show when={loading()}>
121
127
<div class="iconify lucide--loader-circle animate-spin text-xl" />
122
128
</Show>
123
129
</div>
···
170
176
</For>
171
177
</div>
172
178
</Show>
173
173
-
<Show when={!labels().length && !response.loading && searchParams.uriPatterns}>
179
179
+
<Show when={!labels().length && !loading() && searchParams.uriPatterns}>
174
180
<div class="mt-2">No results</div>
175
181
</Show>
176
182
</div>
177
183
);
178
184
};
179
179
-
180
180
-
export { LabelView };