diff --git a/src/application/utilities.ts b/src/application/utilities.ts index 3634468..1e12382 100644 --- a/src/application/utilities.ts +++ b/src/application/utilities.ts @@ -1,4 +1,5 @@ import { spriteOffsets, notification } from '@/application/state'; +import { type Sprite } from '@/application/types'; /** * Logger utility with consistent error format @@ -60,3 +61,86 @@ export function isImageReady(img: HTMLImageElement): boolean { export function getPixelPerfectCoordinate(value: number): number { return Math.floor(value) + 0.5; } + +/** + * Create a sprite object from a file + */ +export function createSpriteFromFile(file: File, index: number): Promise { + return new Promise((resolve, reject) => { + // Create a URL for the file + const objectUrl = URL.createObjectURL(file); + + const img = new Image(); + + // Set up event handlers + img.onload = () => { + // Verify the image has loaded properly + if (img.width === 0 || img.height === 0) { + logger.error('Image loaded with invalid dimensions:', { name: file.name, width: img.width, height: img.height }); + URL.revokeObjectURL(objectUrl); + reject(new Error(`Image has invalid dimensions: ${file.name}`)); + return; + } + + // Create the sprite object + const sprite: Sprite = { + img, + width: img.width, + height: img.height, + x: 0, + y: 0, + name: file.name, + id: `sprite-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + uploadOrder: index, + }; + + // Keep the objectUrl reference and don't revoke it yet + // The image is still needed for rendering later + resolve(sprite); + }; + + img.onerror = error => { + logger.error('Error loading image:', { name: file.name, error }); + URL.revokeObjectURL(objectUrl); + reject(new Error(`Failed to load image: ${file.name}`)); + }; + + // Set the source to the object URL + img.src = objectUrl; + }); +} + +/** + * Process multiple files and create sprites + */ +export async function processImageFiles(files: FileList): Promise<{ newSprites: Sprite[], errorCount: number }> { + const imageFiles = Array.from(files).filter(file => file.type.startsWith('image/')); + + if (imageFiles.length === 0) { + return { newSprites: [], errorCount: 0 }; + } + + const newSprites: Sprite[] = []; + let errorCount = 0; + + for (let i = 0; i < imageFiles.length; i++) { + const file = imageFiles[i]; + + try { + const sprite = await createSpriteFromFile(file, i); + newSprites.push(sprite); + } catch (error) { + errorCount++; + logger.error('Error loading sprite:', error); + } + } + + return { newSprites, errorCount }; +} + +/** + * Truncate text to a specific length and add ellipsis if needed + */ +export function truncateText(text: string, maxLength: number = 15): string { + return text.length > maxLength ? `${text.substring(0, maxLength)}...` : text; +} diff --git a/src/components/DropZone.vue b/src/components/DropZone.vue index 44fc773..112ea5b 100644 --- a/src/components/DropZone.vue +++ b/src/components/DropZone.vue @@ -54,20 +54,12 @@ return; } - const newSprites: Sprite[] = []; - let errorCount = 0; + // Use the utility function to process image files + const { newSprites, errorCount } = await store.processImageFiles(files); - for (let i = 0; i < imageFiles.length; i++) { - const file = imageFiles[i]; - - try { - const sprite = await createSpriteFromFile(file, i); - newSprites.push(sprite); - } catch (error) { - errorCount++; - console.error('Error loading sprite:', error); - store.showNotification(`Failed to load ${file.name}`, 'error'); - } + // Handle individual file errors + if (errorCount > 0) { + store.showNotification(`Failed to load ${errorCount} file(s)`, 'error'); } if (newSprites.length > 0) { @@ -75,54 +67,7 @@ emit('files-uploaded', newSprites); store.showNotification(`Added ${newSprites.length} sprites successfully`); } else if (errorCount > 0) { - store.showNotification(`Failed to load all ${errorCount} sprites`, 'error'); + store.showNotification(`Failed to load all sprites`, 'error'); } }; - - const createSpriteFromFile = (file: File, index: number): Promise => { - return new Promise((resolve, reject) => { - // Create a URL for the file - const objectUrl = URL.createObjectURL(file); - - const img = new Image(); - - // Set up event handlers - img.onload = () => { - // Verify the image has loaded properly - if (img.width === 0 || img.height === 0) { - console.error('Image loaded with invalid dimensions:', file.name, img.width, img.height); - URL.revokeObjectURL(objectUrl); - reject(new Error(`Image has invalid dimensions: ${file.name}`)); - return; - } - - // Create the sprite object - const sprite: Sprite = { - img, - width: img.width, - height: img.height, - x: 0, - y: 0, - name: file.name, - id: `sprite-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, - uploadOrder: index, - }; - - // Keep the objectUrl reference and don't revoke it yet - // The image is still needed for rendering later - // URL.revokeObjectURL(objectUrl); - Don't do this anymore - - resolve(sprite); - }; - - img.onerror = error => { - console.error('Error loading image:', file.name, error); - URL.revokeObjectURL(objectUrl); - reject(new Error(`Failed to load image: ${file.name}`)); - }; - - // Set the source to the object URL - img.src = objectUrl; - }); - }; diff --git a/src/components/SpriteList.vue b/src/components/SpriteList.vue index a45a8ad..220144e 100644 --- a/src/components/SpriteList.vue +++ b/src/components/SpriteList.vue @@ -10,7 +10,7 @@ diff --git a/src/components/SpritesModal.vue b/src/components/SpritesModal.vue index ad2b6cc..d6a173a 100644 --- a/src/components/SpritesModal.vue +++ b/src/components/SpritesModal.vue @@ -69,7 +69,8 @@ } }; + // Use the utility function for truncating text const truncateName = (name: string) => { - return name.length > 15 ? `${name.substring(0, 15)}...` : name; + return store.truncateText(name); }; diff --git a/src/composables/useSpritesheetStore.ts b/src/composables/useSpritesheetStore.ts index f83ee75..9c3c0bf 100644 --- a/src/composables/useSpritesheetStore.ts +++ b/src/composables/useSpritesheetStore.ts @@ -1,6 +1,6 @@ import { sprites, canvas, ctx, cellSize, columns, draggedSprite, dragOffset, isShiftPressed, isModalOpen, isSettingsModalOpen, isSpritesModalOpen, isHelpModalOpen, zoomLevel, previewBorder, animation, notification, currentSpriteOffset, spriteOffsets } from '@/application/state'; -import { getSpriteOffset, showNotification } from '@/application/utilities'; +import { getSpriteOffset, showNotification, createSpriteFromFile, processImageFiles, truncateText } from '@/application/utilities'; import { addSprites, updateCellSize, autoArrangeSprites, highlightSprite, clearAllSprites, applyOffsetsToMainView } from '@/application/spriteOperations'; @@ -36,6 +36,9 @@ export function useSpritesheetStore() { // Utils getSpriteOffset, showNotification, + createSpriteFromFile, + processImageFiles, + truncateText, // Sprite operations addSprites,