Compare commits

..

No commits in common. "c20e4796ae7e28ae4bdee915c25b7e9fa6af9c7d" and "e58be35cee814c9f6d66be888467f60b54f6831a" have entirely different histories.

4 changed files with 47 additions and 85 deletions

View File

@ -6,7 +6,6 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc -b && vite build", "build": "tsc -b && vite build",
"typecheck": "tsc -b --noEmit",
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview" "preview": "vite preview"
}, },

View File

@ -158,9 +158,6 @@ function looksLikeIframeOption(option: unknown): boolean {
if (typeof option === 'object') { if (typeof option === 'object') {
const o = option as Record<string, unknown>; const o = option as Record<string, unknown>;
if ('iframeUrl' in o || 'iframeSrc' in o || 'embedUrl' in o) return true; if ('iframeUrl' in o || 'iframeSrc' in o || 'embedUrl' in o) return true;
// Some exports store raw HTML instead of a URL.
if ('html' in o || 'htmlContent' in o || 'content' in o || 'template' in o) return true;
} }
const url = pickUrlLike(option); const url = pickUrlLike(option);
@ -184,19 +181,7 @@ function looksLikeVideoOption(option: unknown): boolean {
// Prefer explicit video-ish keys when option is an object. // Prefer explicit video-ish keys when option is an object.
if (typeof option === 'object') { if (typeof option === 'object') {
const o = option as Record<string, unknown>; const o = option as Record<string, unknown>;
if ( if ('videoUrl' in o || 'videoSrc' in o || 'mp4' in o || 'm3u8' in o || 'flv' in o || 'hls' in o || 'rtsp' in o) {
'videoUrl' in o ||
'videoSrc' in o ||
'mp4' in o ||
'm3u8' in o ||
'flv' in o ||
'hls' in o ||
'rtsp' in o ||
// list-ish shapes
'sources' in o ||
'sourceList' in o ||
'urlList' in o
) {
return true; return true;
} }
} }
@ -338,53 +323,25 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt
for (const raw of componentList) { for (const raw of componentList) {
const c = unwrapComponent(raw); const c = unwrapComponent(raw);
const option = optionOf(c); const option = optionOf(c);
// We try to infer the widget kind early so we can pick better default sizes
// when exports omit sizing information.
const inferredType: 'text' | 'image' | 'iframe' | 'video' | undefined = isTextCommon(c)
? 'text'
: isImage(c) || looksLikeImageOption(option)
? 'image'
: // Important: run video checks before iframe checks; iframe URL detection is broader.
isVideo(c) || looksLikeVideoOption(option)
? 'video'
: isIframe(c) || looksLikeIframeOption(option)
? 'iframe'
: undefined;
const defaults =
inferredType === 'text'
? { w: 320, h: 60 }
: inferredType === 'image'
? { w: 320, h: 180 }
: inferredType === 'iframe'
? { w: 480, h: 270 }
: inferredType === 'video'
? { w: 480, h: 270 }
: { w: 320, h: 60 };
const optSize = pickSizeLike(option); const optSize = pickSizeLike(option);
const attr = c.attr as unknown as Record<string, unknown> | undefined; const rect = c.attr
const rect = attr
? { ? {
x: toNumber(attr.x, 0), x: toNumber((c.attr as unknown as Record<string, unknown>).x, 0),
y: toNumber(attr.y, 0), y: toNumber((c.attr as unknown as Record<string, unknown>).y, 0),
// Prefer explicit attr sizing, but fall back to option sizing when missing. // Prefer explicit attr sizing, but fall back to option sizing when missing.
w: toNumber(attr.w, optSize.w ?? defaults.w), w: toNumber((c.attr as unknown as Record<string, unknown>).w, optSize.w ?? 320),
h: toNumber(attr.h, optSize.h ?? defaults.h), h: toNumber((c.attr as unknown as Record<string, unknown>).h, optSize.h ?? 60),
} }
: { x: 0, y: 0, w: optSize.w ?? defaults.w, h: optSize.h ?? defaults.h }; : { x: 0, y: 0, w: optSize.w ?? 320, h: optSize.h ?? 60 };
const zIndex = attr?.zIndex === undefined ? undefined : toNumber(attr.zIndex, 0); if (isTextCommon(c)) {
if (inferredType === 'text') {
const props = convertGoViewTextOptionToNodeProps(option as GoViewTextOption); const props = convertGoViewTextOptionToNodeProps(option as GoViewTextOption);
nodes.push({ nodes.push({
id: c.id ?? `import_text_${Math.random().toString(16).slice(2)}`, id: c.id ?? `import_text_${Math.random().toString(16).slice(2)}`,
type: 'text', type: 'text',
rect, rect,
zIndex, zIndex: c.attr?.zIndex === undefined ? undefined : toNumber((c.attr as unknown as Record<string, unknown>).zIndex, 0),
locked: c.status?.lock ?? false, locked: c.status?.lock ?? false,
hidden: c.status?.hide ?? false, hidden: c.status?.hide ?? false,
props, props,
@ -392,13 +349,13 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt
continue; continue;
} }
if (inferredType === 'image') { if (isImage(c) || looksLikeImageOption(option)) {
const props = convertGoViewImageOptionToNodeProps(option as GoViewImageOption); const props = convertGoViewImageOptionToNodeProps(option as GoViewImageOption);
nodes.push({ nodes.push({
id: c.id ?? `import_image_${Math.random().toString(16).slice(2)}`, id: c.id ?? `import_image_${Math.random().toString(16).slice(2)}`,
type: 'image', type: 'image',
rect, rect,
zIndex, zIndex: c.attr?.zIndex === undefined ? undefined : toNumber((c.attr as unknown as Record<string, unknown>).zIndex, 0),
locked: c.status?.lock ?? false, locked: c.status?.lock ?? false,
hidden: c.status?.hide ?? false, hidden: c.status?.hide ?? false,
props, props,
@ -406,13 +363,14 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt
continue; continue;
} }
if (inferredType === 'iframe') { // Prefer explicit component keys over heuristics.
if (isIframe(c)) {
const props = convertGoViewIframeOptionToNodeProps(option as GoViewIframeOption); const props = convertGoViewIframeOptionToNodeProps(option as GoViewIframeOption);
nodes.push({ nodes.push({
id: c.id ?? `import_iframe_${Math.random().toString(16).slice(2)}`, id: c.id ?? `import_iframe_${Math.random().toString(16).slice(2)}`,
type: 'iframe', type: 'iframe',
rect, rect,
zIndex, zIndex: c.attr?.zIndex === undefined ? undefined : toNumber((c.attr as unknown as Record<string, unknown>).zIndex, 0),
locked: c.status?.lock ?? false, locked: c.status?.lock ?? false,
hidden: c.status?.hide ?? false, hidden: c.status?.hide ?? false,
props, props,
@ -420,13 +378,43 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt
continue; continue;
} }
if (inferredType === 'video') { if (isVideo(c)) {
const props = convertGoViewVideoOptionToNodeProps(option as GoViewVideoOption); const props = convertGoViewVideoOptionToNodeProps(option as GoViewVideoOption);
nodes.push({ nodes.push({
id: c.id ?? `import_video_${Math.random().toString(16).slice(2)}`, id: c.id ?? `import_video_${Math.random().toString(16).slice(2)}`,
type: 'video', type: 'video',
rect, rect,
zIndex, 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;
}
// Heuristic fallback: distinguish common URL patterns.
// Important: run video checks before iframe checks; iframe URL detection is broader.
if (looksLikeVideoOption(option)) {
const props = convertGoViewVideoOptionToNodeProps(option 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;
}
if (looksLikeIframeOption(option)) {
const props = convertGoViewIframeOptionToNodeProps(option 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, locked: c.status?.lock ?? false,
hidden: c.status?.hide ?? false, hidden: c.status?.hide ?? false,
props, props,

View File

@ -17,29 +17,9 @@ export interface GoViewIframeOption {
*/ */
export type LegacyIframeOption = GoViewIframeOption; export type LegacyIframeOption = GoViewIframeOption;
function toDataHtmlUrl(html: string): string {
// Keep this simple and safe: use a data URL so we can render the provided HTML
// without needing an external host.
// NOTE: encodeURIComponent is important to preserve characters + avoid breaking the URL.
return `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
}
function pickSrc(option: GoViewIframeOption): string { function pickSrc(option: GoViewIframeOption): string {
// Prefer the whole option first (covers iframeUrl/embedUrl variants directly on the object). // Prefer the whole option first (covers iframeUrl/embedUrl variants directly on the object).
const url = pickUrlLike(option) || pickUrlLike(option.dataset) || pickUrlLike(option.src) || pickUrlLike(option.url); return pickUrlLike(option) || pickUrlLike(option.dataset) || pickUrlLike(option.src) || pickUrlLike(option.url);
if (url) return url;
// Some goView / low-code exports store raw HTML instead of a URL.
const html = pickFromNested(
option,
(obj) => {
const v = obj.html ?? obj.htmlContent ?? obj.content ?? obj.template;
return typeof v === 'string' ? v : undefined;
},
2,
);
return html ? toDataHtmlUrl(html) : '';
} }
function toMaybeNumber(v: unknown): number | undefined { function toMaybeNumber(v: unknown): number | undefined {

View File

@ -34,7 +34,6 @@ function pickUrlLikeInner(input: unknown, depth: number): string {
for (const key of [ for (const key of [
'value', 'value',
'url', 'url',
'uri',
'src', 'src',
'href', 'href',
'link', 'link',
@ -87,10 +86,6 @@ function pickUrlLikeInner(input: unknown, depth: number): string {
'props', 'props',
'source', 'source',
'media', 'media',
// common list-ish wrappers for media sources
'sources',
'sourceList',
'urlList',
// widget-ish wrappers seen in exports // widget-ish wrappers seen in exports
'iframe', 'iframe',
'video', 'video',