[FEAT] Drag drop in file container

This commit is contained in:
2025-12-17 21:11:34 +01:00
parent 6fe90c6af9
commit bcd6dc43c7
3 changed files with 34 additions and 18 deletions

View File

@@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
- Add rotate sprite to context menu - Add rotate sprite to context menu
- Add flip sprite to context menu - Add flip sprite to context menu
- Add multi select for sprite removal - Add multi select for sprite removal
- Add drag and drop in file area when sprite is open
## [1.9.0] - 2025-12-14 ## [1.9.0] - 2025-12-14
- You can now share spritesheets to edit them later or share them with others - You can now share spritesheets to edit them later or share them with others

View File

@@ -529,23 +529,22 @@
if (pos) { if (pos) {
const clickedSprite = findSpriteAtPosition(pos.x, pos.y); const clickedSprite = findSpriteAtPosition(pos.x, pos.y);
if (clickedSprite) { if (clickedSprite) {
// Selection logic with multi-select mode check // Selection logic with multi-select mode check
if (event.ctrlKey || event.metaKey || isMultiSelectMode.value) { if (event.ctrlKey || event.metaKey || isMultiSelectMode.value) {
// Toggle selection // Toggle selection
if (selectedSpriteIds.value.has(clickedSprite.id)) { if (selectedSpriteIds.value.has(clickedSprite.id)) {
selectedSpriteIds.value.delete(clickedSprite.id); selectedSpriteIds.value.delete(clickedSprite.id);
} else {
selectedSpriteIds.value.add(clickedSprite.id);
}
} else { } else {
// Single select (but don't clear if dragging starts immediately? selectedSpriteIds.value.add(clickedSprite.id);
// Usually standard behavior is to clear others unless shift/ctrl held)
if (!selectedSpriteIds.value.has(clickedSprite.id)) {
selectedSpriteIds.value.clear();
selectedSpriteIds.value.add(clickedSprite.id);
}
} }
} else {
// Single select (but don't clear if dragging starts immediately?
// Usually standard behavior is to clear others unless shift/ctrl held)
if (!selectedSpriteIds.value.has(clickedSprite.id)) {
selectedSpriteIds.value.clear();
selectedSpriteIds.value.add(clickedSprite.id);
}
}
} else { } else {
// Clicked on empty space // Clicked on empty space
selectedSpriteIds.value.clear(); selectedSpriteIds.value.clear();

View File

@@ -62,11 +62,19 @@
</button> </button>
</div> </div>
<button <button
class="w-full p-6 text-center border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-xl hover:border-gray-500 dark:hover:border-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-all cursor-pointer focus:outline-none focus:ring-2 focus:ring-gray-500 group" class="w-full p-6 text-center border-2 border-dashed rounded-xl transition-all cursor-pointer focus:outline-none focus:ring-2 focus:ring-gray-500 group"
:class="[isDragging ? 'border-blue-500 dark:border-blue-400 bg-blue-50 dark:bg-blue-900/20' : 'border-gray-300 dark:border-gray-600 hover:border-gray-500 dark:hover:border-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800/50']"
@click="openFileDialog" @click="openFileDialog"
@dragenter.prevent="isDragging = true"
@dragleave.prevent="isDragging = false"
@dragover.prevent
@drop.prevent="handleDrop"
> >
<i class="fas fa-plus-circle text-3xl text-gray-400 dark:text-gray-500 group-hover:text-gray-600 dark:group-hover:text-gray-300 mb-3 transition-colors"></i> <i class="fas fa-plus-circle text-3xl mb-3 transition-colors" :class="[isDragging ? 'text-blue-500 dark:text-blue-400' : 'text-gray-400 dark:text-gray-500 group-hover:text-gray-600 dark:group-hover:text-gray-300']"></i>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-gray-200 transition-colors">Add sprites</p> <p class="text-sm font-medium transition-colors" :class="[isDragging ? 'text-blue-600 dark:text-blue-300' : 'text-gray-600 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-gray-200']">
<span v-if="isDragging">Drop files here</span>
<span v-else>Add sprites</span>
</p>
</button> </button>
<input ref="uploadInput" type="file" multiple accept="image/*" class="hidden" @change="handleUploadChange" /> <input ref="uploadInput" type="file" multiple accept="image/*" class="hidden" @change="handleUploadChange" />
<input ref="jsonFileInput" type="file" accept=".json,application/json" class="hidden" @change="handleJSONFileChange" /> <input ref="jsonFileInput" type="file" accept=".json,application/json" class="hidden" @change="handleJSONFileChange" />
@@ -338,6 +346,7 @@
const editingLayerId = ref<string | null>(null); const editingLayerId = ref<string | null>(null);
const editingLayerName = ref(''); const editingLayerName = ref('');
const layerNameInput = ref<HTMLInputElement | null>(null); const layerNameInput = ref<HTMLInputElement | null>(null);
const isDragging = ref(false);
const handleSpritesUpload = async (files: File[]) => { const handleSpritesUpload = async (files: File[]) => {
const jsonFile = files.find(file => file.type === 'application/json' || file.name.endsWith('.json')); const jsonFile = files.find(file => file.type === 'application/json' || file.name.endsWith('.json'));
@@ -378,6 +387,13 @@
processImageFiles(files); processImageFiles(files);
}; };
const handleDrop = (event: DragEvent) => {
isDragging.value = false;
if (event.dataTransfer?.files && event.dataTransfer.files.length > 0) {
handleSpritesUpload(Array.from(event.dataTransfer.files));
}
};
const handleJSONImport = async (jsonFile: File) => { const handleJSONImport = async (jsonFile: File) => {
try { try {
await importSpritesheetJSON(jsonFile); await importSpritesheetJSON(jsonFile);