replies timeline only, appview-less bluesky client

set aspect ratios for uploaded media

ptr.pet 8658000a 0eaf2dff

verified
Changed files
+34 -6
src
+33 -5
src/components/PostComposer.svelte
··· 43 43 client.user?.did ? generateColorForDid(client.user?.did) : 'var(--nucleus-accent2)' 44 44 ); 45 45 46 + const getVideoDimensions = ( 47 + blobUrl: string 48 + ): Promise<Result<{ width: number; height: number }, string>> => 49 + new Promise((resolve) => { 50 + const video = document.createElement('video'); 51 + video.onloadedmetadata = () => { 52 + resolve(ok({ width: video.videoWidth, height: video.videoHeight })); 53 + }; 54 + video.onerror = (e) => resolve(err(String(e))); 55 + video.src = blobUrl; 56 + }); 57 + 46 58 const uploadVideo = async (blobUrl: string, mimeType: string) => { 47 59 const file = await (await fetch(blobUrl)).blob(); 48 60 return await client.uploadVideo(file, mimeType, (status) => { ··· 56 68 } 57 69 }); 58 70 }; 71 + 72 + const getImageDimensions = ( 73 + blobUrl: string 74 + ): Promise<Result<{ width: number; height: number }, string>> => 75 + new Promise((resolve) => { 76 + const img = new Image(); 77 + img.onload = () => resolve(ok({ width: img.width, height: img.height })); 78 + img.onerror = (e) => resolve(err(String(e))); 79 + img.src = blobUrl; 80 + }); 81 + 59 82 const uploadImage = async (blobUrl: string) => { 60 83 const file = await (await fetch(blobUrl)).blob(); 61 84 return await client.uploadBlob(file, (progress) => { ··· 77 100 const images = _state.attachedMedia.images; 78 101 let uploadedImages: typeof images = []; 79 102 for (const image of images) { 80 - const upload = _state.blobsState.get((image.image as AtpBlob<string>).ref.$link); 103 + const blobUrl = (image.image as AtpBlob<string>).ref.$link; 104 + const upload = _state.blobsState.get(blobUrl); 81 105 if (!upload || upload.state !== 'uploaded') continue; 106 + const size = await getImageDimensions(blobUrl); 107 + if (size.ok) image.aspectRatio = size.value; 82 108 uploadedImages.push({ 83 109 ...image, 84 110 image: upload.blob ··· 91 117 images: uploadedImages 92 118 }; 93 119 } else if (_state.attachedMedia?.$type === 'app.bsky.embed.video') { 94 - const upload = _state.blobsState.get( 95 - (_state.attachedMedia.video as AtpBlob<string>).ref.$link 96 - ); 97 - if (upload && upload.state === 'uploaded') 120 + const blobUrl = (_state.attachedMedia.video as AtpBlob<string>).ref.$link; 121 + const upload = _state.blobsState.get(blobUrl); 122 + if (upload && upload.state === 'uploaded') { 123 + const size = await getVideoDimensions(blobUrl); 124 + if (size.ok) _state.attachedMedia.aspectRatio = size.value; 98 125 media = { 99 126 ..._state.attachedMedia, 100 127 $type: 'app.bsky.embed.video', 101 128 video: upload.blob 102 129 }; 130 + } 103 131 } 104 132 // console.log('media', media); 105 133
+1 -1
src/lib/at/client.svelte.ts
··· 359 359 }, 360 360 (uploaded, total) => onStatus?.({ stage: 'uploading', progress: uploaded / total }) 361 361 ); 362 - if (!uploadResult.ok) return err(`failed to upload video: ${uploadResult.error.message}`); 362 + if (!uploadResult.ok) return err(`failed to upload video: ${uploadResult.error}`); 363 363 const jobStatus = uploadResult.value; 364 364 let videoBlobRef: AtpBlob<string> = jobStatus.blob; 365 365