atmosphere explorer
0
fork

Configure Feed

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

truncate large fields in record preview

Juliet b931a75a 057f5a4b

+70 -55
+69 -55
src/components/json.tsx
··· 1 1 import { isCid, isDid, isNsid, isResourceUri, Nsid } from "@atcute/lexicons/syntax"; 2 2 import { A, useNavigate, useParams } from "@solidjs/router"; 3 - import { createEffect, createSignal, ErrorBoundary, For, on, Show } from "solid-js"; 3 + import { 4 + createContext, 5 + createEffect, 6 + createSignal, 7 + ErrorBoundary, 8 + For, 9 + on, 10 + Show, 11 + useContext, 12 + } from "solid-js"; 4 13 import { resolveLexiconAuthority } from "../utils/api"; 5 14 import { hideMedia } from "../views/settings"; 6 15 import { pds } from "./navbar"; 7 16 import { addNotification, removeNotification } from "./notification"; 8 17 import VideoPlayer from "./video-player"; 9 18 19 + interface JSONContext { 20 + repo: string; 21 + truncate?: boolean; 22 + parentIsBlob?: boolean; 23 + } 24 + 25 + const JSONCtx = createContext<JSONContext>(); 26 + const useJSONCtx = () => useContext(JSONCtx)!; 27 + 10 28 interface AtBlob { 11 29 $type: string; 12 30 ref: { $link: string }; 13 31 mimeType: string; 14 32 } 15 33 16 - const JSONString = (props: { 17 - data: string; 18 - isType?: boolean; 19 - isLink?: boolean; 20 - parentIsBlob?: boolean; 21 - }) => { 34 + const isURL = 35 + URL.canParse ?? 36 + ((url, base) => { 37 + try { 38 + new URL(url, base); 39 + return true; 40 + } catch { 41 + return false; 42 + } 43 + }); 44 + 45 + const JSONString = (props: { data: string; isType?: boolean; isLink?: boolean }) => { 46 + const ctx = useJSONCtx(); 22 47 const navigate = useNavigate(); 23 48 const params = useParams(); 24 49 25 - const isURL = 26 - URL.canParse ?? 27 - ((url, base) => { 28 - try { 29 - new URL(url, base); 30 - return true; 31 - } catch { 32 - return false; 33 - } 34 - }); 35 - 36 50 const handleClick = async (lex: string) => { 37 51 try { 38 52 const [nsid, anchor] = lex.split("#"); ··· 49 63 setTimeout(() => removeNotification(id), 5000); 50 64 } 51 65 }; 66 + 67 + const MAX_LENGTH = 200; 68 + const isTruncated = () => ctx.truncate && props.data.length > MAX_LENGTH; 69 + const displayData = () => (isTruncated() ? props.data.slice(0, MAX_LENGTH) : props.data); 70 + const remainingChars = () => props.data.length - MAX_LENGTH; 52 71 53 72 return ( 54 73 <span> 55 74 " 56 - <For each={props.data.split(/(\s)/)}> 75 + <For each={displayData().split(/(\s)/)}> 57 76 {(part) => ( 58 77 <> 59 78 {isResourceUri(part) ? ··· 72 91 > 73 92 {part} 74 93 </button> 75 - : isCid(part) && props.isLink && props.parentIsBlob && params.repo ? 94 + : isCid(part) && props.isLink && ctx.parentIsBlob && params.repo ? 76 95 <A 77 96 class="text-blue-400 hover:underline active:underline" 78 97 rel="noopener" ··· 93 112 </> 94 113 )} 95 114 </For> 115 + <Show when={isTruncated()}> 116 + <span>…</span> 117 + </Show> 96 118 " 119 + <Show when={isTruncated()}> 120 + <span class="ml-1 text-neutral-500 dark:text-neutral-400"> 121 + (+{remainingChars().toLocaleString()}) 122 + </span> 123 + </Show> 97 124 </span> 98 125 ); 99 126 }; ··· 110 137 return <span>null</span>; 111 138 }; 112 139 113 - const JSONObject = (props: { 114 - data: { [x: string]: JSONType }; 115 - repo: string; 116 - parentIsBlob?: boolean; 117 - }) => { 140 + const JSONObject = (props: { data: { [x: string]: JSONType } }) => { 141 + const ctx = useJSONCtx(); 118 142 const params = useParams(); 119 143 const [hide, setHide] = createSignal( 120 144 localStorage.hideMedia === "true" || params.rkey === undefined, ··· 136 160 ); 137 161 138 162 const isBlob = props.data.$type === "blob"; 139 - const isBlobContext = isBlob || props.parentIsBlob; 163 + const isBlobContext = isBlob || ctx.parentIsBlob; 140 164 141 165 const Obj = ({ key, value }: { key: string; value: JSONType }) => { 142 166 const [show, setShow] = createSignal(true); ··· 172 196 "invisible h-0": !show(), 173 197 }} 174 198 > 175 - <JSONValue 176 - data={value} 177 - repo={props.repo} 178 - isType={key === "$type"} 179 - isLink={key === "$link"} 180 - parentIsBlob={isBlobContext} 181 - /> 199 + <JSONCtx.Provider value={{ ...ctx, parentIsBlob: isBlobContext }}> 200 + <JSONValueInner data={value} isType={key === "$type"} isLink={key === "$link"} /> 201 + </JSONCtx.Provider> 182 202 </span> 183 203 </span> 184 204 ); ··· 200 220 <Show when={blob.mimeType.startsWith("image/")}> 201 221 <img 202 222 class="h-auto max-h-48 max-w-48 object-contain sm:max-h-64 sm:max-w-64" 203 - src={`https://${pds()}/xrpc/com.atproto.sync.getBlob?did=${props.repo}&cid=${blob.ref.$link}`} 223 + src={`https://${pds()}/xrpc/com.atproto.sync.getBlob?did=${ctx.repo}&cid=${blob.ref.$link}`} 204 224 onLoad={() => setMediaLoaded(true)} 205 225 /> 206 226 </Show> 207 227 <Show when={blob.mimeType === "video/mp4"}> 208 228 <ErrorBoundary fallback={() => <span>Failed to load video</span>}> 209 229 <VideoPlayer 210 - did={props.repo} 230 + did={ctx.repo} 211 231 cid={blob.ref.$link} 212 232 onLoad={() => setMediaLoaded(true)} 213 233 /> ··· 241 261 return rawObj; 242 262 }; 243 263 244 - const JSONArray = (props: { data: JSONType[]; repo: string; parentIsBlob?: boolean }) => { 264 + const JSONArray = (props: { data: JSONType[] }) => { 245 265 return ( 246 266 <For each={props.data}> 247 267 {(value, index) => ( ··· 252 272 }} 253 273 > 254 274 <span class="ml-[1ch] w-full"> 255 - <JSONValue data={value} repo={props.repo} parentIsBlob={props.parentIsBlob} /> 275 + <JSONValueInner data={value} /> 256 276 </span> 257 277 </span> 258 278 )} ··· 260 280 ); 261 281 }; 262 282 263 - export const JSONValue = (props: { 264 - data: JSONType; 265 - repo: string; 266 - isType?: boolean; 267 - isLink?: boolean; 268 - parentIsBlob?: boolean; 269 - }) => { 283 + const JSONValueInner = (props: { data: JSONType; isType?: boolean; isLink?: boolean }) => { 270 284 const data = props.data; 271 285 if (typeof data === "string") 272 - return ( 273 - <JSONString 274 - data={data} 275 - isType={props.isType} 276 - isLink={props.isLink} 277 - parentIsBlob={props.parentIsBlob} 278 - /> 279 - ); 286 + return <JSONString data={data} isType={props.isType} isLink={props.isLink} />; 280 287 if (typeof data === "number") return <JSONNumber data={data} />; 281 288 if (typeof data === "boolean") return <JSONBoolean data={data} />; 282 289 if (data === null) return <JSONNull />; 283 - if (Array.isArray(data)) 284 - return <JSONArray data={data} repo={props.repo} parentIsBlob={props.parentIsBlob} />; 285 - return <JSONObject data={data} repo={props.repo} parentIsBlob={props.parentIsBlob} />; 290 + if (Array.isArray(data)) return <JSONArray data={data} />; 291 + return <JSONObject data={data} />; 292 + }; 293 + 294 + export const JSONValue = (props: { data: JSONType; repo: string; truncate?: boolean }) => { 295 + return ( 296 + <JSONCtx.Provider value={{ repo: props.repo, truncate: props.truncate }}> 297 + <JSONValueInner data={props.data} /> 298 + </JSONCtx.Provider> 299 + ); 286 300 }; 287 301 288 302 export type JSONType = string | number | boolean | null | { [x: string]: JSONType } | JSONType[];
+1
src/views/collection.tsx
··· 67 67 <JSONValue 68 68 data={props.record.record.value as JSONType} 69 69 repo={props.record.record.uri.split("/")[2]} 70 + truncate 70 71 /> 71 72 </span> 72 73 </Show>