From 9d95f34bd969d5ac123e52ea47afad6a9ca2d33d Mon Sep 17 00:00:00 2001 From: clawdbot Date: Wed, 28 Jan 2026 14:15:27 +0800 Subject: [PATCH] refactor: improve goView import and context menu selection parity --- packages/editor/src/editor/Canvas.tsx | 30 ++++++++++++++++++++-- packages/editor/src/editor/ContextMenu.tsx | 4 +++ packages/editor/src/editor/EditorApp.tsx | 13 +++++++++- packages/sdk/src/core/goview/convert.ts | 29 ++++++++++++++++----- 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/packages/editor/src/editor/Canvas.tsx b/packages/editor/src/editor/Canvas.tsx index ed25bb9..ea3de9f 100644 --- a/packages/editor/src/editor/Canvas.tsx +++ b/packages/editor/src/editor/Canvas.tsx @@ -141,6 +141,15 @@ export function Canvas(props: CanvasProps) { const additive = (e as React.MouseEvent).ctrlKey || (e as React.MouseEvent).metaKey || (e as React.MouseEvent).shiftKey; + const nextSelectionIds = targetId + ? props.selectionIds.includes(targetId) + ? props.selectionIds + : additive + ? [...props.selectionIds, targetId] + : [targetId] + : props.selectionIds; + const selectionKey = nextSelectionIds.join('|'); + if (targetId) { if (!props.selectionIds.includes(targetId)) { // goView-ish: right click selects the item. @@ -158,8 +167,25 @@ export function Canvas(props: CanvasProps) { props.onOpenContextMenu( targetId - ? { kind: 'node', clientX: e.clientX, clientY: e.clientY, worldX: p.x, worldY: p.y, targetId } - : { kind: 'canvas', clientX: e.clientX, clientY: e.clientY, worldX: p.x, worldY: p.y }, + ? { + kind: 'node', + clientX: e.clientX, + clientY: e.clientY, + worldX: p.x, + worldY: p.y, + targetId, + selectionKey, + selectionIds: nextSelectionIds, + } + : { + kind: 'canvas', + clientX: e.clientX, + clientY: e.clientY, + worldX: p.x, + worldY: p.y, + selectionKey, + selectionIds: nextSelectionIds, + }, ); }; diff --git a/packages/editor/src/editor/ContextMenu.tsx b/packages/editor/src/editor/ContextMenu.tsx index 9638fda..beb7413 100644 --- a/packages/editor/src/editor/ContextMenu.tsx +++ b/packages/editor/src/editor/ContextMenu.tsx @@ -8,6 +8,8 @@ export type ContextMenuState = clientY: number; worldX: number; worldY: number; + selectionKey: string; + selectionIds: string[]; } | { kind: 'node'; @@ -16,6 +18,8 @@ export type ContextMenuState = worldX: number; worldY: number; targetId: string; + selectionKey: string; + selectionIds: string[]; }; export function ContextMenu(props: { diff --git a/packages/editor/src/editor/EditorApp.tsx b/packages/editor/src/editor/EditorApp.tsx index 08ee502..da1242b 100644 --- a/packages/editor/src/editor/EditorApp.tsx +++ b/packages/editor/src/editor/EditorApp.tsx @@ -80,7 +80,10 @@ export function EditorApp() { // Selection parity: if selection changes via hotkeys/toolbar, close any open context menu. useEffect(() => { if (!ctxMenu) return; - setCtxMenu(null); + const currentKey = state.selection.ids.join('|'); + if (currentKey !== ctxMenu.selectionKey) { + setCtxMenu(null); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [state.selection.ids]); @@ -468,6 +471,12 @@ export function EditorApp() { e.preventDefault(); e.stopPropagation(); const additive = e.ctrlKey || e.metaKey || e.shiftKey; + const nextSelectionIds = state.selection.ids.includes(node.id) + ? state.selection.ids + : additive + ? [...state.selection.ids, node.id] + : [node.id]; + const selectionKey = nextSelectionIds.join('|'); if (!state.selection.ids.includes(node.id)) { if (additive) { dispatch({ type: 'toggleSelect', id: node.id }); @@ -482,6 +491,8 @@ export function EditorApp() { worldX: node.rect.x + node.rect.w / 2, worldY: node.rect.y + node.rect.h / 2, targetId: node.id, + selectionKey, + selectionIds: nextSelectionIds, }); }} style={{ diff --git a/packages/sdk/src/core/goview/convert.ts b/packages/sdk/src/core/goview/convert.ts index e23f90b..168b4ef 100644 --- a/packages/sdk/src/core/goview/convert.ts +++ b/packages/sdk/src/core/goview/convert.ts @@ -175,11 +175,12 @@ function isVideo(c: GoViewComponentLike): boolean { // Chinese low-code widget names. if ( - k.includes('视频') || - k.includes('监控') || - k.includes('直播') || - k.includes('摄像') || - k.includes('摄像头') + k.includes('\u89c6\u9891') || + k.includes('\u76d1\u63a7') || + k.includes('\u76f4\u64ad') || + k.includes('\u6444\u50cf') || + k.includes('\u6444\u50cf\u5934') || + k.includes('\u56de\u653e') ) { return true; } @@ -206,7 +207,10 @@ function isVideo(c: GoViewComponentLike): boolean { k.includes('live') || k.includes('camera') || k.includes('cctv') || - k.includes('monitor') + k.includes('monitor') || + k.includes('playback') || + k.includes('streaming') || + k.includes('videostream') ); } @@ -276,7 +280,11 @@ function looksLikeIframeOption(option: unknown): boolean { if (typeof option === 'string') { const trimmed = option.trim(); - if (trimmed.startsWith('<') && trimmed.includes('>')) return true; + if (trimmed.startsWith('<') && trimmed.includes('>')) { + // If the HTML is a video tag, let the video detector handle it. + if (/\bvideo\b/i.test(trimmed) || /\bsource\b/i.test(trimmed)) return false; + return true; + } } // Prefer explicit iframe-ish keys when option is an object (including nested shapes). @@ -331,6 +339,13 @@ function looksLikeVideoOption(option: unknown): boolean { // Avoid false positives: some TextCommon widgets carry link-like fields. if (looksLikeTextOption(option)) return false; + if (typeof option === 'string') { + const trimmed = option.trim(); + if (trimmed.startsWith('<') && trimmed.includes('>')) { + return /\bvideo\b/i.test(trimmed) || /\bsource\b/i.test(trimmed); + } + } + // Prefer explicit video-ish keys when option is an object (including nested shapes). if (typeof option === 'object') { if (