121 lines
2.6 KiB
TypeScript
121 lines
2.6 KiB
TypeScript
import debug from 'debug';
|
|
import { DEBUG } from '$lib/debug';
|
|
|
|
const dbg = debug('kestrel:imageQueue');
|
|
|
|
class ImageQueue {
|
|
concurrency: number;
|
|
queue: {
|
|
element: HTMLElement;
|
|
id: string;
|
|
callback: () => Promise<void>;
|
|
check?: () => boolean;
|
|
}[] = [];
|
|
active = 0;
|
|
|
|
constructor(concurrency: number) {
|
|
this.concurrency = concurrency;
|
|
}
|
|
|
|
public add(
|
|
element: HTMLImageElement,
|
|
id: string,
|
|
callback: () => Promise<void>,
|
|
check?: () => boolean
|
|
) {
|
|
this.queue = this.queue.filter((e) => e.element !== element);
|
|
this.queue.push({ element, id, callback, check });
|
|
this.update();
|
|
}
|
|
|
|
private update() {
|
|
this.queue.sort((a, b) => {
|
|
const aBox = a.element.getBoundingClientRect();
|
|
const bBox = b.element.getBoundingClientRect();
|
|
const topDifference = aBox.top - bBox.top;
|
|
if (topDifference !== 0) {
|
|
return topDifference;
|
|
} else {
|
|
return aBox.left - bBox.left;
|
|
}
|
|
});
|
|
|
|
while (this.active < this.concurrency && this.queue.length) {
|
|
const nextIdx = this.queue.findIndex((e) => e.check && e.check()) || 0;
|
|
const next = this.queue.splice(nextIdx, 1)[0];
|
|
dbg(`Getting ${next.id}...`);
|
|
this.active += 1;
|
|
next.element.classList.add('image-loading');
|
|
if (DEBUG.imageQueueHalt) {
|
|
return;
|
|
}
|
|
next
|
|
.callback()
|
|
.then(() => {
|
|
dbg(`Loaded ${next.id}`);
|
|
})
|
|
.catch(() => {
|
|
dbg(`Failed to load ${next.id}...`);
|
|
})
|
|
.finally(() => {
|
|
this.active -= 1;
|
|
next.element.classList.remove('image-loading');
|
|
this.update();
|
|
});
|
|
}
|
|
|
|
dbg(
|
|
'Active: %d, Queue: %O',
|
|
this.active,
|
|
this.queue.map((e) => [e.element, e.id])
|
|
);
|
|
}
|
|
}
|
|
|
|
const imageQueue = new ImageQueue(2);
|
|
|
|
export function concurrentImage(element: HTMLImageElement, src: string) {
|
|
const bbox = element.getBoundingClientRect();
|
|
let visible =
|
|
bbox.top >= 0 &&
|
|
bbox.left >= 0 &&
|
|
bbox.bottom <= window.innerHeight &&
|
|
bbox.right <= window.innerWidth;
|
|
|
|
const observer = new IntersectionObserver((entries) => {
|
|
visible = entries.some((e) => e.isIntersecting);
|
|
});
|
|
observer.observe(element);
|
|
|
|
function queueSelf() {
|
|
element.classList.add('image-queued');
|
|
const loadSelf = () => {
|
|
element.classList.remove('image-queued');
|
|
return new Promise<void>((resolve, reject) => {
|
|
if (element.src === src) {
|
|
resolve();
|
|
return;
|
|
}
|
|
element.addEventListener('load', () => {
|
|
resolve();
|
|
});
|
|
element.addEventListener('error', () => {
|
|
reject();
|
|
});
|
|
element.src = src;
|
|
});
|
|
};
|
|
|
|
imageQueue.add(element, src, loadSelf, () => visible);
|
|
}
|
|
queueSelf();
|
|
|
|
return {
|
|
update(_src: string) {
|
|
queueSelf();
|
|
},
|
|
destroy() {
|
|
observer.disconnect();
|
|
}
|
|
};
|
|
}
|