import { ExcalidrawElement } from "../../packages/excalidraw/element/types"; import { AppState, BinaryFiles, Collaborator, SocketId, } from "../../packages/excalidraw/types"; /** * Describes the metadata of a canvas. */ export interface CanvasMetadata { id: string; name: string; createdAt: string; updatedAt: string; userId: number; thumbnail?: string; } /** * Encapsulates the complete data for a single canvas. */ export interface CanvasData { elements: readonly ExcalidrawElement[]; appState: AppState; files: BinaryFiles; thumbnail?: string; } /** * Defines the contract for all storage adapters. * Any storage backend (default server, Cloudflare KV, S3, etc.) * must implement this interface to be compatible with the application. */ export interface IStorageAdapter { /** * Lists all canvases available for the current user. */ listCanvases(): Promise; /** * Loads a single canvas's data. * @param id The unique identifier of the canvas to load. * @returns The canvas data, or null if not found. */ loadCanvas(id: string): Promise; /** * Saves a canvas's data. This is typically used for updating an existing canvas. * @param id The unique identifier of the canvas to save. * @param data The complete data of the canvas. */ saveCanvas(id: string, data: CanvasData): Promise; /** * Creates a new canvas. * @param data The initial data for the new canvas. * @returns The metadata of the newly created canvas. */ createCanvas(data: CanvasData): Promise; /** * Deletes a canvas. * @param id The unique identifier of the canvas to delete. */ deleteCanvas(id: string): Promise; /** * Renames a canvas. * @param id The unique identifier of the canvas to rename. * @param newName The new name for the canvas. */ renameCanvas(id: string, newName: string): Promise; } /** * Converts a raw JSON object into a CanvasData object, ensuring complex types * like Map are correctly instantiated. * @param data The raw data from the API. */ export const hydrateCanvasData = (data: any): CanvasData => { const canvasData: CanvasData = { ...data }; // Ensure collaborators is a Map, not an object. if ( canvasData.appState && canvasData.appState.collaborators && !(canvasData.appState.collaborators instanceof Map) ) { canvasData.appState.collaborators = new Map( Object.entries( canvasData.appState.collaborators as { [key: string]: Collaborator }, ).map(([key, value]) => [key as SocketId, value]), ); } else if (canvasData.appState && !canvasData.appState.collaborators) { // Ensure collaborators is at least an empty Map if it's missing. canvasData.appState.collaborators = new Map(); } return canvasData; }; /** * Prepares canvas data for JSON serialization, converting complex types like Map * into plain objects. * @param data The CanvasData object to dehydrate. */ export const dehydrateCanvasData = (data: CanvasData) => { const dehydratedData = { ...data, appState: { ...data.appState, collaborators: data.appState.collaborators instanceof Map ? Object.fromEntries(data.appState.collaborators) : data.appState.collaborators, }, }; return dehydratedData; };