atmosphere explorer
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

rework indent collapse and add key hash linking

Juliet c9e52c5d db993dd8

+68 -45
+63 -40
src/components/json.tsx
··· 1 1 import { isCid, isDid, isNsid, isResourceUri, Nsid } from "@atcute/lexicons/syntax"; 2 - import { A, useNavigate, useParams } from "@solidjs/router"; 2 + import { A, useLocation, useNavigate, useParams } from "@solidjs/router"; 3 3 import { 4 4 createContext, 5 5 createEffect, ··· 28 28 parentIsBlob?: boolean; 29 29 newTab?: boolean; 30 30 hideBlobs?: boolean; 31 + path?: string; 31 32 } 32 33 33 34 const JSONCtx = createContext<JSONContext>(); ··· 159 160 parentIsBlob?: boolean; 160 161 }) => { 161 162 const ctx = useJSONCtx(); 163 + const location = useLocation(); 162 164 const [show, setShow] = createSignal(true); 163 165 const isBlobContext = props.parentIsBlob ?? ctx.parentIsBlob; 164 166 167 + const labelStr = () => { 168 + const l = String(props.label); 169 + return l.startsWith("#") ? l.slice(1) : l; 170 + }; 171 + const fullPath = () => (ctx.path ? `${ctx.path}.${labelStr()}` : labelStr()); 172 + const isHighlighted = () => location.hash === `#record:${fullPath()}`; 173 + 174 + createEffect(() => { 175 + if (isHighlighted()) { 176 + requestAnimationFrame(() => { 177 + document 178 + .getElementById(`key-${fullPath()}`) 179 + ?.scrollIntoView({ behavior: "instant", block: "center" }); 180 + }); 181 + } 182 + }); 183 + 165 184 const isObject = () => props.value === Object(props.value); 166 185 const isEmpty = () => 167 186 Array.isArray(props.value) ? 168 187 (props.value as JSONType[]).length === 0 169 188 : Object.keys(props.value as object).length === 0; 170 - const isCollapsible = () => (isObject() && !isEmpty()) || typeof props.value === "string"; 171 189 const summary = () => { 172 - if (typeof props.value === "string") { 173 - const len = props.value.length; 174 - return `${len.toLocaleString()} ${len === 1 ? "char" : "chars"}`; 175 - } 176 190 if (Array.isArray(props.value)) { 177 191 const len = (props.value as JSONType[]).length; 178 192 return `[ ${len} ${len === 1 ? "item" : "items"} ]`; ··· 183 197 184 198 return ( 185 199 <span 200 + id={`key-${fullPath()}`} 186 201 classList={{ 187 202 "group/indent flex gap-x-1 w-full": true, 188 203 "flex-col": isObject() && !isEmpty(), 189 204 }} 190 205 > 191 - <button 192 - class="group/clip relative flex size-fit shrink-0 items-center gap-x-1 wrap-anywhere" 193 - classList={{ 194 - "max-w-[40%] sm:max-w-[50%]": props.maxWidth !== undefined && show(), 195 - "text-indigo-500 hover:text-indigo-700 active:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300 dark:active:text-indigo-200": 196 - !props.isIndex, 197 - "text-violet-500 hover:text-violet-700 active:text-violet-800 dark:text-violet-400 dark:hover:text-violet-300 dark:active:text-violet-200": 198 - props.isIndex, 199 - }} 200 - onclick={() => isCollapsible() && setShow(!show())} 206 + <span 207 + class="relative flex size-fit shrink-0 items-center gap-x-1 wrap-anywhere" 208 + classList={{ "max-w-[40%] sm:max-w-[50%]": props.maxWidth !== undefined && show() }} 201 209 > 202 - <Show when={isCollapsible()}> 203 - <span 204 - classList={{ 205 - "dark:bg-dark-500 absolute w-4 text-neutral-500 dark:text-neutral-400 flex items-center -left-4 bg-neutral-100 text-sm": true, 206 - "hidden group-hover/clip:flex": show(), 207 - }} 208 - > 209 - {show() ? 210 - <span class="iconify lucide--chevron-down"></span> 211 - : <span class="iconify lucide--chevron-right"></span>} 210 + <a 211 + href={`#record:${fullPath()}`} 212 + class="group/key rounded" 213 + classList={{ 214 + "text-indigo-500 hover:text-indigo-700 active:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300 dark:active:text-indigo-200": 215 + !props.isIndex && !isHighlighted(), 216 + "text-violet-500 hover:text-violet-700 active:text-violet-800 dark:text-violet-400 dark:hover:text-violet-300 dark:active:text-violet-200": 217 + props.isIndex && !isHighlighted(), 218 + "bg-indigo-200 text-indigo-700 dark:bg-indigo-500/60 dark:text-indigo-200": 219 + isHighlighted() && !props.isIndex, 220 + "bg-violet-200 text-violet-700 dark:bg-violet-500/60 dark:text-violet-200": 221 + isHighlighted() && props.isIndex, 222 + }} 223 + > 224 + <span class="absolute top-1/2 -left-3.5 flex -translate-y-1/2 items-center text-xs text-neutral-500 opacity-0 transition-opacity group-hover/key:opacity-100 dark:text-neutral-400"> 225 + <span class="iconify lucide--link"></span> 212 226 </span> 213 - </Show> 214 - <span> 215 227 {props.label} 216 228 <span class="text-neutral-500 dark:text-neutral-400">:</span> 217 - </span> 229 + </a> 218 230 <Show when={!show() && summary()}> 219 - <span class="absolute left-full ml-1 whitespace-nowrap text-neutral-400 dark:text-neutral-500"> 231 + <button 232 + type="button" 233 + class="flex items-center gap-0.5 rounded bg-neutral-200 px-1 py-0.5 text-xs whitespace-nowrap text-neutral-500 hover:bg-neutral-300 hover:text-neutral-700 dark:bg-neutral-700 dark:text-neutral-400 dark:hover:bg-neutral-600 dark:hover:text-neutral-200" 234 + onclick={() => setShow(true)} 235 + > 236 + <span class="iconify lucide--chevron-right"></span> 220 237 {summary()} 221 - </span> 238 + </button> 222 239 </Show> 223 - </button> 240 + </span> 224 241 <span 225 242 classList={{ 226 243 "self-center": !isObject() || isEmpty(), 227 - "pl-[calc(2ch-0.5px)] border-l-[0.5px] border-neutral-500/50 dark:border-neutral-400/50 has-hover:group-hover/indent:border-neutral-700 transition-colors dark:has-hover:group-hover/indent:border-neutral-400": 228 - isObject() && !isEmpty(), 244 + "relative pl-[2ch]": isObject() && !isEmpty(), 229 245 "invisible h-0 overflow-hidden": !show(), 230 246 }} 231 247 > 232 - <JSONCtx.Provider value={{ ...ctx, parentIsBlob: isBlobContext }}> 248 + <Show when={isObject() && !isEmpty()}> 249 + <span 250 + class="group/fold absolute inset-y-0 left-0 z-10 flex w-4 -translate-x-1/2 items-center justify-center" 251 + onclick={() => setShow(!show())} 252 + > 253 + <span class="h-full w-px bg-neutral-300 transition-colors group-hover/fold:bg-neutral-600 dark:bg-neutral-600 dark:group-hover/fold:bg-neutral-300" /> 254 + </span> 255 + </Show> 256 + <JSONCtx.Provider value={{ ...ctx, parentIsBlob: isBlobContext, path: fullPath() }}> 233 257 <JSONValueInner 234 258 data={props.value} 235 259 isType={props.isType} ··· 294 318 295 319 createEffect(() => { 296 320 if (!expanded()) return; 297 - const handler = (e: KeyboardEvent) => { if (e.key === "Escape") setExpanded(false); }; 321 + const handler = (e: KeyboardEvent) => { 322 + if (e.key === "Escape") setExpanded(false); 323 + }; 298 324 window.addEventListener("keydown", handler); 299 325 onCleanup(() => window.removeEventListener("keydown", handler)); 300 326 }); ··· 339 365 class="fixed inset-0 z-50 flex cursor-zoom-out items-center justify-center bg-black/80" 340 366 onclick={() => setExpanded(false)} 341 367 > 342 - <img 343 - class="max-h-screen max-w-screen object-contain" 344 - src={imageUrl()} 345 - /> 368 + <img class="max-h-screen max-w-screen object-contain" src={imageUrl()} /> 346 369 </div> 347 370 </Portal> 348 371 </Show>
+1 -1
src/components/lexicon-schema.tsx
··· 475 475 const hasDefContent = () => props.def.refs || props.def.items || hasConstraints(props.def); 476 476 477 477 return ( 478 - <div class="flex flex-col gap-3" id={`def-${props.name}`}> 478 + <div class="flex flex-col gap-3 scroll-mt-4" id={`def-${props.name}`}> 479 479 <div class="group flex items-center gap-2"> 480 480 <a href={`#schema:${props.name}`} class="relative text-lg font-semibold hover:underline"> 481 481 <span class="iconify lucide--link absolute top-1/2 -left-6 -translate-y-1/2 text-base opacity-0 transition-opacity group-hover:opacity-100" />
+2 -1
src/views/record.tsx
··· 385 385 }) => { 386 386 const isActive = () => { 387 387 if (!location.hash && props.tab === "record") return true; 388 + if (props.tab === "record") return location.hash.startsWith("#record"); 388 389 if (location.hash === `#${props.tab}`) return true; 389 390 if (props.tab === "schema" && location.hash.startsWith("#schema:")) return true; 390 391 return false; ··· 548 549 </MenuProvider> 549 550 </div> 550 551 </div> 551 - <Show when={!location.hash || location.hash === "#record"}> 552 + <Show when={!location.hash || location.hash.startsWith("#record")}> 552 553 <div class="w-full max-w-screen min-w-full px-2 font-mono text-xs wrap-anywhere whitespace-pre-wrap sm:w-max sm:text-sm md:max-w-3xl"> 553 554 <JSONValue data={record()?.value as any} repo={record()!.uri.split("/")[2]} /> 554 555 </div>
+2 -3
src/views/repo.tsx
··· 105 105 if (!!error() && props.tab === "identity") return true; 106 106 return false; 107 107 } 108 - if (props.tab === "collections") 109 - return location.hash === "#collections" || location.hash.startsWith("#collections:"); 108 + if (props.tab === "collections") return location.hash.startsWith("#collections"); 110 109 return location.hash === `#${props.tab}`; 111 110 }; 112 111 ··· 464 463 return ( 465 464 <div 466 465 id={`collection-${authority}`} 467 - class="group flex items-start gap-2 rounded-lg p-1 transition-colors" 466 + class="group flex items-start gap-2 rounded-lg p-1 transition-colors scroll-mt-4" 468 467 classList={{ 469 468 "dark:hover:bg-dark-300 hover:bg-neutral-200": !isHighlighted(), 470 469 "bg-blue-100 dark:bg-blue-500/25": isHighlighted(),