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, columns: Ref, negativeSpacingEnabled: Ref, backgroundColor?: Ref, manualCellSizeEnabled?: Ref, manualCellWidth?: Ref, manualCellHeight?: Ref): Promise => { // 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 => { 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, columns: Ref, negativeSpacingEnabled: Ref, backgroundColor?: Ref, manualCellSizeEnabled?: Ref, manualCellWidth?: Ref, manualCellHeight?: Ref) => { const share = () => shareSpritesheet(layersRef, columns, negativeSpacingEnabled, backgroundColor, manualCellSizeEnabled, manualCellWidth, manualCellHeight); return { share, fetchSpritesheet, buildShareUrl, }; };