170 lines
4.9 KiB
TypeScript
170 lines
4.9 KiB
TypeScript
import type { Ref } from 'vue';
|
|
import type { Layer } from '@/types/sprites';
|
|
|
|
const POCKETBASE_URL = import.meta.env.VITE_POCKETBASE_URL;
|
|
const COLLECTION = 'spritesheets';
|
|
|
|
export interface SpritesheetConfig {
|
|
version: number;
|
|
columns: number;
|
|
negativeSpacingEnabled: boolean;
|
|
backgroundColor: string;
|
|
manualCellSizeEnabled: boolean;
|
|
manualCellWidth: number;
|
|
manualCellHeight: number;
|
|
}
|
|
|
|
export interface SharedSprite {
|
|
id: string;
|
|
width: number;
|
|
height: number;
|
|
x: number;
|
|
y: number;
|
|
rotation: number;
|
|
flipX: boolean;
|
|
flipY: boolean;
|
|
base64: string;
|
|
name?: string;
|
|
}
|
|
|
|
export interface SharedLayer {
|
|
id: string;
|
|
name: string;
|
|
visible: boolean;
|
|
locked: boolean;
|
|
sprites: SharedSprite[];
|
|
}
|
|
|
|
export interface SpritesheetRecord {
|
|
id: string;
|
|
config: SpritesheetConfig;
|
|
sprites: SharedLayer[];
|
|
created: string;
|
|
updated: string;
|
|
}
|
|
|
|
export interface ShareResult {
|
|
id: string;
|
|
url: string;
|
|
}
|
|
|
|
/**
|
|
* Build the shareable URL for a spritesheet
|
|
*/
|
|
export const buildShareUrl = (id: string): string => {
|
|
return `${window.location.origin}/share/${id}`;
|
|
};
|
|
|
|
/**
|
|
* Share a spritesheet by uploading to PocketBase
|
|
*/
|
|
export const shareSpritesheet = async (layersRef: Ref<Layer[]>, columns: Ref<number>, negativeSpacingEnabled: Ref<boolean>, backgroundColor?: Ref<string>, manualCellSizeEnabled?: Ref<boolean>, manualCellWidth?: Ref<number>, manualCellHeight?: Ref<number>): Promise<ShareResult> => {
|
|
// Build layers data with base64 sprites (same format as exportSpritesheetJSON)
|
|
const layersData = await Promise.all(
|
|
layersRef.value.map(async layer => {
|
|
const sprites = await Promise.all(
|
|
layer.sprites.map(async sprite => {
|
|
const canvas = document.createElement('canvas');
|
|
const ctx = canvas.getContext('2d');
|
|
if (!ctx) return null;
|
|
canvas.width = sprite.width;
|
|
canvas.height = sprite.height;
|
|
if (sprite.rotation || sprite.flipX || sprite.flipY) {
|
|
ctx.save();
|
|
ctx.translate(sprite.width / 2, sprite.height / 2);
|
|
ctx.rotate((sprite.rotation * Math.PI) / 180);
|
|
ctx.scale(sprite.flipX ? -1 : 1, sprite.flipY ? -1 : 1);
|
|
ctx.drawImage(sprite.img, -sprite.width / 2, -sprite.height / 2);
|
|
ctx.restore();
|
|
} else {
|
|
ctx.drawImage(sprite.img, 0, 0);
|
|
}
|
|
const base64 = canvas.toDataURL('image/png');
|
|
// Since we bake transformations into the image, set them to 0/false in metadata
|
|
return {
|
|
id: sprite.id,
|
|
width: sprite.width,
|
|
height: sprite.height,
|
|
x: sprite.x,
|
|
y: sprite.y,
|
|
rotation: 0,
|
|
flipX: false,
|
|
flipY: false,
|
|
base64,
|
|
name: sprite.file.name,
|
|
};
|
|
})
|
|
);
|
|
return {
|
|
id: layer.id,
|
|
name: layer.name,
|
|
visible: layer.visible,
|
|
locked: layer.locked,
|
|
sprites: sprites.filter(Boolean),
|
|
};
|
|
})
|
|
);
|
|
|
|
const config: SpritesheetConfig = {
|
|
version: 2,
|
|
columns: columns.value,
|
|
negativeSpacingEnabled: negativeSpacingEnabled.value,
|
|
backgroundColor: backgroundColor?.value || 'transparent',
|
|
manualCellSizeEnabled: manualCellSizeEnabled?.value || false,
|
|
manualCellWidth: manualCellWidth?.value || 64,
|
|
manualCellHeight: manualCellHeight?.value || 64,
|
|
};
|
|
|
|
const response = await fetch(`${POCKETBASE_URL}/api/collections/${COLLECTION}/records`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
config,
|
|
sprites: layersData,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const text = await response.text().catch(() => '');
|
|
throw new Error(text || `Request failed with status ${response.status}`);
|
|
}
|
|
|
|
const record = await response.json();
|
|
return {
|
|
id: record.id,
|
|
url: buildShareUrl(record.id),
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Fetch a shared spritesheet from PocketBase
|
|
*/
|
|
export const fetchSpritesheet = async (id: string): Promise<SpritesheetRecord> => {
|
|
const response = await fetch(`${POCKETBASE_URL}/api/collections/${COLLECTION}/records/${id}`);
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 404) {
|
|
throw new Error('Spritesheet not found');
|
|
}
|
|
const text = await response.text().catch(() => '');
|
|
throw new Error(text || `Request failed with status ${response.status}`);
|
|
}
|
|
|
|
return response.json();
|
|
};
|
|
|
|
/**
|
|
* Composable hook for share functionality
|
|
*/
|
|
export const useShare = (layersRef: Ref<Layer[]>, columns: Ref<number>, negativeSpacingEnabled: Ref<boolean>, backgroundColor?: Ref<string>, manualCellSizeEnabled?: Ref<boolean>, manualCellWidth?: Ref<number>, manualCellHeight?: Ref<number>) => {
|
|
const share = () => shareSpritesheet(layersRef, columns, negativeSpacingEnabled, backgroundColor, manualCellSizeEnabled, manualCellWidth, manualCellHeight);
|
|
|
|
return {
|
|
share,
|
|
fetchSpritesheet,
|
|
buildShareUrl,
|
|
};
|
|
};
|