export type ImageConfig = { image: HTMLImageElement | undefined; imageLink: string; imageReady: boolean; cropLeft: number; cropTop: number; cropRight: number; cropBottom: number; }; export class ImageConfigState { image = $state(undefined); imageLink = $state(""); imageReady = $state(false); cropLeft = $state(0); cropTop = $state(0); cropRight = $state(0); cropBottom = $state(0); #currentAbortController: AbortController | null = null; private cleanup() { if (this.#currentAbortController) { this.#currentAbortController.abort(); this.#currentAbortController = null; } if (this.image) { this.image.onload = null; this.image.onerror = null; this.image.src = ""; this.image = undefined; } } async uploadImageByLink(link: string): Promise { this.cleanup(); this.imageReady = false; this.imageLink = link; this.#currentAbortController = new AbortController(); const { signal } = this.#currentAbortController; return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = "anonymous"; const onFinished = () => { img.onload = null; img.onerror = null; }; img.onload = () => { if (signal.aborted) return; onFinished(); this.image = img; this.imageReady = true; resolve(); }; img.onerror = () => { if (signal.aborted) return; onFinished(); this.imageReady = false; reject(new Error(`Failed to load image: ${link}`)); }; signal.addEventListener( "abort", () => { onFinished(); img.src = ""; reject(new DOMException("Aborted", "AbortError")); }, { once: true }, ); img.src = link; }); } reset() { this.cleanup(); this.imageLink = ""; this.imageReady = false; this.cropLeft = 0; this.cropTop = 0; this.cropRight = 0; this.cropBottom = 0; } destroy() { this.cleanup(); } } export const imageConfigState = new ImageConfigState();