## [1.7.0] - 2025-11-22
- Add layer support - Add background color picker - Improved UI
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [1.7.0] - 2025-11-22
|
||||||
|
- Add layer support
|
||||||
|
- Add background color picker
|
||||||
|
- Improved UI
|
||||||
|
|
||||||
## [1.6.0] - 2025-11-18
|
## [1.6.0] - 2025-11-18
|
||||||
- Improved animation preview modal
|
- Improved animation preview modal
|
||||||
- Add toggle for negative spacing in cells
|
- Add toggle for negative spacing in cells
|
||||||
@@ -34,30 +39,30 @@ All notable changes to this project will be documented in this file.
|
|||||||
## [1.7.0] - 2025-05-02
|
## [1.7.0] - 2025-05-02
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- 🪟 Checkerboard pattern inside sprite cells as it could conflict with the sprite. (Thanks Rivers)
|
- Checkerboard pattern inside sprite cells as it could conflict with the sprite. (Thanks Rivers)
|
||||||
|
|
||||||
## [1.6.0] - 2025-04-30
|
## [1.6.0] - 2025-04-30
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- 🎨 Dark mode support
|
- Dark mode support
|
||||||
- ⭐ Preview other sprites inside cells from overview
|
- Preview other sprites inside cells from overview
|
||||||
|
|
||||||
## [1.5.0] - 2025-04-30
|
## [1.5.0] - 2025-04-30
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- 📏 Offset indicators for better sprite alignment
|
- Offset indicators for better sprite alignment
|
||||||
- Set base offset for offset indicators
|
- Set base offset for offset indicators
|
||||||
|
|
||||||
## [1.4.0] - 2025-04-06
|
## [1.4.0] - 2025-04-06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- 🎥 Download as GIF functionality
|
- Download as GIF functionality
|
||||||
- 🗂 Download as ZIP functionality
|
- Download as ZIP functionality
|
||||||
|
|
||||||
## [1.3.0] - 2025-04-06
|
## [1.3.0] - 2025-04-06
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- 📄 When importing a spritesheet, the tool will now remove transparent from the edges of each sprite so you can move them inside their cells.
|
- When importing a spritesheet, the tool will now remove transparent from the edges of each sprite so you can move them inside their cells.
|
||||||
|
|
||||||
## [1.2.0] - 2025-04-06
|
## [1.2.0] - 2025-04-06
|
||||||
|
|
||||||
@@ -67,22 +72,22 @@ All notable changes to this project will be documented in this file.
|
|||||||
## [1.1.0] - 2025-04-06
|
## [1.1.0] - 2025-04-06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- 📝 Help modal with instructions and tips
|
- Help modal with instructions and tips
|
||||||
- 🎨 Pixel perfect mode for better sprite alignment
|
- Pixel perfect mode for better sprite alignment
|
||||||
|
|
||||||
## [1.0.0] - 2025-04-06
|
## [1.0.0] - 2025-04-06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- 🎉 Initial release
|
- 🎉 Initial release
|
||||||
- ✨ Basic spritesheet generation functionality
|
- Basic spritesheet generation functionality
|
||||||
- Drag and drop image upload
|
- Drag and drop image upload
|
||||||
- Grid-based sprite arrangement
|
- Grid-based sprite arrangement
|
||||||
- Custom grid size configuration
|
- Custom grid size configuration
|
||||||
- 🎮 Animation preview functionality
|
- Animation preview functionality
|
||||||
- Real-time animation preview
|
- Real-time animation preview
|
||||||
- Adjustable animation speed
|
- Adjustable animation speed
|
||||||
- Frame-by-frame navigation
|
- Frame-by-frame navigation
|
||||||
- 💾 JSON import/export support
|
- JSON import/export support
|
||||||
- Save sprite arrangements
|
- Save sprite arrangements
|
||||||
- Load previous projects
|
- Load previous projects
|
||||||
- Export configuration files
|
- Export configuration files
|
||||||
37
src/App.vue
37
src/App.vue
@@ -45,9 +45,7 @@
|
|||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<div v-for="layer in layers" :key="layer.id" class="flex items-center gap-2 px-2 py-1 rounded border border-gray-200 dark:border-gray-600" :class="{ 'ring-2 ring-blue-500': layer.id === activeLayerId }">
|
<div v-for="layer in layers" :key="layer.id" class="flex items-center gap-2 px-2 py-1 rounded border border-gray-200 dark:border-gray-600" :class="{ 'ring-2 ring-blue-500': layer.id === activeLayerId }">
|
||||||
<button @click="activeLayerId = layer.id" class="px-2 py-0.5 rounded bg-blue-50 dark:bg-blue-900/30 text-blue-800 dark:text-blue-200">{{ layer.name }}</button>
|
<button @click="activeLayerId = layer.id" class="px-2 py-0.5 rounded bg-blue-50 dark:bg-blue-900/30 text-blue-800 dark:text-blue-200">{{ layer.name }}</button>
|
||||||
<label class="flex items-center gap-1 text-xs text-gray-600 dark:text-gray-300">
|
<label class="flex items-center gap-1 text-xs text-gray-600 dark:text-gray-300"> <input type="checkbox" v-model="layer.visible" /> Visible </label>
|
||||||
<input type="checkbox" v-model="layer.visible" /> Visible
|
|
||||||
</label>
|
|
||||||
<button @click="moveLayer(layer.id, 'up')" class="text-xs px-1.5 py-0.5 bg-gray-100 dark:bg-gray-700 rounded">↑</button>
|
<button @click="moveLayer(layer.id, 'up')" class="text-xs px-1.5 py-0.5 bg-gray-100 dark:bg-gray-700 rounded">↑</button>
|
||||||
<button @click="moveLayer(layer.id, 'down')" class="text-xs px-1.5 py-0.5 bg-gray-100 dark:bg-gray-700 rounded">↓</button>
|
<button @click="moveLayer(layer.id, 'down')" class="text-xs px-1.5 py-0.5 bg-gray-100 dark:bg-gray-700 rounded">↓</button>
|
||||||
<button v-if="layers.length > 1" @click="removeLayer(layer.id)" class="text-xs px-1.5 py-0.5 bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-200 rounded">Remove</button>
|
<button v-if="layers.length > 1" @click="removeLayer(layer.id)" class="text-xs px-1.5 py-0.5 bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-200 rounded">Remove</button>
|
||||||
@@ -123,7 +121,17 @@
|
|||||||
<span>Preview animation</span>
|
<span>Preview animation</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<sprite-canvas :layers="layers" :active-layer-id="activeLayerId" :columns="columns" @update-sprite="updateSpritePosition" @update-sprite-cell="updateSpriteCell" @remove-sprite="removeSprite" @replace-sprite="replaceSprite" @add-sprite="addSprite" @add-sprite-with-resize="addSpriteWithResize" />
|
<sprite-canvas
|
||||||
|
:layers="layers"
|
||||||
|
:active-layer-id="activeLayerId"
|
||||||
|
:columns="columns"
|
||||||
|
@update-sprite="updateSpritePosition"
|
||||||
|
@update-sprite-cell="updateSpriteCell"
|
||||||
|
@remove-sprite="removeSprite"
|
||||||
|
@replace-sprite="replaceSprite"
|
||||||
|
@add-sprite="addSprite"
|
||||||
|
@add-sprite-with-resize="addSpriteWithResize"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -173,26 +181,9 @@
|
|||||||
import type { SpriteFile } from './types/sprites';
|
import type { SpriteFile } from './types/sprites';
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const {
|
const { layers, visibleLayers, activeLayer, activeLayerId, columns, updateSpritePosition, updateSpriteCell, removeSprite, replaceSprite, addSprite, addSpriteWithResize, processImageFiles, alignSprites, addLayer, removeLayer, moveLayer } = useLayers();
|
||||||
layers,
|
|
||||||
visibleLayers,
|
|
||||||
activeLayer,
|
|
||||||
activeLayerId,
|
|
||||||
columns,
|
|
||||||
updateSpritePosition,
|
|
||||||
updateSpriteCell,
|
|
||||||
removeSprite,
|
|
||||||
replaceSprite,
|
|
||||||
addSprite,
|
|
||||||
addSpriteWithResize,
|
|
||||||
processImageFiles,
|
|
||||||
alignSprites,
|
|
||||||
addLayer,
|
|
||||||
removeLayer,
|
|
||||||
moveLayer,
|
|
||||||
} = useLayers();
|
|
||||||
|
|
||||||
const { downloadSpritesheet, exportSpritesheetJSON, importSpritesheetJSON, downloadAsGif, downloadAsZip } = useExportLayers(layers, columns, toRef(settingsStore, 'negativeSpacingEnabled'));
|
const { downloadSpritesheet, exportSpritesheetJSON, importSpritesheetJSON, downloadAsGif, downloadAsZip } = useExportLayers(layers, columns, toRef(settingsStore, 'negativeSpacingEnabled'), activeLayerId, toRef(settingsStore, 'backgroundColor'));
|
||||||
|
|
||||||
const cellSize = computed(() => {
|
const cellSize = computed(() => {
|
||||||
if (!layers.value.length) return { width: 0, height: 0 };
|
if (!layers.value.length) return { width: 0, height: 0 };
|
||||||
|
|||||||
@@ -23,6 +23,18 @@
|
|||||||
<input id="negative-spacing" type="checkbox" v-model="settingsStore.negativeSpacingEnabled" class="mr-2 w-4 h-4" />
|
<input id="negative-spacing" type="checkbox" v-model="settingsStore.negativeSpacingEnabled" class="mr-2 w-4 h-4" />
|
||||||
<label for="negative-spacing" class="dark:text-gray-200 text-sm sm:text-base">Negative spacing</label>
|
<label for="negative-spacing" class="dark:text-gray-200 text-sm sm:text-base">Negative spacing</label>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Background color picker -->
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<label for="bg-color" class="dark:text-gray-200 text-sm sm:text-base">Background:</label>
|
||||||
|
<select id="bg-color" v-model="settingsStore.backgroundColor" class="px-2 py-1 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 dark:text-gray-200 text-sm">
|
||||||
|
<option value="transparent">Transparent</option>
|
||||||
|
<option value="#ffffff">White</option>
|
||||||
|
<option value="#000000">Black</option>
|
||||||
|
<option value="#f9fafb">Light Gray</option>
|
||||||
|
<option value="custom">Custom...</option>
|
||||||
|
</select>
|
||||||
|
<input v-if="settingsStore.backgroundColor === 'custom'" type="color" v-model="customColor" @input="settingsStore.setBackgroundColor(customColor)" class="w-8 h-8 border border-gray-300 dark:border-gray-600 rounded cursor-pointer" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -172,7 +184,7 @@
|
|||||||
findSpriteAtPosition,
|
findSpriteAtPosition,
|
||||||
calculateMaxDimensions,
|
calculateMaxDimensions,
|
||||||
} = useDragSprite({
|
} = useDragSprite({
|
||||||
sprites: computed(() => (props.layers.find(l => l.id === props.activeLayerId)?.sprites ?? [])),
|
sprites: computed(() => props.layers.find(l => l.id === props.activeLayerId)?.sprites ?? []),
|
||||||
columns: toRef(props, 'columns'),
|
columns: toRef(props, 'columns'),
|
||||||
zoom,
|
zoom,
|
||||||
allowCellSwap,
|
allowCellSwap,
|
||||||
@@ -198,6 +210,7 @@
|
|||||||
const contextMenuSpriteId = ref<string | null>(null);
|
const contextMenuSpriteId = ref<string | null>(null);
|
||||||
const replacingSpriteId = ref<string | null>(null);
|
const replacingSpriteId = ref<string | null>(null);
|
||||||
const fileInput = ref<HTMLInputElement | null>(null);
|
const fileInput = ref<HTMLInputElement | null>(null);
|
||||||
|
const customColor = ref('#ffffff');
|
||||||
|
|
||||||
const startDrag = (event: MouseEvent) => {
|
const startDrag = (event: MouseEvent) => {
|
||||||
if (!canvasRef.value) return;
|
if (!canvasRef.value) return;
|
||||||
@@ -423,6 +436,7 @@
|
|||||||
watch(() => settingsStore.pixelPerfect, requestDraw);
|
watch(() => settingsStore.pixelPerfect, requestDraw);
|
||||||
watch(() => settingsStore.darkMode, requestDraw);
|
watch(() => settingsStore.darkMode, requestDraw);
|
||||||
watch(() => settingsStore.negativeSpacingEnabled, requestDraw);
|
watch(() => settingsStore.negativeSpacingEnabled, requestDraw);
|
||||||
|
watch(() => settingsStore.backgroundColor, requestDraw);
|
||||||
watch(showAllSprites, requestDraw);
|
watch(showAllSprites, requestDraw);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -307,12 +307,12 @@
|
|||||||
const spriteCanvasX = negativeSpacing + activeSprite.x;
|
const spriteCanvasX = negativeSpacing + activeSprite.x;
|
||||||
const spriteCanvasY = negativeSpacing + activeSprite.y;
|
const spriteCanvasY = negativeSpacing + activeSprite.y;
|
||||||
if (mouseX >= spriteCanvasX && mouseX <= spriteCanvasX + activeSprite.width && mouseY >= spriteCanvasY && mouseY <= spriteCanvasY + activeSprite.height) {
|
if (mouseX >= spriteCanvasX && mouseX <= spriteCanvasX + activeSprite.width && mouseY >= spriteCanvasY && mouseY <= spriteCanvasY + activeSprite.height) {
|
||||||
isDragging.value = true;
|
isDragging.value = true;
|
||||||
activeSpriteId.value = activeSprite.id;
|
activeSpriteId.value = activeSprite.id;
|
||||||
dragStartX.value = mouseX;
|
dragStartX.value = mouseX;
|
||||||
dragStartY.value = mouseY;
|
dragStartY.value = mouseY;
|
||||||
spritePosBeforeDrag.value = { x: activeSprite.x, y: activeSprite.y };
|
spritePosBeforeDrag.value = { x: activeSprite.x, y: activeSprite.y };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -116,9 +116,10 @@ export function useCanvas2D(canvasRef: Ref<HTMLCanvasElement | null>, options?:
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fill cell background with theme-aware color
|
// Fill cell background with selected color or transparent
|
||||||
const fillCellBackground = (x: number, y: number, width: number, height: number) => {
|
const fillCellBackground = (x: number, y: number, width: number, height: number) => {
|
||||||
const color = settingsStore.darkMode ? '#1F2937' : '#f9fafb';
|
if (settingsStore.backgroundColor === 'transparent') return;
|
||||||
|
const color = settingsStore.backgroundColor === 'custom' ? settingsStore.backgroundColor : settingsStore.backgroundColor;
|
||||||
fillRect(x, y, width, height, color);
|
fillRect(x, y, width, height, color);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -156,8 +156,6 @@ export const useExport = (sprites: Ref<Sprite[]>, columns: Ref<number>, negative
|
|||||||
|
|
||||||
sprites.value.forEach(sprite => {
|
sprites.value.forEach(sprite => {
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
ctx.fillStyle = '#f9fafb';
|
|
||||||
ctx.fillRect(0, 0, cellWidth, cellHeight);
|
|
||||||
ctx.drawImage(sprite.img, Math.floor(negativeSpacing + sprite.x), Math.floor(negativeSpacing + sprite.y));
|
ctx.drawImage(sprite.img, Math.floor(negativeSpacing + sprite.x), Math.floor(negativeSpacing + sprite.y));
|
||||||
gif.addFrame(ctx, { copy: true, delay: 1000 / fps });
|
gif.addFrame(ctx, { copy: true, delay: 1000 / fps });
|
||||||
});
|
});
|
||||||
@@ -194,8 +192,6 @@ export const useExport = (sprites: Ref<Sprite[]>, columns: Ref<number>, negative
|
|||||||
|
|
||||||
sprites.value.forEach((sprite, index) => {
|
sprites.value.forEach((sprite, index) => {
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
ctx.fillStyle = '#f9fafb';
|
|
||||||
ctx.fillRect(0, 0, cellWidth, cellHeight);
|
|
||||||
ctx.drawImage(sprite.img, Math.floor(negativeSpacing + sprite.x), Math.floor(negativeSpacing + sprite.y));
|
ctx.drawImage(sprite.img, Math.floor(negativeSpacing + sprite.x), Math.floor(negativeSpacing + sprite.y));
|
||||||
const dataURL = canvas.toDataURL('image/png');
|
const dataURL = canvas.toDataURL('image/png');
|
||||||
const binary = atob(dataURL.split(',')[1]);
|
const binary = atob(dataURL.split(',')[1]);
|
||||||
|
|||||||
@@ -6,14 +6,17 @@ import type { Layer, Sprite } from '@/types/sprites';
|
|||||||
import { getMaxDimensionsAcrossLayers } from './useLayers';
|
import { getMaxDimensionsAcrossLayers } from './useLayers';
|
||||||
import { calculateNegativeSpacing } from './useNegativeSpacing';
|
import { calculateNegativeSpacing } from './useNegativeSpacing';
|
||||||
|
|
||||||
export const useExportLayers = (layersRef: Ref<Layer[]>, columns: Ref<number>, negativeSpacingEnabled: Ref<boolean>) => {
|
export const useExportLayers = (layersRef: Ref<Layer[]>, columns: Ref<number>, negativeSpacingEnabled: Ref<boolean>, activeLayerId?: Ref<string>, backgroundColor?: Ref<string>) => {
|
||||||
const getVisibleLayers = () => layersRef.value.filter(l => l.visible);
|
const getVisibleLayers = () => layersRef.value.filter(l => l.visible);
|
||||||
const getAllVisibleSprites = () => getVisibleLayers().flatMap(l => l.sprites);
|
const getAllVisibleSprites = () => getVisibleLayers().flatMap(l => l.sprites);
|
||||||
|
|
||||||
const drawCompositeCell = (ctx: CanvasRenderingContext2D, cellIndex: number, cellWidth: number, cellHeight: number, negativeSpacing: number) => {
|
const drawCompositeCell = (ctx: CanvasRenderingContext2D, cellIndex: number, cellWidth: number, cellHeight: number, negativeSpacing: number) => {
|
||||||
ctx.clearRect(0, 0, cellWidth, cellHeight);
|
ctx.clearRect(0, 0, cellWidth, cellHeight);
|
||||||
ctx.fillStyle = '#f9fafb';
|
// Apply background color if not transparent
|
||||||
ctx.fillRect(0, 0, cellWidth, cellHeight);
|
if (backgroundColor?.value && backgroundColor.value !== 'transparent') {
|
||||||
|
ctx.fillStyle = backgroundColor.value;
|
||||||
|
ctx.fillRect(0, 0, cellWidth, cellHeight);
|
||||||
|
}
|
||||||
const vLayers = getVisibleLayers();
|
const vLayers = getVisibleLayers();
|
||||||
vLayers.forEach(layer => {
|
vLayers.forEach(layer => {
|
||||||
const sprite = layer.sprites[cellIndex];
|
const sprite = layer.sprites[cellIndex];
|
||||||
@@ -130,16 +133,32 @@ export const useExportLayers = (layersRef: Ref<Layer[]>, columns: Ref<number>, n
|
|||||||
const sprites: Sprite[] = await Promise.all(layerData.sprites.map((s: any) => loadSprite(s)));
|
const sprites: Sprite[] = await Promise.all(layerData.sprites.map((s: any) => loadSprite(s)));
|
||||||
newLayers.push({ id: layerData.id || crypto.randomUUID(), name: layerData.name || 'Layer', visible: layerData.visible !== false, locked: !!layerData.locked, sprites });
|
newLayers.push({ id: layerData.id || crypto.randomUUID(), name: layerData.name || 'Layer', visible: layerData.visible !== false, locked: !!layerData.locked, sprites });
|
||||||
}
|
}
|
||||||
|
// Ensure at least one layer with sprites is visible
|
||||||
|
if (newLayers.length > 0 && !newLayers.some(l => l.visible && l.sprites.length > 0)) {
|
||||||
|
const firstLayerWithSprites = newLayers.find(l => l.sprites.length > 0);
|
||||||
|
if (firstLayerWithSprites) {
|
||||||
|
firstLayerWithSprites.visible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
layersRef.value = newLayers;
|
layersRef.value = newLayers;
|
||||||
|
// Set active layer to the first layer with sprites
|
||||||
|
if (activeLayerId && newLayers.length > 0) {
|
||||||
|
const firstWithSprites = newLayers.find(l => l.sprites.length > 0);
|
||||||
|
activeLayerId.value = firstWithSprites ? firstWithSprites.id : newLayers[0].id;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(data.sprites)) {
|
if (Array.isArray(data.sprites)) {
|
||||||
const sprites: Sprite[] = await Promise.all(data.sprites.map((s: any) => loadSprite(s)));
|
const sprites: Sprite[] = await Promise.all(data.sprites.map((s: any) => loadSprite(s)));
|
||||||
|
const baseLayerId = crypto.randomUUID();
|
||||||
layersRef.value = [
|
layersRef.value = [
|
||||||
{ id: crypto.randomUUID(), name: 'Base', visible: true, locked: false, sprites },
|
{ id: baseLayerId, name: 'Base', visible: true, locked: false, sprites },
|
||||||
{ id: crypto.randomUUID(), name: 'Clothes', visible: true, locked: false, sprites: [] },
|
{ id: crypto.randomUUID(), name: 'Other', visible: true, locked: false, sprites: [] },
|
||||||
];
|
];
|
||||||
|
if (activeLayerId) {
|
||||||
|
activeLayerId.value = baseLayerId;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,9 +241,7 @@ export const useExportLayers = (layersRef: Ref<Layer[]>, columns: Ref<number>, n
|
|||||||
name: layer.name,
|
name: layer.name,
|
||||||
visible: layer.visible,
|
visible: layer.visible,
|
||||||
locked: layer.locked,
|
locked: layer.locked,
|
||||||
sprites: await Promise.all(
|
sprites: await Promise.all(layer.sprites.map(async s => ({ id: s.id, width: s.width, height: s.height, x: s.x, y: s.y, name: s.file.name }))),
|
||||||
layer.sprites.map(async s => ({ id: s.id, width: s.width, height: s.height, x: s.x, y: s.y, name: s.file.name }))
|
|
||||||
),
|
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
const meta = { version: 2, columns: columns.value, negativeSpacingEnabled: negativeSpacingEnabled.value, layers: layersPayload };
|
const meta = { version: 2, columns: columns.value, negativeSpacingEnabled: negativeSpacingEnabled.value, layers: layersPayload };
|
||||||
|
|||||||
@@ -200,6 +200,6 @@ export const useLayers = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getMaxDimensionsAcrossLayers = (layers: Layer[]) => {
|
export const getMaxDimensionsAcrossLayers = (layers: Layer[]) => {
|
||||||
const sprites = layers.flatMap(l => l.visible ? l.sprites : []);
|
const sprites = layers.flatMap(l => (l.visible ? l.sprites : []));
|
||||||
return getMaxDimensionsSingle(sprites);
|
return getMaxDimensionsSingle(sprites);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { ref, watch } from 'vue';
|
|||||||
const pixelPerfect = ref(true);
|
const pixelPerfect = ref(true);
|
||||||
const darkMode = ref(false);
|
const darkMode = ref(false);
|
||||||
const negativeSpacingEnabled = ref(false);
|
const negativeSpacingEnabled = ref(false);
|
||||||
|
const backgroundColor = ref('transparent');
|
||||||
|
|
||||||
// Initialize dark mode from localStorage or system preference
|
// Initialize dark mode from localStorage or system preference
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
@@ -56,14 +57,20 @@ export const useSettingsStore = defineStore('settings', () => {
|
|||||||
negativeSpacingEnabled.value = !negativeSpacingEnabled.value;
|
negativeSpacingEnabled.value = !negativeSpacingEnabled.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setBackgroundColor(color: string) {
|
||||||
|
backgroundColor.value = color;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pixelPerfect,
|
pixelPerfect,
|
||||||
darkMode,
|
darkMode,
|
||||||
negativeSpacingEnabled,
|
negativeSpacingEnabled,
|
||||||
|
backgroundColor,
|
||||||
togglePixelPerfect,
|
togglePixelPerfect,
|
||||||
setPixelPerfect,
|
setPixelPerfect,
|
||||||
toggleDarkMode,
|
toggleDarkMode,
|
||||||
setDarkMode,
|
setDarkMode,
|
||||||
toggleNegativeSpacing,
|
toggleNegativeSpacing,
|
||||||
|
setBackgroundColor,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user