[FEAT] Replace img

This commit is contained in:
2026-01-07 18:58:47 +01:00
parent 8a4e14750b
commit 06ab1e45db
3 changed files with 129 additions and 8 deletions

View File

@@ -40,10 +40,10 @@
@touchmove="handleTouchMove" @touchmove="handleTouchMove"
@touchend="stopDrag" @touchend="stopDrag"
@contextmenu.prevent.stop @contextmenu.prevent.stop
@dragover="handleDragOver" @dragover="handleGridDragOver"
@dragenter="handleDragEnter" @dragenter="handleGridDragEnter"
@dragleave="onDragLeave" @dragleave="handleGridDragLeave"
@drop="handleDrop" @drop="handleGridDrop"
:class="{ 'ring-4 ring-blue-500 ring-opacity-50': isDragOver }" :class="{ 'ring-4 ring-blue-500 ring-opacity-50': isDragOver }"
> >
<!-- Grid cells --> <!-- Grid cells -->
@@ -174,7 +174,7 @@
import { ref, onMounted, watch, onUnmounted, toRef, computed, nextTick } from 'vue'; import { ref, onMounted, watch, onUnmounted, toRef, computed, nextTick } from 'vue';
import { useSettingsStore } from '@/stores/useSettingsStore'; import { useSettingsStore } from '@/stores/useSettingsStore';
import type { Sprite } from '@/types/sprites'; import type { Sprite } from '@/types/sprites';
import { useDragSprite } from '@/composables/useDragSprite'; import { useDragSprite, type CellPosition } from '@/composables/useDragSprite';
import { useFileDrop } from '@/composables/useFileDrop'; import { useFileDrop } from '@/composables/useFileDrop';
import { useGridMetrics } from '@/composables/useGridMetrics'; import { useGridMetrics } from '@/composables/useGridMetrics';
import { useBackgroundStyles } from '@/composables/useBackgroundStyles'; import { useBackgroundStyles } from '@/composables/useBackgroundStyles';
@@ -204,6 +204,7 @@
(e: 'removeSprites', ids: string[]): void; (e: 'removeSprites', ids: string[]): void;
(e: 'replaceSprite', id: string, file: File): void; (e: 'replaceSprite', id: string, file: File): void;
(e: 'addSprite', file: File, index?: number): void; (e: 'addSprite', file: File, index?: number): void;
(e: 'addSprites', files: File[], index?: number): void;
(e: 'addSpriteWithResize', file: File): void; (e: 'addSpriteWithResize', file: File): void;
(e: 'rotateSprite', id: string, angle: number): void; (e: 'rotateSprite', id: string, angle: number): void;
(e: 'flipSprite', id: string, direction: 'horizontal' | 'vertical'): void; (e: 'flipSprite', id: string, direction: 'horizontal' | 'vertical'): void;
@@ -541,10 +542,50 @@
} }
}; };
const onDragLeave = (event: DragEvent) => { // Grid Drag & Drop
// Grid Drag & Drop
const handleGridDragEnter = (event: DragEvent) => {
handleDragEnter(event);
};
const handleGridDragOver = (event: DragEvent) => {
handleDragOver(event);
};
const handleGridDragLeave = (event: DragEvent) => {
handleDragLeave(event, gridContainerRef.value); handleDragLeave(event, gridContainerRef.value);
}; };
const handleGridDrop = (event: DragEvent) => {
handleDragLeave(event, gridContainerRef.value); // Reset drag over state
isDragOver.value = false;
if (!event.dataTransfer?.files.length) return;
// Calculate target cell immediately on drop
let targetCellIndex: number | undefined;
const pos = getMousePosition(event as unknown as MouseEvent, props.zoom);
if (pos) {
const cell = findCellAtPosition(pos.x, pos.y);
if (cell) {
targetCellIndex = cell.index;
}
}
const files = Array.from(event.dataTransfer.files).filter(file => file.type.startsWith('image/'));
if (files.length === 0) return;
if (targetCellIndex !== undefined) {
// Add sprites starting from the target cell index
emit('addSprites', files, targetCellIndex);
} else {
// Fallback to default behavior (append)
emit('addSprites', files);
}
};
onMounted(() => { onMounted(() => {
document.addEventListener('mouseup', stopDrag); document.addEventListener('mouseup', stopDrag);
document.addEventListener('keydown', handleKeyDown); document.addEventListener('keydown', handleKeyDown);

View File

@@ -286,6 +286,63 @@ export const useLayers = () => {
reader.readAsDataURL(file); reader.readAsDataURL(file);
}; };
const addSprites = async (files: File[], index?: number) => {
const l = activeLayer.value;
if (!l) return;
const promises = files.map(file => {
return new Promise<Sprite>((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,
rotation: 0,
flipX: false,
flipY: false,
});
};
img.onerror = () => {
console.error('Failed to load sprite image:', file.name);
reject(new Error(`Failed to load sprite image: ${file.name}`));
};
img.src = url;
};
reader.onerror = () => {
console.error('Failed to read sprite image file:', file.name);
reject(new Error(`Failed to read sprite image file: ${file.name}`));
};
reader.readAsDataURL(file);
});
});
try {
const newSprites = await Promise.all(promises);
const currentSprites = [...l.sprites];
if (typeof index === 'number') {
while (currentSprites.length < index) {
currentSprites.push(createEmptySprite());
}
currentSprites.splice(index, 0, ...newSprites);
} else {
currentSprites.push(...newSprites);
}
l.sprites = currentSprites;
} catch (error) {
console.error('Error adding sprites:', error);
}
};
const processImageFiles = async (files: File[]) => { const processImageFiles = async (files: File[]) => {
for (const f of files) addSprite(f); for (const f of files) addSprite(f);
}; };
@@ -376,6 +433,7 @@ export const useLayers = () => {
flipSprite, flipSprite,
replaceSprite, replaceSprite,
addSprite, addSprite,
addSprites,
processImageFiles, processImageFiles,
alignSprites, alignSprites,
addLayer, addLayer,

View File

@@ -309,6 +309,7 @@
@flip-sprite="flipSprite" @flip-sprite="flipSprite"
@copy-sprite-to-frame="copySpriteToFrame" @copy-sprite-to-frame="copySpriteToFrame"
@open-pixel-editor="openPixelEditor" @open-pixel-editor="openPixelEditor"
@add-sprites="addSprites"
/> />
</div> </div>
<div v-else-if="activeTab === 'preview'" class="h-full flex items-center justify-center"> <div v-else-if="activeTab === 'preview'" class="h-full flex items-center justify-center">
@@ -372,8 +373,29 @@
const projectStore = useProjectStore(); const projectStore = useProjectStore();
const { closeProject, loadProjectData } = useProjectManager(); const { closeProject, loadProjectData } = useProjectManager();
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const { layers, visibleLayers, activeLayer, activeLayerId, columns, updateSpritePosition, updateSpriteInLayer, updateSpriteCell, removeSprite, removeSprites, replaceSprite, addSprite, processImageFiles, alignSprites, addLayer, removeLayer, moveLayer, rotateSprite, flipSprite, copySpriteToFrame } = const {
useLayers(); layers,
visibleLayers,
activeLayer,
activeLayerId,
columns,
updateSpritePosition,
updateSpriteInLayer,
updateSpriteCell,
removeSprite,
removeSprites,
replaceSprite,
addSprite,
addSprites,
processImageFiles,
alignSprites,
addLayer,
removeLayer,
moveLayer,
rotateSprite,
flipSprite,
copySpriteToFrame,
} = useLayers();
const { downloadSpritesheet, exportSpritesheetJSON, importSpritesheetJSON, downloadAsGif, downloadAsZip } = useExportLayers( const { downloadSpritesheet, exportSpritesheetJSON, importSpritesheetJSON, downloadAsGif, downloadAsZip } = useExportLayers(
layers, layers,