this repo has no descr,ription vt3e.cat

feat: queue image loads

vt3e.cat a700e88b a803a33f

verified
+76 -3
+12 -3
pkgs/web/src/components/Gallery/GalleryItem.vue
··· 5 5 import { CatVt3eGalleryImage } from '@vt3e/gallery' 6 6 7 7 import { DID } from '@/utils/links' 8 + import { enqueueLoad } from '@/utils/imageLoadQueue' 8 9 9 10 const props = defineProps<{ 10 11 item: CatVt3eGalleryImage.Main ··· 20 21 return props.item.width / props.item.height 21 22 }) 22 23 24 + const cid = computed(() => 25 + isLegacyBlob(props.item.image) ? props.item.image.cid : props.item.image.ref.$link, 26 + ) 27 + 23 28 const blobUrl = computed(() => { 24 29 const parts = [ 25 30 'https://pds.vt3e.cat/xrpc/com.atproto.sync.getBlob', 26 31 `?did=${DID}`, 27 - `&cid=${isLegacyBlob(props.item.image) ? props.item.image.cid : props.item.image.ref.$link}`, 32 + `&cid=${cid.value}`, 28 33 ] 29 34 return parts.join('') 30 35 }) 31 36 37 + const shortLabel = computed(() => cid.value.slice(0, 12)) 38 + 32 39 const blurhashData = computed(() => { 33 40 if (!props.item.blurhash) return null 34 41 try { ··· 86 93 } 87 94 } 88 95 96 + let cancelQueue: (() => void) | null = null 89 97 let observer: IntersectionObserver | null = null 90 98 91 99 onMounted(() => { ··· 94 102 observer = new IntersectionObserver( 95 103 (entries) => { 96 104 if (entries[0]?.isIntersecting) { 97 - loadImage() 105 + cancelQueue = enqueueLoad(shortLabel.value, loadImage) 98 106 observer?.disconnect() 99 107 } 100 108 }, ··· 106 114 107 115 onUnmounted(() => { 108 116 observer?.disconnect() 117 + cancelQueue?.() 109 118 if (imageUrl.value) URL.revokeObjectURL(imageUrl.value) 110 119 }) 111 120 </script> ··· 141 150 border-radius: 0.75rem; 142 151 overflow: hidden; 143 152 background-color: hsla(var(--surface0) / 0.5); 144 - transform: translateZ(0); /* try to force hardware accel */ 153 + transform: translateZ(0); 145 154 146 155 &:hover { 147 156 filter: brightness(1.1);
+64
pkgs/web/src/utils/imageLoadQueue.ts
··· 1 + const MAX_CONCURRENT = 1; 2 + 3 + type LoadFn = () => Promise<void>; 4 + interface QueueEntry { 5 + label: string; 6 + load: LoadFn; 7 + cancelled: boolean; 8 + } 9 + 10 + const pending: QueueEntry[] = []; 11 + let activeCount = 0; 12 + 13 + const status = () => `(active: ${activeCount}, pending: ${pending.length})`; 14 + 15 + function processNext() { 16 + while (activeCount < MAX_CONCURRENT) { 17 + const entry = pending.shift(); 18 + if (!entry) break; 19 + 20 + if (entry.cancelled) { 21 + console.debug("[gallery-queue] skip (cancelled):", entry.label); 22 + continue; 23 + } 24 + 25 + activeCount++; 26 + console.groupCollapsed(`[gallery-queue] ${entry.label}`); 27 + console.debug("started", status()); 28 + console.groupEnd(); 29 + 30 + entry 31 + .load() 32 + .then(() => { 33 + console.groupCollapsed(`[gallery-queue] ✓ ${entry.label}`); 34 + console.debug("completed", status()); 35 + console.groupEnd(); 36 + }) 37 + .catch((err) => { 38 + console.group(`[gallery-queue] ✗ ${entry.label}`); 39 + console.warn("failed", err); 40 + console.debug(status()); 41 + console.groupEnd(); 42 + }) 43 + .finally(() => { 44 + activeCount--; 45 + processNext(); 46 + }); 47 + } 48 + } 49 + 50 + export function enqueueLoad(label: string, load: LoadFn): () => void { 51 + const entry: QueueEntry = { label, load, cancelled: false }; 52 + pending.push(entry); 53 + 54 + console.groupCollapsed(`[gallery-queue] + ${entry.label}`); 55 + console.debug("enqueued", status()); 56 + console.groupEnd(); 57 + 58 + processNext(); 59 + 60 + return () => { 61 + entry.cancelled = true; 62 + console.debug(`[gallery-queue] − ${entry.label} (cancelled)`); 63 + }; 64 + }