tangled
alpha
login
or
join now
ansxor.ca
/
pdsls
forked from
pds.ls/pdsls
0
fork
atom
atmosphere explorer
0
fork
atom
overview
issues
pulls
pipelines
fetch image blob and create object url
handle.invalid
1 month ago
db027778
1a7d02e7
verified
This commit was signed with the committer's
known signature
.
handle.invalid
SSH Key Fingerprint:
SHA256:mBrT4x0JdzLpbVR95g1hjI1aaErfC02kmLRkPXwsYCk=
+67
-37
2 changed files
expand all
collapse all
unified
split
src
components
json.tsx
video-player.tsx
+59
-34
src/components/json.tsx
···
7
7
ErrorBoundary,
8
8
For,
9
9
on,
10
10
+
onCleanup,
11
11
+
onMount,
10
12
Show,
11
13
useContext,
12
14
} from "solid-js";
···
260
262
!ctx.hideBlobs &&
261
263
(blob.mimeType.startsWith("image/") || blob.mimeType === "video/mp4");
262
264
263
263
-
const MediaDisplay = () => (
264
264
-
<div>
265
265
-
<span class="group/media relative flex w-fit">
266
266
-
<Show when={!hide()}>
267
267
-
<Show when={blob.mimeType.startsWith("image/")}>
268
268
-
<img
269
269
-
class="h-auto max-h-48 max-w-48 object-contain sm:max-h-64 sm:max-w-64"
270
270
-
src={`https://${pds()}/xrpc/com.atproto.sync.getBlob?did=${ctx.repo}&cid=${blob.ref.$link}`}
271
271
-
onLoad={() => setMediaLoaded(true)}
272
272
-
/>
273
273
-
</Show>
274
274
-
<Show when={blob.mimeType === "video/mp4"}>
275
275
-
<ErrorBoundary fallback={() => <span>Failed to load video</span>}>
276
276
-
<VideoPlayer
277
277
-
did={ctx.repo}
278
278
-
cid={blob.ref.$link}
265
265
+
const MediaDisplay = () => {
266
266
+
const [imageObjectUrl, setImageObjectUrl] = createSignal<string>();
267
267
+
268
268
+
onMount(() => {
269
269
+
if (blob.mimeType.startsWith("image/")) {
270
270
+
const fetchImage = async () => {
271
271
+
const res = await fetch(
272
272
+
`https://${pds()}/xrpc/com.atproto.sync.getBlob?did=${ctx.repo}&cid=${blob.ref.$link}`,
273
273
+
);
274
274
+
if (!res.ok) throw new Error(res.statusText);
275
275
+
const blobData = await res.blob();
276
276
+
const url = URL.createObjectURL(blobData);
277
277
+
setImageObjectUrl(url);
278
278
+
};
279
279
+
fetchImage().catch((err) => console.error("Failed to load image:", err));
280
280
+
}
281
281
+
});
282
282
+
283
283
+
onCleanup(() => {
284
284
+
if (imageObjectUrl()) URL.revokeObjectURL(imageObjectUrl()!);
285
285
+
});
286
286
+
287
287
+
return (
288
288
+
<div>
289
289
+
<span class="group/media relative flex w-fit">
290
290
+
<Show when={!hide()}>
291
291
+
<Show when={blob.mimeType.startsWith("image/") && imageObjectUrl()}>
292
292
+
<img
293
293
+
class="h-auto max-h-48 max-w-48 object-contain sm:max-h-64 sm:max-w-64"
294
294
+
src={imageObjectUrl()}
279
295
onLoad={() => setMediaLoaded(true)}
280
296
/>
281
281
-
</ErrorBoundary>
297
297
+
</Show>
298
298
+
<Show when={blob.mimeType === "video/mp4"}>
299
299
+
<ErrorBoundary fallback={() => <span>Failed to load video</span>}>
300
300
+
<VideoPlayer
301
301
+
did={ctx.repo}
302
302
+
cid={blob.ref.$link}
303
303
+
onLoad={() => setMediaLoaded(true)}
304
304
+
/>
305
305
+
</ErrorBoundary>
306
306
+
</Show>
307
307
+
<Show when={mediaLoaded()}>
308
308
+
<button
309
309
+
onclick={() => setHide(true)}
310
310
+
class="absolute top-1 right-1 flex items-center rounded-lg bg-neutral-700/70 p-1.5 text-white opacity-0 backdrop-blur-sm transition-opacity group-hover/media:opacity-100 hover:bg-neutral-700 active:bg-neutral-800 dark:bg-neutral-100/70 dark:text-neutral-900 dark:hover:bg-neutral-100 dark:active:bg-neutral-200"
311
311
+
>
312
312
+
<span class="iconify lucide--eye-off text-base"></span>
313
313
+
</button>
314
314
+
</Show>
282
315
</Show>
283
283
-
<Show when={mediaLoaded()}>
316
316
+
<Show when={hide()}>
284
317
<button
285
285
-
onclick={() => setHide(true)}
286
286
-
class="absolute top-1 right-1 flex items-center rounded-lg bg-neutral-700/70 p-1.5 text-white opacity-0 backdrop-blur-sm transition-opacity group-hover/media:opacity-100 hover:bg-neutral-700 active:bg-neutral-800 dark:bg-neutral-100/70 dark:text-neutral-900 dark:hover:bg-neutral-100 dark:active:bg-neutral-200"
318
318
+
onclick={() => setHide(false)}
319
319
+
class="flex items-center gap-1 rounded-md bg-neutral-200 px-2 py-1.5 text-sm transition-colors hover:bg-neutral-300 active:bg-neutral-400 dark:bg-neutral-700 dark:hover:bg-neutral-600 dark:active:bg-neutral-500"
287
320
>
288
288
-
<span class="iconify lucide--eye-off text-base"></span>
321
321
+
<span class="iconify lucide--image"></span>
322
322
+
<span class="font-sans">Show media</span>
289
323
</button>
290
324
</Show>
291
291
-
</Show>
292
292
-
<Show when={hide()}>
293
293
-
<button
294
294
-
onclick={() => setHide(false)}
295
295
-
class="flex items-center gap-1 rounded-md bg-neutral-200 px-2 py-1.5 text-sm transition-colors hover:bg-neutral-300 active:bg-neutral-400 dark:bg-neutral-700 dark:hover:bg-neutral-600 dark:active:bg-neutral-500"
296
296
-
>
297
297
-
<span class="iconify lucide--image"></span>
298
298
-
<span class="font-sans">Show media</span>
299
299
-
</button>
300
300
-
</Show>
301
301
-
</span>
302
302
-
</div>
303
303
-
);
325
325
+
</span>
326
326
+
</div>
327
327
+
);
328
328
+
};
304
329
305
330
if (blob.$type === "blob") {
306
331
return (
+8
-3
src/components/video-player.tsx
···
1
1
-
import { onMount } from "solid-js";
1
1
+
import { onCleanup, onMount } from "solid-js";
2
2
import { pds } from "./navbar";
3
3
4
4
export interface VideoPlayerProps {
···
9
9
10
10
const VideoPlayer = (props: VideoPlayerProps) => {
11
11
let video!: HTMLVideoElement;
12
12
+
let objectUrl: string | undefined;
12
13
13
14
onMount(async () => {
14
15
// thanks bf <3
···
17
18
);
18
19
if (!res.ok) throw new Error(res.statusText);
19
20
const blob = await res.blob();
20
20
-
const url = URL.createObjectURL(blob);
21
21
-
if (video) video.src = url;
21
21
+
objectUrl = URL.createObjectURL(blob);
22
22
+
if (video) video.src = objectUrl;
23
23
+
});
24
24
+
25
25
+
onCleanup(() => {
26
26
+
if (objectUrl) URL.revokeObjectURL(objectUrl);
22
27
});
23
28
24
29
return (