[FEAT] Format code
This commit is contained in:
@@ -113,18 +113,15 @@ export function useDragSprite(options: DragSpriteOptions) {
|
||||
});
|
||||
|
||||
const findCellAtPosition = (x: number, y: number): CellPosition | null => {
|
||||
const sprites = getSprites();
|
||||
const columns = getColumns();
|
||||
const { maxWidth, maxHeight } = calculateMaxDimensions();
|
||||
const col = Math.floor(x / maxWidth);
|
||||
const row = Math.floor(y / maxHeight);
|
||||
|
||||
const totalRows = Math.ceil(sprites.length / columns);
|
||||
if (col >= 0 && col < columns && row >= 0 && row < totalRows) {
|
||||
// Allow dropping anywhere in the columns, assuming infinite rows effectively
|
||||
if (col >= 0 && col < columns && row >= 0) {
|
||||
const index = row * columns + col;
|
||||
if (index < sprites.length) {
|
||||
return { col, row, index };
|
||||
}
|
||||
return { col, row, index };
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -15,6 +15,20 @@ const layers = ref<Layer[]>([createEmptyLayer('Base')]);
|
||||
const activeLayerId = ref<string>(layers.value[0].id);
|
||||
const columns = ref(4);
|
||||
|
||||
const createEmptySprite = (): Sprite => ({
|
||||
id: crypto.randomUUID(),
|
||||
file: new File([], 'empty'),
|
||||
img: new Image(),
|
||||
url: '',
|
||||
width: 0,
|
||||
height: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
rotation: 0,
|
||||
flipX: false,
|
||||
flipY: false,
|
||||
});
|
||||
|
||||
export const useLayers = () => {
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
@@ -103,17 +117,66 @@ export const useLayers = () => {
|
||||
if (!l) return;
|
||||
const currentIndex = l.sprites.findIndex(s => s.id === id);
|
||||
if (currentIndex === -1 || currentIndex === newIndex) return;
|
||||
|
||||
const next = [...l.sprites];
|
||||
if (newIndex < next.length) {
|
||||
const moving = { ...next[currentIndex] };
|
||||
const target = { ...next[newIndex] };
|
||||
next[currentIndex] = target;
|
||||
next[newIndex] = moving;
|
||||
} else {
|
||||
const [moved] = next.splice(currentIndex, 1);
|
||||
next.splice(newIndex, 0, moved);
|
||||
|
||||
// Remove the moving sprite first
|
||||
const [moving] = next.splice(currentIndex, 1);
|
||||
|
||||
// Determine the actual index to insert at, considering we removed one item
|
||||
// If the target index was greater than current index, it shifts down by 1 in the original array perspective?
|
||||
// Actually simpler: we just want to put 'moving' at 'newIndex' in the final array.
|
||||
|
||||
// If newIndex is beyond the current bounds (after removal), fill with placeholders
|
||||
while (next.length < newIndex) {
|
||||
next.push(createEmptySprite());
|
||||
}
|
||||
|
||||
// Now insert
|
||||
// If newIndex is within bounds, we might be swapping if there was something there
|
||||
// But the DragSprite logic implies we are "moving to this cell".
|
||||
// If there is existing content at newIndex, we should swap or splice?
|
||||
// The previous implementation did a swap if newIndex < length (before removal).
|
||||
|
||||
// Let's stick to the "swap" logic if there's a sprite there, or "move" if we are reordering.
|
||||
// Wait, Drag and Drop usually implies "insert here" or "swap with this".
|
||||
// useDragSprite says: "if allowCellSwap... updateSpriteCell".
|
||||
|
||||
// The original logic:
|
||||
// if (newIndex < next.length) -> swap
|
||||
// else -> splice (move)
|
||||
|
||||
// Re-evaluating original logic:
|
||||
// next has NOT had the item removed yet in the original logic 'if' block.
|
||||
|
||||
// Let's implement robust swap/move logic.
|
||||
// 1. If target is empty placeholder -> just move there (replace placeholder).
|
||||
// 2. If target has sprite -> swap.
|
||||
// 3. If target is out of bounds -> pad and move.
|
||||
|
||||
if (newIndex < l.sprites.length) {
|
||||
// Perform Swap
|
||||
const target = l.sprites[newIndex];
|
||||
const moving = l.sprites[currentIndex];
|
||||
|
||||
// Clone array
|
||||
const newSprites = [...l.sprites];
|
||||
newSprites[currentIndex] = target;
|
||||
newSprites[newIndex] = moving;
|
||||
l.sprites = newSprites;
|
||||
} else {
|
||||
// Move to previously empty/non-existent cell
|
||||
const newSprites = [...l.sprites];
|
||||
// Remove from old pos
|
||||
const [moved] = newSprites.splice(currentIndex, 1);
|
||||
// Pad
|
||||
while (newSprites.length < newIndex) {
|
||||
newSprites.push(createEmptySprite());
|
||||
}
|
||||
// Insert (or push if equal length)
|
||||
newSprites.splice(newIndex, 0, moved);
|
||||
l.sprites = newSprites;
|
||||
}
|
||||
l.sprites = next;
|
||||
};
|
||||
|
||||
const removeSprite = (id: string) => {
|
||||
@@ -203,7 +266,7 @@ export const useLayers = () => {
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
|
||||
const addSprite = (file: File) => {
|
||||
const addSprite = (file: File, index?: number) => {
|
||||
const l = activeLayer.value;
|
||||
if (!l) return;
|
||||
const reader = new FileReader();
|
||||
@@ -224,7 +287,28 @@ export const useLayers = () => {
|
||||
flipX: false,
|
||||
flipY: false,
|
||||
};
|
||||
l.sprites = [...l.sprites, next];
|
||||
|
||||
const currentSprites = [...l.sprites];
|
||||
|
||||
if (typeof index === 'number') {
|
||||
// If index is provided, insert there (padding if needed)
|
||||
while (currentSprites.length < index) {
|
||||
currentSprites.push(createEmptySprite());
|
||||
}
|
||||
// If valid index, replace if empty or splice?
|
||||
// "Adds it not in the one I selected".
|
||||
// If I select a cell, I expect it to go there.
|
||||
// If the cell is empty (placeholder), replace it.
|
||||
// If the cell has a sprite, maybe insert/shift?
|
||||
// Usually "Add" implies append, but context menu "Add sprite" on a cell implies "Put it here".
|
||||
// Let's Insert (Shift others) for safety, or check if empty.
|
||||
// But simpler: just splice it in.
|
||||
currentSprites.splice(index, 0, next);
|
||||
} else {
|
||||
// No index, append to end
|
||||
currentSprites.push(next);
|
||||
}
|
||||
l.sprites = currentSprites;
|
||||
};
|
||||
img.onerror = () => {
|
||||
console.error('Failed to load sprite image:', file.name);
|
||||
@@ -299,19 +383,7 @@ export const useLayers = () => {
|
||||
|
||||
// Expand the sprites array if necessary with empty placeholder sprites
|
||||
while (targetLayer.sprites.length < targetFrameIndex) {
|
||||
targetLayer.sprites.push({
|
||||
id: crypto.randomUUID(),
|
||||
file: new File([], 'empty'),
|
||||
img: new Image(),
|
||||
url: '',
|
||||
width: 0,
|
||||
height: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
rotation: 0,
|
||||
flipX: false,
|
||||
flipY: false,
|
||||
});
|
||||
targetLayer.sprites.push(createEmptySprite());
|
||||
}
|
||||
|
||||
// Replace or insert the sprite at the target index
|
||||
|
||||
95
src/composables/useProjectManager.ts
Normal file
95
src/composables/useProjectManager.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { ref, toRef, watch } from 'vue';
|
||||
import { useLayers, createEmptyLayer, getMaxDimensionsAcrossLayers } from '@/composables/useLayers';
|
||||
import { useSettingsStore } from '@/stores/useSettingsStore';
|
||||
import { useProjectStore, type Project } from '@/stores/useProjectStore';
|
||||
import { useExportLayers } from '@/composables/useExportLayers';
|
||||
|
||||
// Global state for editor visibility
|
||||
const isEditorActive = ref(false);
|
||||
|
||||
export const useProjectManager = () => {
|
||||
const settingsStore = useSettingsStore();
|
||||
const projectStore = useProjectStore();
|
||||
const { layers, columns, activeLayerId, visibleLayers } = useLayers();
|
||||
|
||||
const { generateProjectJSON, loadProjectData } = useExportLayers(
|
||||
layers,
|
||||
columns,
|
||||
toRef(settingsStore, 'negativeSpacingEnabled'),
|
||||
activeLayerId,
|
||||
toRef(settingsStore, 'backgroundColor'),
|
||||
toRef(settingsStore, 'manualCellSizeEnabled'),
|
||||
toRef(settingsStore, 'manualCellWidth'),
|
||||
toRef(settingsStore, 'manualCellHeight')
|
||||
);
|
||||
|
||||
// Watch for sprites to automatically open editor (legacy behavior support)
|
||||
watch(
|
||||
() => layers.value.some(l => l.sprites.length > 0),
|
||||
hasSprites => {
|
||||
if (hasSprites) isEditorActive.value = true;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const createProject = (config: { width: number; height: number; columns: number; rows: number }) => {
|
||||
// 1. Reset Settings
|
||||
settingsStore.setManualCellSize(config.width, config.height);
|
||||
settingsStore.manualCellSizeEnabled = true;
|
||||
|
||||
// 2. Reset Layers
|
||||
const newLayer = createEmptyLayer('Base');
|
||||
layers.value = [newLayer];
|
||||
activeLayerId.value = newLayer.id;
|
||||
|
||||
// 3. Set Columns
|
||||
columns.value = config.columns;
|
||||
|
||||
// 4. Reset Project Store
|
||||
projectStore.currentProject = null;
|
||||
|
||||
// 5. Force Editor Active
|
||||
isEditorActive.value = true;
|
||||
};
|
||||
|
||||
const openProject = async (project: Project) => {
|
||||
try {
|
||||
if (project.data) {
|
||||
await loadProjectData(project.data);
|
||||
}
|
||||
projectStore.currentProject = project;
|
||||
isEditorActive.value = true;
|
||||
} catch (e) {
|
||||
console.error('Failed to open project', e);
|
||||
alert('Failed to open project data');
|
||||
}
|
||||
};
|
||||
|
||||
const saveProject = async (name: string) => {
|
||||
try {
|
||||
const data = await generateProjectJSON();
|
||||
|
||||
if (projectStore.currentProject && projectStore.currentProject.name === name) {
|
||||
await projectStore.updateProject(projectStore.currentProject.id, data);
|
||||
} else {
|
||||
await projectStore.createProject(name, data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert('Failed to save project');
|
||||
throw e; // Re-throw to let caller know
|
||||
}
|
||||
};
|
||||
|
||||
const closeEditor = () => {
|
||||
isEditorActive.value = false;
|
||||
};
|
||||
|
||||
return {
|
||||
isEditorActive,
|
||||
createProject,
|
||||
openProject,
|
||||
saveProject,
|
||||
closeEditor,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user