sdk: harden legacy import (iframe/video/image)

This commit is contained in:
ErSan 2026-01-27 19:54:01 +08:00
parent 299326e060
commit b6f69b8d6a
5 changed files with 125 additions and 28 deletions

View File

@ -1,13 +1,16 @@
import type { Screen } from './schema';
import { convertGoViewProjectToScreen } from './goview/convert';
/**
* goView JSON converter (stub).
* goView JSON converter.
*
* The legacy goView format isn't implemented yet; this is a placeholder so we can
* start wiring UI + migration flows without committing to the full mapping.
* We keep this permissive because goView exports / storage snapshots vary across
* versions and forks. The heavy lifting lives in `goview/convert.ts`.
*/
export function convertGoViewJSONToScreen(input: unknown): Screen {
// keep reference to avoid unused-vars lint until implemented
void input;
throw new Error('convertGoViewJSONToScreen: not implemented yet');
if (!input || typeof input !== 'object') {
throw new Error('convertGoViewJSONToScreen: expected object');
}
return convertGoViewProjectToScreen(input as unknown as object);
}

View File

@ -85,13 +85,40 @@ function isVideo(c: GoViewComponentLike): boolean {
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;
}
export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewStorageLike): Screen {
const editCanvasConfig = (input as GoViewStorageLike).editCanvasConfig;
// 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 ?? 1920;
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 ?? 1080;
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;
@ -105,7 +132,13 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt
nodes: [],
});
const componentList = (input as GoViewStorageLike).componentList ?? (input as GoViewProjectLike).componentList ?? [];
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> = [];

View File

@ -5,9 +5,9 @@ import type { IframeWidgetNode } from '../schema';
* Keep it permissive and normalize the common fields.
*/
export interface GoViewIframeOption {
dataset?: string;
src?: string;
url?: string;
dataset?: unknown;
src?: unknown;
url?: unknown;
borderRadius?: number;
}
@ -16,8 +16,23 @@ export interface GoViewIframeOption {
*/
export type LegacyIframeOption = GoViewIframeOption;
function asString(v: unknown): string {
if (typeof v === 'string') return v;
if (!v) return '';
// Some goView widgets store data as { value: '...' } or { url: '...' }.
if (typeof v === 'object') {
const obj = v as Record<string, unknown>;
if (typeof obj.value === 'string') return obj.value;
if (typeof obj.url === 'string') return obj.url;
if (typeof obj.src === 'string') return obj.src;
}
return '';
}
function pickSrc(option: GoViewIframeOption): string {
return option.dataset ?? option.src ?? option.url ?? '';
return asString(option.dataset) || asString(option.src) || asString(option.url);
}
export function convertGoViewIframeOptionToNodeProps(option: GoViewIframeOption): IframeWidgetNode['props'] {

View File

@ -8,28 +8,51 @@ export interface GoViewImageOption {
/**
* Common in existing legacy widgets (same as iframe/video).
*/
dataset?: string;
dataset?: unknown;
/**
* Other variants seen in the wild.
*/
src?: string;
url?: string;
src?: unknown;
url?: unknown;
/**
* Styling.
*/
fit?: ImageWidgetNode['props']['fit'];
objectFit?: ImageWidgetNode['props']['fit'];
fit?: unknown;
objectFit?: unknown;
borderRadius?: number;
}
function asString(v: unknown): string {
if (typeof v === 'string') return v;
if (!v) return '';
// Some goView widgets store data as { value: '...' } or { url: '...' }.
if (typeof v === 'object') {
const obj = v as Record<string, unknown>;
if (typeof obj.value === 'string') return obj.value;
if (typeof obj.url === 'string') return obj.url;
if (typeof obj.src === 'string') return obj.src;
}
return '';
}
function pickSrc(option: GoViewImageOption): string {
return option.dataset ?? option.src ?? option.url ?? '';
return asString(option.dataset) || asString(option.src) || asString(option.url);
}
function pickFit(option: GoViewImageOption): ImageWidgetNode['props']['fit'] | undefined {
return option.fit ?? option.objectFit;
const raw = asString(option.fit) || asString(option.objectFit);
if (!raw) return undefined;
const v = raw.toLowerCase();
if (v === 'contain' || v === 'cover' || v === 'fill' || v === 'none' || v === 'scale-down') {
return v as ImageWidgetNode['props']['fit'];
}
return undefined;
}
export function convertGoViewImageOptionToNodeProps(option: GoViewImageOption): ImageWidgetNode['props'] {

View File

@ -5,15 +5,15 @@ import type { VideoWidgetNode } from '../schema';
* Keep it permissive and normalize the common fields.
*/
export interface GoViewVideoOption {
dataset?: string;
src?: string;
url?: string;
dataset?: unknown;
src?: unknown;
url?: unknown;
loop?: boolean;
muted?: boolean;
fit?: VideoWidgetNode['props']['fit'];
objectFit?: VideoWidgetNode['props']['fit'];
fit?: unknown;
objectFit?: unknown;
borderRadius?: number;
}
@ -23,12 +23,35 @@ export interface GoViewVideoOption {
*/
export type LegacyVideoOption = GoViewVideoOption;
function asString(v: unknown): string {
if (typeof v === 'string') return v;
if (!v) return '';
if (typeof v === 'object') {
const obj = v as Record<string, unknown>;
if (typeof obj.value === 'string') return obj.value;
if (typeof obj.url === 'string') return obj.url;
if (typeof obj.src === 'string') return obj.src;
}
return '';
}
function pickSrc(option: GoViewVideoOption): string {
return option.dataset ?? option.src ?? option.url ?? '';
return asString(option.dataset) || asString(option.src) || asString(option.url);
}
function pickFit(option: GoViewVideoOption): VideoWidgetNode['props']['fit'] | undefined {
return option.fit ?? option.objectFit;
const raw = asString(option.fit) || asString(option.objectFit);
if (!raw) return undefined;
// normalize common variants
const v = raw.toLowerCase();
if (v === 'contain' || v === 'cover' || v === 'fill' || v === 'none' || v === 'scale-down') {
return v as VideoWidgetNode['props']['fit'];
}
return undefined;
}
export function convertGoViewVideoOptionToNodeProps(option: GoViewVideoOption): VideoWidgetNode['props'] {