/** * extractArtifactToCanvas — Pull a generated artifact out of a generator shape * and place it as a standalone canvas object (folk-image, folk-embed, or folk-bookmark). */ import type { FolkShape } from "./folk-shape"; export type ArtifactMediaType = "image" | "video" | "pdf" | "download"; interface ExtractOptions { url: string; mediaType: ArtifactMediaType; title?: string; sourceShape: FolkShape; } interface CanvasApi { newShape: (tagName: string, props?: Record, atPosition?: { x: number; y: number }) => any; findFreePosition: (w: number, h: number, px?: number, py?: number, exclude?: any) => { x: number; y: number }; SHAPE_DEFAULTS: Record; } const TAG_MAP: Record = { image: "folk-image", video: "folk-embed", pdf: "folk-embed", download: "folk-bookmark", }; export function extractArtifactToCanvas({ url, mediaType, title, sourceShape }: ExtractOptions): boolean { const api = (window as any).__canvasApi as CanvasApi | undefined; if (!api) { console.warn("[extract-artifact] Canvas API not available"); return false; } const tagName = TAG_MAP[mediaType]; const defaults = api.SHAPE_DEFAULTS[tagName] || { width: 400, height: 300 }; // Position to the right of the source shape const preferX = sourceShape.x + sourceShape.width + 40 + defaults.width / 2; const preferY = sourceShape.y + sourceShape.height / 2; const pos = api.findFreePosition(defaults.width, defaults.height, preferX, preferY, sourceShape); const props: Record = {}; if (mediaType === "image") { props.src = url; if (title) props.alt = title; } else if (mediaType === "video" || mediaType === "pdf") { props.url = url; } else { props.url = url; if (title) props.title = title; } api.newShape(tagName, props, { x: pos.x + defaults.width / 2, y: pos.y + defaults.height / 2 }); return true; } /** CSS for the extract button — inject into each component's stylesheet */ export const extractBtnCss = ` .extract-btn { position: absolute; top: 6px; right: 6px; padding: 3px 8px; background: rgba(0, 0, 0, 0.6); color: white; border: none; border-radius: 12px; font-size: 12px; cursor: pointer; opacity: 0; transition: opacity 0.15s; z-index: 1; line-height: 1; } .image-item:hover .extract-btn, .video-item:hover .extract-btn, .section:hover .extract-btn, .render-preview:hover .extract-btn, .preview-area:hover > .extract-btn { opacity: 1; } .extract-btn:hover { background: rgba(0, 0, 0, 0.8); } `;