your personal website on atproto - mirror blento.app
at card-command-bar 74 lines 2.2 kB view raw
1<script lang="ts"> 2 import { Canvas } from '@threlte/core'; 3 import { CineonToneMapping } from 'three'; 4 import type { ContentComponentProps } from '../types'; 5 import Model3DScene from './Model3DScene.svelte'; 6 import { getDidContext } from '$lib/website/context'; 7 import { getBlobURL } from '$lib/atproto'; 8 import type { Did } from '@atcute/lexicons'; 9 import { onMount } from 'svelte'; 10 11 let { item }: ContentComponentProps = $props(); 12 13 let isHovering = $state(false); 14 let objectUrl = $state<string | undefined>(undefined); 15 let isLoading = $state(false); 16 17 const did = getDidContext(); 18 19 // Fetch blob from PDS and create object URL (like VideoCard does) 20 onMount(async () => { 21 if (item.cardData.modelBlob?.$type === 'blob') { 22 isLoading = true; 23 try { 24 const pdsUrl = await getBlobURL({ did: did as Did, blob: item.cardData.modelBlob }); 25 const response = await fetch(pdsUrl); 26 if (!response.ok) throw new Error(response.statusText); 27 const blob = await response.blob(); 28 objectUrl = URL.createObjectURL(blob); 29 } catch (e) { 30 console.error('Failed to load 3D model:', e); 31 } finally { 32 isLoading = false; 33 } 34 } 35 }); 36 37 // Get the model URL from various sources 38 let modelUrl = $derived.by(() => { 39 // Local file (during editing before save) 40 if (item.cardData.modelFile?.objectUrl) { 41 return item.cardData.modelFile.objectUrl; 42 } 43 44 // Uploaded blob (after save) - use fetched object URL 45 if (item.cardData.modelBlob?.$type === 'blob') { 46 return objectUrl; 47 } 48 49 return undefined; 50 }); 51 52 let modelType = $derived(item.cardData.modelFile?.type || item.cardData.modelType || 'gltf') as 53 | 'gltf' 54 | 'stl' 55 | 'fbx'; 56</script> 57 58<div 59 class="absolute inset-0 h-full w-full" 60 role="img" 61 aria-label="3D model viewer" 62 onpointerenter={() => (isHovering = true)} 63 onpointerleave={() => (isHovering = false)} 64> 65 {#if modelUrl} 66 <Canvas toneMapping={CineonToneMapping}> 67 <Model3DScene path={modelUrl} hover={isHovering} {modelType} /> 68 </Canvas> 69 {:else if isLoading} 70 <div class="flex h-full items-center justify-center text-sm opacity-50">Loading model...</div> 71 {:else} 72 <div class="flex h-full items-center justify-center text-sm opacity-50">No model loaded</div> 73 {/if} 74</div>