From 01b7a3a722c824fec0a54e689d8a0287740359c6 Mon Sep 17 00:00:00 2001 From: clawdbot Date: Wed, 28 Jan 2026 10:01:54 +0800 Subject: [PATCH] refactor: improve goView import (video/iframe) + editor state --- packages/editor/src/editor/store.ts | 61 +++++++++++++------------ packages/sdk/src/core/goview/convert.ts | 44 +++++++++++++++--- 2 files changed, 70 insertions(+), 35 deletions(-) diff --git a/packages/editor/src/editor/store.ts b/packages/editor/src/editor/store.ts index a58d7b2..15ca68c 100644 --- a/packages/editor/src/editor/store.ts +++ b/packages/editor/src/editor/store.ts @@ -1,4 +1,5 @@ import { + assertNever, convertGoViewProjectToScreen, createEmptyScreen, migrateScreen, @@ -78,13 +79,13 @@ interface BoxSelectSession { baseIds: string[]; } -type InternalEditorState = EditorState & { +type EditorRuntimeState = EditorState & { __pan?: PanSession; __boxSelect?: BoxSelectSession; __drag?: DragSession; }; -function historyPush(state: EditorState): EditorState { +function historyPush(state: EditorRuntimeState): EditorRuntimeState { return { ...state, history: { @@ -94,12 +95,12 @@ function historyPush(state: EditorState): EditorState { }; } -function ensureSelected(state: EditorState, id: string): EditorState { +function ensureSelected(state: EditorRuntimeState, id: string): EditorRuntimeState { if (state.selection.ids.includes(id)) return state; return { ...state, selection: { ids: [id] } }; } -export function createInitialState(): EditorState { +export function createInitialState(): EditorRuntimeState { const screen = createEmptyScreen({ width: 1920, height: 1080, @@ -141,8 +142,8 @@ export function createInitialState(): EditorState { }; } -export function editorReducer(state: EditorState, action: EditorAction): EditorState { - const s = state as InternalEditorState; +export function editorReducer(state: EditorRuntimeState, action: EditorAction): EditorRuntimeState { + const s = state; switch (action.type) { case 'keyboard': return { ...state, keyboard: { ctrl: action.ctrl, space: action.space } }; @@ -454,7 +455,7 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS startPanX: state.canvas.panX, startPanY: state.canvas.panY, }, - } as EditorState; + }; } case 'updatePan': { @@ -476,15 +477,14 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS case 'endPan': { if (!state.canvas.isPanning) return state; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { __pan: _pan, ...rest } = s; return { - ...rest, + ...state, canvas: { ...state.canvas, isPanning: false, }, - } as EditorState; + __pan: undefined, + }; } case 'selectSingle': @@ -525,7 +525,7 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS }, selection: { ids: baseIds }, __boxSelect: { additive: action.additive, baseIds }, - } as EditorState; + }; } case 'updateBoxSelect': { @@ -567,16 +567,15 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS case 'endBoxSelect': { if (!state.canvas.isBoxSelecting) return state; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { __boxSelect: _box, ...rest } = s; return { - ...rest, + ...state, canvas: { ...state.canvas, isBoxSelecting: false, mouse: { ...state.canvas.mouse, offsetX: 0, offsetY: 0, offsetStartX: 0, offsetStartY: 0 }, }, - } as EditorState; + __boxSelect: undefined, + }; } case 'beginMove': { @@ -614,7 +613,7 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS }, }, __drag: drag, - } as EditorState; + }; } case 'updateMove': { @@ -669,17 +668,19 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS case 'endMove': { const drag = s.__drag; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { __drag: _drag, ...rest } = s; - if (!drag || drag.kind !== 'move') { - return { ...rest, canvas: { ...state.canvas, isDragging: false, guides: { xs: [], ys: [] } } }; + return { + ...state, + canvas: { ...state.canvas, isDragging: false, guides: { xs: [], ys: [] } }, + __drag: undefined, + }; } const changed = didRectsChange(drag.snapshot, state.doc.screen); return { - ...rest, + ...state, canvas: { ...state.canvas, isDragging: false, guides: { xs: [], ys: [] } }, + __drag: undefined, history: changed ? { past: [...state.history.past, { screen: drag.beforeScreen }], @@ -709,7 +710,7 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS ...next, canvas: { ...next.canvas, isDragging: true, guides: { xs: [], ys: [] } }, __drag: drag, - } as EditorState; + }; } case 'updateResize': { @@ -774,17 +775,19 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS case 'endResize': { const drag = s.__drag; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { __drag: _drag, ...rest } = s; - if (!drag || drag.kind !== 'resize') { - return { ...rest, canvas: { ...state.canvas, isDragging: false, guides: { xs: [], ys: [] } } }; + return { + ...state, + canvas: { ...state.canvas, isDragging: false, guides: { xs: [], ys: [] } }, + __drag: undefined, + }; } const changed = didRectsChange(drag.snapshot, state.doc.screen); return { - ...rest, + ...state, canvas: { ...state.canvas, isDragging: false, guides: { xs: [], ys: [] } }, + __drag: undefined, history: changed ? { past: [...state.history.past, { screen: drag.beforeScreen }], @@ -830,7 +833,7 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS } default: - return state; + return assertNever(action); } } diff --git a/packages/sdk/src/core/goview/convert.ts b/packages/sdk/src/core/goview/convert.ts index d008379..c4d14fe 100644 --- a/packages/sdk/src/core/goview/convert.ts +++ b/packages/sdk/src/core/goview/convert.ts @@ -270,7 +270,22 @@ function looksLikeIframeOption(option: unknown): boolean { if ( hasAnyKeyDeep(option, ['iframeUrl', 'iframeSrc', 'embedUrl', 'frameUrl', 'frameSrc'], 2) || // Some exports store raw HTML instead of a URL. - hasAnyKeyDeep(option, ['srcdoc', 'srcDoc', 'html', 'htmlContent', 'content', 'template'], 2) + hasAnyKeyDeep( + option, + [ + 'srcdoc', + 'srcDoc', + 'html', + 'htmlContent', + 'htmlString', + 'iframeHtml', + 'embedHtml', + 'embedCode', + 'content', + 'template', + ], + 2, + ) ) { return true; } @@ -401,6 +416,12 @@ function normalizeOption(option: unknown): unknown { return option; } +function toMaybeString(v: unknown): string | undefined { + if (typeof v === 'string') return v; + if (typeof v === 'number' && Number.isFinite(v)) return String(v); + return undefined; +} + function toNumber(v: unknown, fallback: number): number { if (typeof v === 'number' && Number.isFinite(v)) return v; if (typeof v === 'string') { @@ -451,6 +472,15 @@ function pickSizeLike(option: unknown): { w?: number; h?: number } { return { w, h }; } +function normalizeRect( + rect: { x: number; y: number; w: number; h: number }, + fallback: { w: number; h: number }, +): { x: number; y: number; w: number; h: number } { + const w = rect.w > 0 ? rect.w : fallback.w; + const h = rect.h > 0 ? rect.h : fallback.h; + return { x: rect.x, y: rect.y, w, h }; +} + 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; @@ -566,7 +596,7 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt const optSize = pickSizeLike(option); const attr = c.attr as unknown as Record | undefined; - const rect = attr + const rawRect = attr ? { x: toNumber(attr.x, 0), y: toNumber(attr.y, 0), @@ -575,13 +605,15 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt h: toNumber(attr.h, optSize.h ?? defaults.h), } : { x: 0, y: 0, w: optSize.w ?? defaults.w, h: optSize.h ?? defaults.h }; + const rect = normalizeRect(rawRect, defaults); const zIndex = attr?.zIndex === undefined ? undefined : toNumber(attr.zIndex, 0); + const baseId = toMaybeString(c.id); if (inferredType === 'text') { const props = convertGoViewTextOptionToNodeProps(option as GoViewTextOption); nodes.push({ - id: c.id ?? `import_text_${Math.random().toString(16).slice(2)}`, + id: baseId ?? `import_text_${Math.random().toString(16).slice(2)}`, type: 'text', rect, zIndex, @@ -595,7 +627,7 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt if (inferredType === 'image') { const props = convertGoViewImageOptionToNodeProps(option as GoViewImageOption); nodes.push({ - id: c.id ?? `import_image_${Math.random().toString(16).slice(2)}`, + id: baseId ?? `import_image_${Math.random().toString(16).slice(2)}`, type: 'image', rect, zIndex, @@ -609,7 +641,7 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt if (inferredType === 'iframe') { const props = convertGoViewIframeOptionToNodeProps(option as GoViewIframeOption); nodes.push({ - id: c.id ?? `import_iframe_${Math.random().toString(16).slice(2)}`, + id: baseId ?? `import_iframe_${Math.random().toString(16).slice(2)}`, type: 'iframe', rect, zIndex, @@ -623,7 +655,7 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt if (inferredType === 'video') { const props = convertGoViewVideoOptionToNodeProps(option as GoViewVideoOption); nodes.push({ - id: c.id ?? `import_video_${Math.random().toString(16).slice(2)}`, + id: baseId ?? `import_video_${Math.random().toString(16).slice(2)}`, type: 'video', rect, zIndex,