1import { A, Params, useLocation } from "@solidjs/router";
2import { createEffect, createSignal, Show } from "solid-js";
3import { isTouchDevice } from "../layout";
4import { didDocCache, labelerCache } from "../utils/api";
5import { addToClipboard } from "../utils/copy";
6import Tooltip from "./tooltip";
7
8export const [pds, setPDS] = createSignal<string>();
9
10const CopyButton = (props: { content: string; label: string }) => {
11 return (
12 <Show when={!isTouchDevice}>
13 <Tooltip text={props.label}>
14 <button
15 type="button"
16 onclick={(e) => {
17 e.preventDefault();
18 e.stopPropagation();
19 addToClipboard(props.content);
20 }}
21 class={`-mr-2 hidden items-center rounded px-2 py-1.5 text-neutral-500 transition-all duration-200 group-hover:flex hover:bg-neutral-200/70 hover:text-neutral-600 active:bg-neutral-300/70 dark:text-neutral-400 dark:hover:bg-neutral-700/70 dark:hover:text-neutral-300 dark:active:bg-neutral-600/70`}
22 aria-label="Copy to clipboard"
23 >
24 <span class="iconify lucide--link"></span>
25 </button>
26 </Tooltip>
27 </Show>
28 );
29};
30
31export const NavBar = (props: { params: Params }) => {
32 const location = useLocation();
33 const [handle, setHandle] = createSignal(props.params.repo);
34 const [showHandle, setShowHandle] = createSignal(localStorage.showHandle === "true");
35
36 createEffect(() => {
37 if (pds() !== undefined && props.params.repo) {
38 const hdl =
39 didDocCache[props.params.repo]?.alsoKnownAs
40 ?.filter((alias) => alias.startsWith("at://"))[0]
41 .split("at://")[1] ?? props.params.repo;
42 if (hdl !== handle()) setHandle(hdl);
43 }
44 });
45
46 return (
47 <nav class="flex w-full flex-col text-sm wrap-anywhere sm:text-base">
48 {/* PDS Level */}
49 <div class="group relative flex items-center justify-between gap-1 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">
50 <div class="flex min-h-6 basis-full items-center gap-2 sm:min-h-7">
51 <Tooltip text="PDS">
52 <span class="iconify lucide--hard-drive 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>
53 </Tooltip>
54 <Show when={pds()}>
55 <Show
56 when={props.params.repo}
57 fallback={<span class="py-0.5 font-medium">{pds()}</span>}
58 >
59 <A
60 end
61 href={pds()!}
62 inactiveClass="text-blue-400 py-0.5 w-full font-medium hover:text-blue-500 transition-colors duration-150 dark:hover:text-blue-300"
63 >
64 {pds()}
65 </A>
66 </Show>
67 </Show>
68 </div>
69 <Show when={pds()}>
70 <CopyButton content={pds()!} label="Copy PDS" />
71 </Show>
72 </div>
73
74 <div class="flex flex-col">
75 <Show when={props.params.repo}>
76 {/* Repository Level */}
77 <div class="group relative flex items-center justify-between gap-1 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">
78 <div class="flex basis-full items-center gap-2">
79 <Tooltip text="Repository">
80 <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>
81 </Tooltip>
82 {props.params.collection || location.pathname.includes("/labels") ?
83 <A
84 end
85 href={`/at://${props.params.repo}`}
86 inactiveClass="text-blue-400 w-full py-0.5 font-medium hover:text-blue-500 transition-colors duration-150 dark:hover:text-blue-300"
87 >
88 {showHandle() ? handle() : props.params.repo}
89 </A>
90 : <span class="py-0.5 font-medium">
91 {showHandle() ? handle() : props.params.repo}
92 </span>
93 }
94 </div>
95 <div class="flex">
96 <Tooltip text={showHandle() ? "Show DID" : "Show handle"}>
97 <button
98 type="button"
99 class={`items-center rounded px-1.25 py-1.25 text-neutral-500 transition-all duration-200 hover:bg-neutral-200/70 hover:text-neutral-700 active:bg-neutral-300/70 sm:px-2 sm:py-1.5 dark:text-neutral-400 dark:hover:bg-neutral-700/70 dark:hover:text-neutral-200 dark:active:bg-neutral-600/70 ${isTouchDevice ? "flex" : "hidden group-hover:flex"}`}
100 onclick={() => {
101 localStorage.showHandle = !showHandle();
102 setShowHandle(!showHandle());
103 }}
104 aria-label="Switch DID/Handle"
105 >
106 <span
107 class={`iconify shrink-0 duration-200 ${showHandle() ? "rotate-y-180" : ""} lucide--arrow-left-right`}
108 ></span>
109 </button>
110 </Tooltip>
111 <CopyButton content={props.params.repo} label="Copy DID" />
112 </div>
113 </div>
114 </Show>
115
116 {/* Labels Level */}
117 <Show
118 when={
119 !props.params.collection &&
120 (props.params.repo in labelerCache || location.pathname.endsWith("/labels"))
121 }
122 >
123 <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">
124 <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>
125 <A
126 end
127 href={`/at://${props.params.repo}/labels`}
128 class="py-0.5"
129 inactiveClass="text-blue-400 grow font-medium hover:text-blue-500 transition-colors duration-150 dark:hover:text-blue-300"
130 >
131 labels
132 </A>
133 </div>
134 </Show>
135
136 {/* Collection Level */}
137 <Show when={props.params.collection}>
138 <div class="group flex items-center justify-between 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">
139 <div class="flex basis-full items-center gap-2">
140 <Tooltip text="Collection">
141 <span class="iconify lucide--folder-open text-neutral-500 transition-colors duration-200 group-hover:text-neutral-700 dark:text-neutral-400 dark:group-hover:text-neutral-200"></span>
142 </Tooltip>
143 <Show
144 when={props.params.rkey}
145 fallback={<span class="py-0.5 font-medium">{props.params.collection}</span>}
146 >
147 <A
148 end
149 href={`/at://${props.params.repo}/${props.params.collection}`}
150 inactiveClass="text-blue-400 grow py-0.5 font-medium hover:text-blue-500 transition-colors duration-150 dark:hover:text-blue-300"
151 >
152 {props.params.collection}
153 </A>
154 </Show>
155 </div>
156 <CopyButton
157 content={`at://${props.params.repo}/${props.params.collection}`}
158 label="Copy AT URI"
159 />
160 </div>
161 </Show>
162
163 {/* Record Level */}
164 <Show when={props.params.rkey}>
165 <div class="group flex items-center justify-between 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">
166 <div class="flex basis-full items-center gap-2">
167 <Tooltip text="Record">
168 <span class="iconify lucide--file-json text-neutral-500 transition-colors duration-200 group-hover:text-neutral-700 dark:text-neutral-400 dark:group-hover:text-neutral-200"></span>
169 </Tooltip>
170 <span class="py-0.5 font-medium">{props.params.rkey}</span>
171 </div>
172 <CopyButton
173 content={`at://${props.params.repo}/${props.params.collection}/${props.params.rkey}`}
174 label="Copy AT URI"
175 />
176 </div>
177 </Show>
178 </div>
179 </nav>
180 );
181};