import { ASTRALVIEW_SCHEMA_VERSION, createEmptyScreen, type ImageWidgetNode, type IframeWidgetNode, type Screen, type TextWidgetNode, type VideoWidgetNode, } from '../schema'; import { convertGoViewImageOptionToNodeProps, type GoViewImageOption } from '../widgets/image'; import { convertGoViewIframeOptionToNodeProps, type GoViewIframeOption } from '../widgets/iframe'; import { convertGoViewVideoOptionToNodeProps, type GoViewVideoOption } from '../widgets/video'; import { convertGoViewTextOptionToNodeProps, type GoViewTextOption } from '../widgets/text'; export interface GoViewComponentLike { id?: string; // some exports wrap the actual component under a nested field component?: GoViewComponentLike; // component identity key?: string; // e.g. "TextCommon" (sometimes) componentKey?: string; chartConfig?: { key?: string; chartKey?: string; type?: string; option?: unknown; data?: unknown; }; // geometry attr?: { x: number; y: number; w: number; h: number; zIndex?: number }; // state status?: { lock?: boolean; hide?: boolean }; // widget-specific config option?: unknown; } export interface GoViewEditCanvasConfigLike { projectName?: string; width?: number; height?: number; background?: string; } export interface GoViewStorageLike { editCanvasConfig?: GoViewEditCanvasConfigLike; componentList?: GoViewComponentLike[]; } export interface GoViewProjectLike { width?: number; height?: number; canvas?: { width?: number; height?: number }; componentList?: GoViewComponentLike[]; // persisted store shape (some variants) editCanvasConfig?: GoViewEditCanvasConfigLike; } function unwrapComponent(c: GoViewComponentLike): GoViewComponentLike { // Prefer the nested component shape but keep outer fields as fallback. // This handles exports like: { id, attr, component: { chartConfig, option } } const inner = c.component; if (!inner) return c; return { ...inner, ...c, // ensure the nested component doesn't get lost component: inner.component, }; } function keyOf(cIn: GoViewComponentLike): string { const c = unwrapComponent(cIn); return ( c.chartConfig?.key ?? c.chartConfig?.chartKey ?? c.chartConfig?.type ?? c.componentKey ?? c.key ?? '' ).toLowerCase(); } function isTextCommon(c: GoViewComponentLike): boolean { const k = keyOf(c); if (k === 'textcommon') return true; return k.includes('text'); } function isImage(c: GoViewComponentLike): boolean { const k = keyOf(c); // goView variants: "Image", "image", sometimes with suffixes. return k === 'image' || k.includes('image') || k.includes('picture'); } function isIframe(c: GoViewComponentLike): boolean { const k = keyOf(c); // goView variants: "Iframe", "IframeCommon", etc. if (k === 'iframe' || k.includes('iframe')) return true; // Other names seen in low-code editors for embedded web content. return k.includes('embed') || k.includes('web') || k.includes('html'); } function isVideo(c: GoViewComponentLike): boolean { const k = keyOf(c); // goView variants: "Video", "VideoCommon", etc. if (k === 'video' || k.includes('video')) return true; // Other names seen in the wild. return k.includes('mp4') || k.includes('media') || k.includes('player'); } function pick(...values: Array): T | undefined { for (const v of values) { if (v !== undefined && v !== null) return v as T; } return undefined; } function optionOf(cIn: GoViewComponentLike): unknown { const c = unwrapComponent(cIn); const chartData = c.chartConfig?.data as Record | undefined; return ( c.option ?? c.chartConfig?.option ?? chartData?.option ?? chartData?.options ?? chartData?.config ?? chartData ?? {} ); } function toNumber(v: unknown, fallback: number): number { if (typeof v === 'number' && Number.isFinite(v)) return v; if (typeof v === 'string') { const n = Number(v); if (Number.isFinite(n)) return n; } return fallback; } export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewStorageLike): Screen { // goView exports vary a lot; attempt a few common nesting shapes. const root = input as unknown as Record; const data = (root.data as Record | undefined) ?? undefined; const state = (root.state as Record | undefined) ?? undefined; const project = (root.project as Record | undefined) ?? undefined; const editCanvasConfig = pick( (input as GoViewStorageLike).editCanvasConfig, data?.editCanvasConfig as GoViewEditCanvasConfigLike | undefined, state?.editCanvasConfig as GoViewEditCanvasConfigLike | undefined, project?.editCanvasConfig as GoViewEditCanvasConfigLike | undefined, ); const width = editCanvasConfig?.width ?? (input as GoViewProjectLike).canvas?.width ?? (input as GoViewProjectLike).width ?? (data?.width as number | undefined) ?? 1920; const height = editCanvasConfig?.height ?? (input as GoViewProjectLike).canvas?.height ?? (input as GoViewProjectLike).height ?? (data?.height as number | undefined) ?? 1080; const name = editCanvasConfig?.projectName ?? 'Imported Project'; const background = editCanvasConfig?.background; const screen = createEmptyScreen({ version: ASTRALVIEW_SCHEMA_VERSION, width, height, name, background: background ? { color: background } : undefined, nodes: [], }); const componentList = (input as GoViewStorageLike).componentList ?? (input as GoViewProjectLike).componentList ?? (data?.componentList as GoViewComponentLike[] | undefined) ?? (state?.componentList as GoViewComponentLike[] | undefined) ?? (project?.componentList as GoViewComponentLike[] | undefined) ?? []; const nodes: Array = []; for (const raw of componentList) { const c = unwrapComponent(raw); const rect = c.attr ? { x: toNumber((c.attr as unknown as Record).x, 0), y: toNumber((c.attr as unknown as Record).y, 0), w: toNumber((c.attr as unknown as Record).w, 320), h: toNumber((c.attr as unknown as Record).h, 60), } : { x: 0, y: 0, w: 320, h: 60 }; if (isTextCommon(c)) { const props = convertGoViewTextOptionToNodeProps(optionOf(c) as GoViewTextOption); nodes.push({ id: c.id ?? `import_text_${Math.random().toString(16).slice(2)}`, type: 'text', rect, zIndex: c.attr?.zIndex === undefined ? undefined : toNumber((c.attr as unknown as Record).zIndex, 0), locked: c.status?.lock ?? false, hidden: c.status?.hide ?? false, props, }); continue; } if (isImage(c)) { const props = convertGoViewImageOptionToNodeProps(optionOf(c) as GoViewImageOption); nodes.push({ id: c.id ?? `import_image_${Math.random().toString(16).slice(2)}`, type: 'image', rect, zIndex: c.attr?.zIndex === undefined ? undefined : toNumber((c.attr as unknown as Record).zIndex, 0), locked: c.status?.lock ?? false, hidden: c.status?.hide ?? false, props, }); continue; } if (isIframe(c)) { const props = convertGoViewIframeOptionToNodeProps(optionOf(c) as GoViewIframeOption); nodes.push({ id: c.id ?? `import_iframe_${Math.random().toString(16).slice(2)}`, type: 'iframe', rect, zIndex: c.attr?.zIndex === undefined ? undefined : toNumber((c.attr as unknown as Record).zIndex, 0), locked: c.status?.lock ?? false, hidden: c.status?.hide ?? false, props, }); continue; } if (isVideo(c)) { const props = convertGoViewVideoOptionToNodeProps(optionOf(c) as GoViewVideoOption); nodes.push({ id: c.id ?? `import_video_${Math.random().toString(16).slice(2)}`, type: 'video', rect, zIndex: c.attr?.zIndex === undefined ? undefined : toNumber((c.attr as unknown as Record).zIndex, 0), locked: c.status?.lock ?? false, hidden: c.status?.hide ?? false, props, }); continue; } } return { ...screen, nodes, }; }