273 lines
8.3 KiB
TypeScript
273 lines
8.3 KiB
TypeScript
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<T>(...values: Array<T | undefined | null>): 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<string, unknown> | 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<string, unknown>;
|
|
const data = (root.data as Record<string, unknown> | undefined) ?? undefined;
|
|
const state = (root.state as Record<string, unknown> | undefined) ?? undefined;
|
|
const project = (root.project as Record<string, unknown> | undefined) ?? undefined;
|
|
|
|
const editCanvasConfig = pick<GoViewEditCanvasConfigLike>(
|
|
(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<TextWidgetNode | ImageWidgetNode | IframeWidgetNode | VideoWidgetNode> = [];
|
|
|
|
for (const raw of componentList) {
|
|
const c = unwrapComponent(raw);
|
|
|
|
const rect = c.attr
|
|
? {
|
|
x: toNumber((c.attr as unknown as Record<string, unknown>).x, 0),
|
|
y: toNumber((c.attr as unknown as Record<string, unknown>).y, 0),
|
|
w: toNumber((c.attr as unknown as Record<string, unknown>).w, 320),
|
|
h: toNumber((c.attr as unknown as Record<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).zIndex, 0),
|
|
locked: c.status?.lock ?? false,
|
|
hidden: c.status?.hide ?? false,
|
|
props,
|
|
});
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return {
|
|
...screen,
|
|
nodes,
|
|
};
|
|
}
|