1import { A, Params, useLocation } from "@solidjs/router";
2import Tooltip from "./tooltip";
3import { createEffect, createSignal, Show } from "solid-js";
4import { didDocCache, labelerCache, validateHandle } from "../utils/api";
5import { setShowHandle, showHandle } from "./settings";
6import { Did, Handle } from "@atcute/lexicons";
7import { addToClipboard } from "../utils/copy";
8
9export const [pds, setPDS] = createSignal<string>();
10export const [cid, setCID] = createSignal<string>();
11export const [isLabeler, setIsLabeler] = createSignal(false);
12export const [validRecord, setValidRecord] = createSignal<boolean | undefined>(undefined);
13export const [validSchema, setValidSchema] = createSignal<boolean | undefined>(undefined);
14
15const swapIcons: Record<string, string> = {
16 "did:plc:vwzwgnygau7ed7b7wt5ux7y2": "i-lucide-microchip",
17 "did:plc:oisofpd7lj26yvgiivf3lxsi": "i-lucide-bone",
18 "did:plc:uu5axsmbm2or2dngy4gwchec": "i-lucide-train-track",
19 "did:plc:7x6rtuenkuvxq3zsvffp2ide": "i-lucide-rabbit",
20 "did:plc:ia76kvnndjutgedggx2ibrem": "i-lucide-rabbit",
21 "did:plc:5rowvb4jjbm26fdkx6a5rxls": "i-lucide-rabbit",
22 "did:plc:hdhoaan3xa3jiuq4fg4mefid": "i-lucide-lab-shark",
23 "did:plc:hvakvedv6byxhufjl23mfmsd": "i-lucide-rat",
24 "did:plc:ezhjhbzqt32bqprrn6qjlkri": "i-lucide-film",
25 "did:plc:6v6jqsy7swpzuu53rmzaybjy": "i-lucide-fish",
26 "did:plc:hx53snho72xoj7zqt5uice4u": "i-lucide-lab-flower-rose-single",
27 "did:plc:wzsilnxf24ehtmmc3gssy5bu": "i-lucide-music-2",
28 "did:plc:bnqkww7bjxaacajzvu5gswdf": "i-lucide-gem",
29 "did:plc:pm6jxakrtzmkorra62cr43kr": "i-lucide-flag",
30 "did:plc:355lbopbpckczt672hss2ra4": "i-lucide-lab-basketball",
31 "did:plc:44ybard66vv44zksje25o7dz": "i-lucide-mountain-snow",
32 "did:plc:q6gjnaw2blty4crticxkmujt": "i-lucide-cat",
33 "did:plc:oky5czdrnfjpqslsw2a5iclo": "i-tabler-brand-bluesky",
34};
35
36const NavBar = (props: { params: Params }) => {
37 const location = useLocation();
38 const [handle, setHandle] = createSignal(props.params.repo);
39 const [validHandle, setValidHandle] = createSignal<boolean | undefined>(undefined);
40 const [fullCid, setFullCid] = createSignal(false);
41
42 createEffect(() => {
43 if (cid() !== undefined) setFullCid(false);
44 });
45
46 createEffect(async () => {
47 if (pds() !== undefined && props.params.repo) {
48 const hdl =
49 didDocCache[props.params.repo]?.alsoKnownAs
50 ?.filter((alias) => alias.startsWith("at://"))[0]
51 .split("at://")[1] ?? props.params.repo;
52 if (hdl !== handle()) {
53 setValidHandle(undefined);
54 setHandle(hdl);
55 setValidHandle(await validateHandle(hdl as Handle, props.params.repo as Did));
56 }
57 }
58 });
59
60 return (
61 <div class="break-anywhere mt-4 flex w-[21rem] flex-col font-mono text-sm sm:w-[24rem]">
62 <div class="relative flex items-center justify-between gap-1">
63 <div class="min-h-1.25rem flex basis-full items-center gap-2">
64 <Tooltip text="PDS">
65 <button onclick={() => addToClipboard(pds()!)}>
66 <div class="i-lucide-server shrink-0 text-lg" />
67 </button>
68 </Tooltip>
69 <Show when={pds()}>
70 <Show when={props.params.repo}>
71 <A end href={pds()!} inactiveClass="text-blue-400 w-full hover:underline">
72 {pds()}
73 </A>
74 </Show>
75 <Show when={!props.params.repo}>
76 <span>{pds()}</span>
77 </Show>
78 </Show>
79 </div>
80 <Tooltip
81 text={`Copy ${
82 props.params.collection ? "AT URI"
83 : props.params.repo ? "DID"
84 : "PDS"
85 }`}
86 >
87 <button
88 onclick={() =>
89 addToClipboard(
90 props.params.collection ?
91 `at://${props.params.repo}/${props.params.collection}${props.params.rkey ? `/${props.params.rkey}` : ""}`
92 : props.params.repo ? props.params.repo
93 : pds()!,
94 )
95 }
96 >
97 <div class="i-lucide-copy shrink-0 text-lg" />
98 </button>
99 </Tooltip>
100 </div>
101 <div class="flex flex-col flex-wrap">
102 <Show when={props.params.repo}>
103 <div>
104 <div class="relative mt-1 flex items-center justify-between gap-1">
105 <div class="flex basis-full items-center gap-2">
106 <Tooltip text="Repository">
107 <button onclick={() => addToClipboard(props.params.repo)}>
108 <div class="i-lucide-at-sign text-lg" />
109 </button>
110 </Tooltip>
111 <div class="flex gap-1">
112 {props.params.collection || location.pathname.includes("/labels") ?
113 <A
114 end
115 href={`/at://${props.params.repo}`}
116 inactiveClass="text-blue-400 hover:underline"
117 >
118 {showHandle() ? handle() : props.params.repo}
119 </A>
120 : <span>{showHandle() ? handle() : props.params.repo}</span>}
121 <Show when={showHandle()}>
122 <Tooltip
123 text={
124 validHandle() === true ? "Valid handle"
125 : validHandle() === undefined ?
126 "Validating"
127 : "Invalid handle"
128 }
129 >
130 <div
131 classList={{
132 "i-lucide-circle-check": validHandle() === true,
133 "i-lucide-circle-x text-red-500 dark:text-red-400":
134 validHandle() === false,
135 "i-lucide-loader-circle animate-spin": validHandle() === undefined,
136 }}
137 />
138 </Tooltip>
139 </Show>
140 </div>
141 </div>
142 <Tooltip text={showHandle() ? "Show DID" : "Show Handle"}>
143 <button onclick={() => setShowHandle(!showHandle())}>
144 <div
145 class={
146 "shrink-0 text-lg " +
147 (swapIcons[props.params.repo] ?? "i-lucide-arrow-left-right")
148 }
149 />
150 </button>
151 </Tooltip>
152 </div>
153 <Show when={props.params.repo in labelerCache && !props.params.collection}>
154 <div class="mt-1 flex items-center gap-2">
155 <div class="i-lucide-tag text-lg" />
156 <A
157 end
158 href={`/at://${props.params.repo}/labels`}
159 inactiveClass="text-blue-400 grow hover:underline"
160 >
161 labels
162 </A>
163 </div>
164 </Show>
165 </div>
166 </Show>
167 <Show when={props.params.collection}>
168 <div class="mt-1 flex items-center gap-2">
169 <Tooltip text="Collection">
170 <button onclick={() => addToClipboard(props.params.collection)}>
171 <div class="i-lucide-list text-lg" />
172 </button>
173 </Tooltip>
174 <Show when={props.params.rkey}>
175 <A
176 end
177 href={`/at://${props.params.repo}/${props.params.collection}`}
178 inactiveClass="text-blue-400 w-full hover:underline"
179 >
180 {props.params.collection}
181 </A>
182 </Show>
183 <Show when={!props.params.rkey}>
184 <span>{props.params.collection}</span>
185 </Show>
186 </div>
187 </Show>
188 <Show when={props.params.rkey}>
189 <div class="mt-1 flex items-center gap-2">
190 <Tooltip text="Record">
191 <button onclick={() => addToClipboard(props.params.rkey)}>
192 <div class="i-lucide-braces text-lg" />
193 </button>
194 </Tooltip>
195 <div class="flex gap-1">
196 <span>{props.params.rkey}</span>
197 <Show when={validRecord()}>
198 <Tooltip text="Valid record">
199 <div class="i-lucide-lock-keyhole" />
200 </Tooltip>
201 </Show>
202 <Show when={validRecord() === false}>
203 <Tooltip text="Invalid record">
204 <div class="i-lucide-lock-keyhole-open text-red-500 dark:text-red-400" />
205 </Tooltip>
206 </Show>
207 <Show when={validRecord() === undefined}>
208 <Tooltip text="Validating">
209 <div class="i-lucide-loader-circle animate-spin" />
210 </Tooltip>
211 </Show>
212 <Show when={validSchema()}>
213 <Tooltip text="Valid schema">
214 <div class="i-lucide-file-check" />
215 </Tooltip>
216 </Show>
217 <Show when={validSchema() === false}>
218 <Tooltip text="Invalid schema">
219 <div class="i-lucide-file-x text-red-500 dark:text-red-400" />
220 </Tooltip>
221 </Show>
222 </div>
223 </div>
224 </Show>
225 </div>
226 <Show when={props.params.rkey && cid()}>
227 {(cid) => (
228 <div class="mt-1 flex gap-2">
229 <Tooltip text="CID">
230 <button onclick={() => addToClipboard(cid())}>
231 <div class="i-lucide-box text-lg" />
232 </button>
233 </Tooltip>
234 <button
235 dir="rtl"
236 classList={{ "bg-transparent text-left": true, truncate: !fullCid() }}
237 onclick={() => setFullCid(!fullCid())}
238 >
239 {cid()}
240 </button>
241 </div>
242 )}
243 </Show>
244 </div>
245 );
246};
247
248export { NavBar };