atproto explorer pdsls.dev
atproto tool
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};