From 903b0da44aebe2e3fa75c923fe938dfb6d1aee97 Mon Sep 17 00:00:00 2001 From: clawdbot Date: Wed, 28 Jan 2026 10:08:18 +0800 Subject: [PATCH] refactor: improve goView import and editor selection --- packages/editor/src/editor/Canvas.tsx | 6 +++- packages/editor/src/editor/ContextMenu.tsx | 42 ++++++++++++++-------- packages/editor/src/editor/EditorApp.tsx | 3 ++ packages/editor/src/editor/store.ts | 10 ++++-- packages/sdk/src/core/goview/convert.ts | 4 ++- 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/packages/editor/src/editor/Canvas.tsx b/packages/editor/src/editor/Canvas.tsx index cebd95b..ed25bb9 100644 --- a/packages/editor/src/editor/Canvas.tsx +++ b/packages/editor/src/editor/Canvas.tsx @@ -156,7 +156,11 @@ export function Canvas(props: CanvasProps) { // (Left-click on background already clears unless additive via box-select.) } - props.onOpenContextMenu({ clientX: e.clientX, clientY: e.clientY, worldX: p.x, worldY: p.y, targetId }); + 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 }, + ); }; const onBackgroundPointerDown = (e: React.PointerEvent) => { diff --git a/packages/editor/src/editor/ContextMenu.tsx b/packages/editor/src/editor/ContextMenu.tsx index cb3102b..9638fda 100644 --- a/packages/editor/src/editor/ContextMenu.tsx +++ b/packages/editor/src/editor/ContextMenu.tsx @@ -1,22 +1,32 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { Typography } from 'antd'; -export type ContextMenuState = { - clientX: number; - clientY: number; - worldX: number; - worldY: number; - targetId?: string; -}; +export type ContextMenuState = + | { + kind: 'canvas'; + clientX: number; + clientY: number; + worldX: number; + worldY: number; + } + | { + kind: 'node'; + clientX: number; + clientY: number; + worldX: number; + worldY: number; + targetId: string; + }; export function ContextMenu(props: { state: ContextMenuState | null; selectionIds: string[]; selectionAllLocked: boolean; - selectionSomeLocked?: boolean; + selectionSomeLocked: boolean; + selectionHasUnlocked: boolean; selectionAllHidden: boolean; - selectionSomeHidden?: boolean; - hasAnyNodes?: boolean; + selectionSomeHidden: boolean; + hasAnyNodes: boolean; onClose: () => void; onAddTextAt: (x: number, y: number) => void; onSelectSingle?: (id?: string) => void; @@ -88,8 +98,10 @@ export function ContextMenu(props: { if (!ctx || !position) return null; const hasSelection = props.selectionIds.length > 0; - const hasTarget = !!ctx.targetId; - const targetInSelection = !!ctx.targetId && props.selectionIds.includes(ctx.targetId); + const canModifySelection = hasSelection && props.selectionHasUnlocked; + const hasTarget = ctx.kind === 'node'; + const targetId = hasTarget ? ctx.targetId : undefined; + const targetInSelection = !!targetId && props.selectionIds.includes(targetId); const canSelectSingle = !!props.onSelectSingle; return ( @@ -124,7 +136,7 @@ export function ContextMenu(props: { { - props.onSelectSingle?.(ctx.targetId); + props.onSelectSingle?.(targetId); onClose(); }} /> @@ -154,7 +166,7 @@ export function ContextMenu(props: { { props.onDuplicateSelected?.(); onClose(); @@ -212,7 +224,7 @@ export function ContextMenu(props: { { props.onDeleteSelected?.(); onClose(); diff --git a/packages/editor/src/editor/EditorApp.tsx b/packages/editor/src/editor/EditorApp.tsx index e4bdea2..08ee502 100644 --- a/packages/editor/src/editor/EditorApp.tsx +++ b/packages/editor/src/editor/EditorApp.tsx @@ -55,6 +55,7 @@ export function EditorApp() { const selectionAllLocked = selection.length > 0 && selection.every((n) => n.locked); const selectionSomeLocked = selection.length > 0 && selection.some((n) => n.locked) && !selectionAllLocked; + const selectionHasUnlocked = selection.length > 0 && selection.some((n) => !n.locked); const selectionAllHidden = selection.length > 0 && selection.every((n) => n.hidden); const selectionSomeHidden = selection.length > 0 && selection.some((n) => n.hidden) && !selectionAllHidden; @@ -475,6 +476,7 @@ export function EditorApp() { } } setCtxMenu({ + kind: 'node', clientX: e.clientX, clientY: e.clientY, worldX: node.rect.x + node.rect.w / 2, @@ -512,6 +514,7 @@ export function EditorApp() { selectionIds={state.selection.ids} selectionAllLocked={selectionAllLocked} selectionSomeLocked={selectionSomeLocked} + selectionHasUnlocked={selectionHasUnlocked} selectionAllHidden={selectionAllHidden} selectionSomeHidden={selectionSomeHidden} hasAnyNodes={state.doc.screen.nodes.length > 0} diff --git a/packages/editor/src/editor/store.ts b/packages/editor/src/editor/store.ts index 15ca68c..5e0fb23 100644 --- a/packages/editor/src/editor/store.ts +++ b/packages/editor/src/editor/store.ts @@ -287,15 +287,19 @@ export function editorReducer(state: EditorRuntimeState, action: EditorAction): case 'deleteSelected': { if (!state.selection.ids.length) return state; const ids = new Set(state.selection.ids); + const deletableIds = new Set( + state.doc.screen.nodes.filter((n) => ids.has(n.id) && !n.locked).map((n) => n.id), + ); + if (!deletableIds.size) return state; return { ...historyPush(state), doc: { screen: { ...state.doc.screen, - nodes: state.doc.screen.nodes.filter((n) => !ids.has(n.id)), + nodes: state.doc.screen.nodes.filter((n) => !deletableIds.has(n.id)), }, }, - selection: { ids: [] }, + selection: { ids: state.selection.ids.filter((id) => !deletableIds.has(id)) }, }; } @@ -328,7 +332,7 @@ export function editorReducer(state: EditorRuntimeState, action: EditorAction): const clones: WidgetNode[] = []; for (const n of state.doc.screen.nodes) { - if (!ids.has(n.id)) continue; + if (!ids.has(n.id) || n.locked) continue; const id = `${n.type}_${Date.now()}_${Math.random().toString(16).slice(2)}`; clones.push({ ...n, diff --git a/packages/sdk/src/core/goview/convert.ts b/packages/sdk/src/core/goview/convert.ts index c4d14fe..6ba7b3d 100644 --- a/packages/sdk/src/core/goview/convert.ts +++ b/packages/sdk/src/core/goview/convert.ts @@ -521,17 +521,19 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt nodes: [], }); - const componentList = + const componentListRaw = (input as GoViewStorageLike).componentList ?? (input as GoViewProjectLike).componentList ?? (data?.componentList as GoViewComponentLike[] | undefined) ?? (state?.componentList as GoViewComponentLike[] | undefined) ?? (project?.componentList as GoViewComponentLike[] | undefined) ?? []; + const componentList = Array.isArray(componentListRaw) ? componentListRaw : []; const nodes: Array = []; for (const raw of componentList) { + if (!raw || typeof raw !== 'object') continue; const c = unwrapComponent(raw); const option = normalizeOption(optionOf(c));