Open History to see the last commit
1import type { EnvironmentId } from "@t3tools/contracts";
2import { type ReactNode, useState } from "react";
3
4import { useAssetUrlState } from "~/assets/assetUrls";
5import { observeIntersection } from "~/lib/observeIntersection";
6
7export function ProjectFaviconThumbnail(props: {
8 readonly cwd: string;
9 readonly environmentId: EnvironmentId;
10 readonly fallback: ReactNode;
11 readonly path: string;
12}) {
13 const [inView, setInView] = useState(() => typeof IntersectionObserver === "undefined");
14 const [loadedUrl, setLoadedUrl] = useState<string | null>(null);
15 const [failedUrl, setFailedUrl] = useState<string | null>(null);
16
17 const state = useAssetUrlState(
18 props.environmentId,
19 inView ? { _tag: "draft-workspace-file", cwd: props.cwd, path: props.path } : null,
20 );
21 const url = state._tag === "Success" ? state.url : null;
22
23 const showImage = url !== null && failedUrl !== url;
24 const imageLoaded = showImage && loadedUrl === url;
25
26 return (
27 <span
28 // Asset URLs require a server request, so defer acquisition until the row is visible.
29 ref={
30 inView
31 ? undefined
32 : (node) => {
33 if (!node) return;
34 return observeIntersection(node, (entry) => {
35 if (entry.isIntersecting) setInView(true);
36 });
37 }
38 }
39 className="relative flex size-8 shrink-0 items-center justify-center"
40 >
41 {!imageLoaded ? props.fallback : null}
42 {showImage ? (
43 <img
44 key={url}
45 alt=""
46 className={`absolute max-h-8 max-w-8 rounded-sm object-contain${imageLoaded ? "" : " invisible"}`}
47 decoding="async"
48 loading="lazy"
49 onError={() => setFailedUrl(url)}
50 onLoad={() => setLoadedUrl(url)}
51 src={url}
52 />
53 ) : null}
54 </span>
55 );
56}