[FEAT] Add sharing function, UI enhancement

This commit is contained in:
2025-12-14 19:06:55 +01:00
parent 883c93b7ff
commit a381900356
9 changed files with 664 additions and 59 deletions

135
src/composables/useShare.ts Normal file
View File

@@ -0,0 +1,135 @@
import type { Ref } from 'vue';
import type { Layer } from '@/types/sprites';
const POCKETBASE_URL = 'https://pb1.adhd.sh';
const COLLECTION = 'spritesheets';
export interface SpritesheetConfig {
version: number;
columns: number;
negativeSpacingEnabled: boolean;
backgroundColor: string;
manualCellSizeEnabled: boolean;
manualCellWidth: number;
manualCellHeight: number;
}
export interface SpritesheetRecord {
id: string;
config: SpritesheetConfig;
sprites: any[];
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;
ctx.drawImage(sprite.img, 0, 0);
const base64 = canvas.toDataURL('image/png');
return {
id: sprite.id,
width: sprite.width,
height: sprite.height,
x: sprite.x,
y: sprite.y,
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,
};
};