From caef8003faebc0e3ccdb288cdb88d91ceb756028 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 25 Nov 2025 01:48:02 +0100 Subject: [PATCH] [FEAT] Bug fix --- package-lock.json | 124 +++++++++---------- src/App.vue | 47 ++++--- src/composables/useLayers.ts | 60 +++++---- src/composables/useSprites.ts | 224 +++++++++++++++++++--------------- 4 files changed, 255 insertions(+), 200 deletions(-) diff --git a/package-lock.json b/package-lock.json index 07d10d9..689bccd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2084,39 +2084,39 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.24", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.24.tgz", - "integrity": "sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==", + "version": "3.5.25", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.25.tgz", + "integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==", "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", - "@vue/shared": "3.5.24", + "@vue/shared": "3.5.25", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.24", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.24.tgz", - "integrity": "sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==", + "version": "3.5.25", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.25.tgz", + "integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.24", - "@vue/shared": "3.5.24" + "@vue/compiler-core": "3.5.25", + "@vue/shared": "3.5.25" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.24", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.24.tgz", - "integrity": "sha512-8EG5YPRgmTB+YxYBM3VXy8zHD9SWHUJLIGPhDovo3Z8VOgvP+O7UP5vl0J4BBPWYD9vxtBabzW1EuEZ+Cqs14g==", + "version": "3.5.25", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.25.tgz", + "integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==", "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", - "@vue/compiler-core": "3.5.24", - "@vue/compiler-dom": "3.5.24", - "@vue/compiler-ssr": "3.5.24", - "@vue/shared": "3.5.24", + "@vue/compiler-core": "3.5.25", + "@vue/compiler-dom": "3.5.25", + "@vue/compiler-ssr": "3.5.25", + "@vue/shared": "3.5.25", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", @@ -2124,13 +2124,13 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.24", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.24.tgz", - "integrity": "sha512-trOvMWNBMQ/odMRHW7Ae1CdfYx+7MuiQu62Jtu36gMLXcaoqKvAyh+P73sYG9ll+6jLB6QPovqoKGGZROzkFFg==", + "version": "3.5.25", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.25.tgz", + "integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.24", - "@vue/shared": "3.5.24" + "@vue/compiler-dom": "3.5.25", + "@vue/shared": "3.5.25" } }, "node_modules/@vue/compiler-vue2": { @@ -2240,53 +2240,53 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.24", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz", - "integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==", + "version": "3.5.25", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.25.tgz", + "integrity": "sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.24" + "@vue/shared": "3.5.25" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.24", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.24.tgz", - "integrity": "sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==", + "version": "3.5.25", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.25.tgz", + "integrity": "sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.24", - "@vue/shared": "3.5.24" + "@vue/reactivity": "3.5.25", + "@vue/shared": "3.5.25" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.24", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.24.tgz", - "integrity": "sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==", + "version": "3.5.25", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.25.tgz", + "integrity": "sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.24", - "@vue/runtime-core": "3.5.24", - "@vue/shared": "3.5.24", + "@vue/reactivity": "3.5.25", + "@vue/runtime-core": "3.5.25", + "@vue/shared": "3.5.25", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.24", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.24.tgz", - "integrity": "sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==", + "version": "3.5.25", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.25.tgz", + "integrity": "sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.24", - "@vue/shared": "3.5.24" + "@vue/compiler-ssr": "3.5.25", + "@vue/shared": "3.5.25" }, "peerDependencies": { - "vue": "3.5.24" + "vue": "3.5.25" } }, "node_modules/@vue/shared": { - "version": "3.5.24", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.24.tgz", - "integrity": "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==", + "version": "3.5.25", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.25.tgz", + "integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==", "license": "MIT" }, "node_modules/@vue/tsconfig": { @@ -2374,9 +2374,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.30", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz", - "integrity": "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==", + "version": "2.8.31", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz", + "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2473,9 +2473,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001756", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz", - "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==", + "version": "1.0.30001757", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", + "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", "dev": true, "funding": [ { @@ -2666,9 +2666,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.259", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz", - "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==", + "version": "1.5.260", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.260.tgz", + "integrity": "sha512-ov8rBoOBhVawpzdre+Cmz4FB+y66Eqrk6Gwqd8NGxuhv99GQ8XqMAr351KEkOt7gukXWDg6gJWEMKgL2RLMPtA==", "dev": true, "license": "ISC" }, @@ -4885,16 +4885,16 @@ "license": "MIT" }, "node_modules/vue": { - "version": "3.5.24", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.24.tgz", - "integrity": "sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==", + "version": "3.5.25", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.25.tgz", + "integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.24", - "@vue/compiler-sfc": "3.5.24", - "@vue/runtime-dom": "3.5.24", - "@vue/server-renderer": "3.5.24", - "@vue/shared": "3.5.24" + "@vue/compiler-dom": "3.5.25", + "@vue/compiler-sfc": "3.5.25", + "@vue/runtime-dom": "3.5.25", + "@vue/server-renderer": "3.5.25", + "@vue/shared": "3.5.25" }, "peerDependencies": { "typescript": "*" diff --git a/src/App.vue b/src/App.vue index ee03b0f..5d73622 100644 --- a/src/App.vue +++ b/src/App.vue @@ -53,7 +53,7 @@
-

Upload sprites

+

Upload sprites or single image

Drag and drop images or import from JSON

@@ -65,7 +65,7 @@

Create spritesheets for your game development and animation projects with our completely free, open-source Spritesheet generator.

This powerful online tool lets you upload individual sprite images and automatically arrange them into optimized sprite sheets with customizable layouts - perfect for indie developers, animators, and studios of any size.

Key features of this sprite editor

-
  • +
    • Free sprite editor: Edit, organize, and optimize your game sprites directly in your browser
    • Automatic spritesheet generation: Convert multiple PNG, JPG, or GIF images into efficient sprite atlases
    • Customizable grid layouts: Adjust spacing, padding, and arrangement for pixel-perfect results
    • @@ -74,7 +74,7 @@
    • Zero installation required: No downloads - use our web-based sprite sheet maker instantly
    • Batch processing: Upload and process multiple sprites simultaneously
    • Export options: Download spritesheet as PNG, JPG, GIF, ZIP or JSON.
    • - +

    @@ -392,20 +392,29 @@ if (files.length === 1 && files[0].type.startsWith('image/')) { const file = files[0]; - const url = URL.createObjectURL(file); + const reader = new FileReader(); + reader.onload = e => { + const url = e.target?.result as string; + const img = new Image(); + img.onload = () => { + if (confirm('This looks like it might be a spritesheet. Would you like to split it into individual sprites?')) { + spritesheetImageUrl.value = url; + spritesheetImageFile.value = file; + isSpritesheetSplitterOpen.value = true; + return; + } - const img = new Image(); - img.onload = () => { - if (confirm('This looks like it might be a spritesheet. Would you like to split it into individual sprites?')) { - spritesheetImageUrl.value = url; - spritesheetImageFile.value = file; - isSpritesheetSplitterOpen.value = true; - return; - } - - processImageFiles([file]); + processImageFiles([file]); + }; + img.onerror = () => { + console.error('Failed to load image:', file.name); + }; + img.src = url; }; - img.src = url; + reader.onerror = () => { + console.error('Failed to read image file:', file.name); + }; + reader.readAsDataURL(file); return; } @@ -439,10 +448,12 @@ const closeSpritesheetSplitter = () => { isSpritesheetSplitterOpen.value = false; - if (spritesheetImageUrl.value) { - URL.revokeObjectURL(spritesheetImageUrl.value); - spritesheetImageUrl.value = ''; + if (spritesheetImageUrl.value && spritesheetImageUrl.value.startsWith('blob:')) { + try { + URL.revokeObjectURL(spritesheetImageUrl.value); + } catch {} } + spritesheetImageUrl.value = ''; spritesheetImageFile.value = null; }; diff --git a/src/composables/useLayers.ts b/src/composables/useLayers.ts index c9bc14d..5e083a5 100644 --- a/src/composables/useLayers.ts +++ b/src/composables/useLayers.ts @@ -137,37 +137,53 @@ export const useLayers = () => { } catch {} } - const url = URL.createObjectURL(file); - const img = new Image(); - img.onload = () => { - l.sprites[i] = { id: old.id, file, img, url, width: img.width, height: img.height, x: old.x, y: old.y }; + const reader = new FileReader(); + reader.onload = e => { + const url = e.target?.result as string; + const img = new Image(); + img.onload = () => { + l.sprites[i] = { id: old.id, file, img, url, width: img.width, height: img.height, x: old.x, y: old.y }; + }; + img.onerror = () => { + console.error('Failed to load replacement image:', file.name); + }; + img.src = url; }; - img.onerror = () => { - URL.revokeObjectURL(url); + reader.onerror = () => { + console.error('Failed to read replacement image file:', file.name); }; - img.src = url; + reader.readAsDataURL(file); }; const addSprite = (file: File) => { const l = activeLayer.value; if (!l) return; - const url = URL.createObjectURL(file); - const img = new Image(); - img.onload = () => { - const next: Sprite = { - id: crypto.randomUUID(), - file, - img, - url, - width: img.width, - height: img.height, - x: 0, - y: 0, + const reader = new FileReader(); + reader.onload = e => { + const url = e.target?.result as string; + const img = new Image(); + img.onload = () => { + const next: Sprite = { + id: crypto.randomUUID(), + file, + img, + url, + width: img.width, + height: img.height, + x: 0, + y: 0, + }; + l.sprites = [...l.sprites, next]; }; - l.sprites = [...l.sprites, next]; + img.onerror = () => { + console.error('Failed to load sprite image:', file.name); + }; + img.src = url; }; - img.onerror = () => URL.revokeObjectURL(url); - img.src = url; + reader.onerror = () => { + console.error('Failed to read sprite image file:', file.name); + }; + reader.readAsDataURL(file); }; const processImageFiles = async (files: File[]) => { diff --git a/src/composables/useSprites.ts b/src/composables/useSprites.ts index edd912c..e1c8f40 100644 --- a/src/composables/useSprites.ts +++ b/src/composables/useSprites.ts @@ -87,129 +87,157 @@ export const useSprites = () => { const old = sprites.value[i]; revokeIfBlob(old.url); - const url = URL.createObjectURL(file); - const img = new Image(); - img.onload = () => { - const next: Sprite = { - id: old.id, - file, - img, - url, - width: img.width, - height: img.height, - x: old.x, - y: old.y, + const reader = new FileReader(); + reader.onload = e => { + const url = e.target?.result as string; + const img = new Image(); + img.onload = () => { + const next: Sprite = { + id: old.id, + file, + img, + url, + width: img.width, + height: img.height, + x: old.x, + y: old.y, + }; + const arr = [...sprites.value]; + arr[i] = next; + sprites.value = arr; }; - const arr = [...sprites.value]; - arr[i] = next; - sprites.value = arr; + img.onerror = () => { + console.error('Failed to load replacement image:', file.name); + }; + img.src = url; }; - img.onerror = () => { - console.error('Failed to load replacement image:', file.name); - URL.revokeObjectURL(url); + reader.onerror = () => { + console.error('Failed to read replacement image file:', file.name); }; - img.src = url; + reader.readAsDataURL(file); }; const addSprite = (file: File) => { - const url = URL.createObjectURL(file); - const img = new Image(); - img.onload = () => { - const s: Sprite = { - id: crypto.randomUUID(), - file, - img, - url, - width: img.width, - height: img.height, - x: 0, - y: 0, + const reader = new FileReader(); + reader.onload = e => { + const url = e.target?.result as string; + const img = new Image(); + img.onload = () => { + const s: Sprite = { + id: crypto.randomUUID(), + file, + img, + url, + width: img.width, + height: img.height, + x: 0, + y: 0, + }; + sprites.value = [...sprites.value, s]; }; - sprites.value = [...sprites.value, s]; + img.onerror = () => { + console.error('Failed to load new sprite image:', file.name); + }; + img.src = url; }; - img.onerror = () => { - console.error('Failed to load new sprite image:', file.name); - URL.revokeObjectURL(url); + reader.onerror = () => { + console.error('Failed to read sprite image file:', file.name); }; - img.src = url; + reader.readAsDataURL(file); }; const addSpriteWithResize = (file: File) => { - const url = URL.createObjectURL(file); - const img = new Image(); - img.onload = () => { - const { maxWidth, maxHeight } = getMaxDimensions(sprites.value); + const reader = new FileReader(); + reader.onload = e => { + const url = e.target?.result as string; + const img = new Image(); + img.onload = () => { + const { maxWidth, maxHeight } = getMaxDimensions(sprites.value); - const newSprite: Sprite = { - id: crypto.randomUUID(), - file, - img, - url, - width: img.width, - height: img.height, - x: 0, - y: 0, + const newSprite: Sprite = { + id: crypto.randomUUID(), + file, + img, + url, + width: img.width, + height: img.height, + x: 0, + y: 0, + }; + + const newMaxWidth = Math.max(maxWidth, img.width); + const newMaxHeight = Math.max(maxHeight, img.height); + + if (img.width > maxWidth || img.height > maxHeight) { + sprites.value = sprites.value.map(sprite => { + let newX = sprite.x; + let newY = sprite.y; + + if (img.width > maxWidth) { + const relativeX = maxWidth > 0 ? sprite.x / maxWidth : 0; + newX = Math.floor(relativeX * newMaxWidth); + newX = Math.max(0, Math.min(newX, newMaxWidth - sprite.width)); + } + + if (img.height > maxHeight) { + const relativeY = maxHeight > 0 ? sprite.y / maxHeight : 0; + newY = Math.floor(relativeY * newMaxHeight); + newY = Math.max(0, Math.min(newY, newMaxHeight - sprite.height)); + } + + return { ...sprite, x: newX, y: newY }; + }); + } + + sprites.value = [...sprites.value, newSprite]; + triggerForceRedraw(); }; - - const newMaxWidth = Math.max(maxWidth, img.width); - const newMaxHeight = Math.max(maxHeight, img.height); - - if (img.width > maxWidth || img.height > maxHeight) { - sprites.value = sprites.value.map(sprite => { - let newX = sprite.x; - let newY = sprite.y; - - if (img.width > maxWidth) { - const relativeX = maxWidth > 0 ? sprite.x / maxWidth : 0; - newX = Math.floor(relativeX * newMaxWidth); - newX = Math.max(0, Math.min(newX, newMaxWidth - sprite.width)); - } - - if (img.height > maxHeight) { - const relativeY = maxHeight > 0 ? sprite.y / maxHeight : 0; - newY = Math.floor(relativeY * newMaxHeight); - newY = Math.max(0, Math.min(newY, newMaxHeight - sprite.height)); - } - - return { ...sprite, x: newX, y: newY }; - }); - } - - sprites.value = [...sprites.value, newSprite]; - triggerForceRedraw(); + img.onerror = () => { + console.error('Failed to load new sprite image:', file.name); + }; + img.src = url; }; - img.onerror = () => { - console.error('Failed to load new sprite image:', file.name); - URL.revokeObjectURL(url); + reader.onerror = () => { + console.error('Failed to read sprite image file:', file.name); }; - img.src = url; + reader.readAsDataURL(file); }; const processImageFiles = (files: File[]) => { Promise.all( files.map( file => - new Promise(resolve => { - const url = URL.createObjectURL(file); - const img = new Image(); - img.onload = () => { - resolve({ - id: crypto.randomUUID(), - file, - img, - url, - width: img.width, - height: img.height, - x: 0, - y: 0, - }); + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = e => { + const url = e.target?.result as string; + const img = new Image(); + img.onload = () => { + resolve({ + id: crypto.randomUUID(), + file, + img, + url, + width: img.width, + height: img.height, + x: 0, + y: 0, + }); + }; + img.onerror = () => reject(new Error(`Failed to load image: ${file.name}`)); + img.src = url; }; - img.src = url; + reader.onerror = () => reject(new Error(`Failed to read file: ${file.name}`)); + reader.readAsDataURL(file); }) ) - ).then(newSprites => { - sprites.value = [...sprites.value, ...newSprites]; - }); + ) + .then(newSprites => { + sprites.value = [...sprites.value, ...newSprites]; + }) + .catch(err => { + console.error('Error processing image files:', err); + }); }; onUnmounted(() => {