/** * Image-loading helpers used by photo upload and slot assignment. * * Before this module: `useTemplate.assignImageToSlot` and `UploadTab.getImageSize` * each had their own inline `new Image() + onload + onerror` Promise wrapper, * and `App.handlePhotoEditComplete` had a third variant using callbacks * instead of a Promise. The three wrappers had subtly different error * handling — one rejected with a generic message, another logged to the * console, the third silently swallowed errors and used a fallback path. * * Centralizing here gives every caller the same behavior: a clean Promise * that resolves to dimensions or rejects with a descriptive Error. The * App.jsx photo-edit flow still uses the lower-level callback form because * its onload handler runs synchronously before its setEditingElement call * (timing matters for the Filerobot modal teardown sequence) — but the * other two consumers go through `loadImageDimensions`. */ /** * Load an image and resolve to its natural pixel dimensions. * * Resolves with `{ width, height }` from `naturalWidth`/`naturalHeight`, * falling back to `width`/`height` for the rare case where naturalWidth is 0 * (some Safari versions report this for SVG sources without intrinsic size). * * Rejects with a descriptive Error if the image fails to load. Callers * should `await` and use try/catch — not `.then(undefined, () => fallback)`, * which would silently mask bugs. * * @param {string} src - data:, blob:, or remote URL * @returns {Promise<{ width: number, height: number }>} */ export function loadImageDimensions(src) { return new Promise((resolve, reject) => { const img = new window.Image(); img.onload = () => resolve({ width: img.naturalWidth || img.width, height: img.naturalHeight || img.height, }); img.onerror = () => reject(new Error(`Failed to load image: ${truncate(src, 80)}`)); img.src = src; }); } /** * Truncate a URL for use in error messages. Long data: URLs in stack traces * blow up log readers; the first 80 chars are enough to identify the type. */ function truncate(s, n) { if (typeof s !== 'string') return String(s); return s.length > n ? `${s.slice(0, n)}…` : s; }