diff --git a/packages/editor/src/editor/ContextMenu.tsx b/packages/editor/src/editor/ContextMenu.tsx index beb7413..7b716cc 100644 --- a/packages/editor/src/editor/ContextMenu.tsx +++ b/packages/editor/src/editor/ContextMenu.tsx @@ -1,6 +1,12 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { Typography } from 'antd'; +function selectionKeyOf(ids: string[]): string { + // Keep stable regardless of selection order. + // This avoids false mismatches between context-menu state and reducer state. + return [...ids].sort().join('|'); +} + export type ContextMenuState = | { kind: 'canvas'; @@ -101,11 +107,18 @@ export function ContextMenu(props: { if (!ctx || !position) return null; - const hasSelection = props.selectionIds.length > 0; - const canModifySelection = hasSelection && props.selectionHasUnlocked; + const currentSelectionKey = selectionKeyOf(props.selectionIds); + const selectionInSync = currentSelectionKey === ctx.selectionKey; + + // Use captured selection while waiting for store selection to catch up. + // This prevents transient "disabled" menu items right after right-click selecting. + const selectionIds = selectionInSync ? props.selectionIds : ctx.selectionIds; + + const hasSelection = selectionIds.length > 0; + const canModifySelection = selectionInSync && hasSelection && props.selectionHasUnlocked; const hasTarget = ctx.kind === 'node'; const targetId = hasTarget ? ctx.targetId : undefined; - const targetInSelection = !!targetId && props.selectionIds.includes(targetId); + const targetInSelection = !!targetId && selectionIds.includes(targetId); const canSelectSingle = !!props.onSelectSingle; return ( @@ -136,7 +149,7 @@ export function ContextMenu(props: { }} /> - {canSelectSingle && hasTarget && targetInSelection && props.selectionIds.length > 1 ? ( + {canSelectSingle && hasTarget && targetInSelection && selectionIds.length > 1 ? ( { @@ -185,7 +198,7 @@ export function ContextMenu(props: { ? 'Toggle Lock' : 'Lock' } - disabled={!hasSelection || !props.onToggleLockSelected} + disabled={!selectionInSync || !hasSelection || !props.onToggleLockSelected} onClick={() => { props.onToggleLockSelected?.(); onClose(); @@ -199,7 +212,7 @@ export function ContextMenu(props: { ? 'Toggle Visibility' : 'Hide' } - disabled={!hasSelection || !props.onToggleHideSelected} + disabled={!selectionInSync || !hasSelection || !props.onToggleHideSelected} onClick={() => { props.onToggleHideSelected?.(); onClose(); @@ -210,7 +223,7 @@ export function ContextMenu(props: { { props.onBringToFrontSelected?.(); onClose(); @@ -218,7 +231,7 @@ export function ContextMenu(props: { /> { props.onSendToBackSelected?.(); onClose(); @@ -235,7 +248,13 @@ export function ContextMenu(props: { }} /> - + {!selectionInSync ? ( + + (updating selection…) + + ) : null} + + ({Math.round(ctx.worldX)}, {Math.round(ctx.worldY)}) diff --git a/packages/sdk/src/core/goview/convert.ts b/packages/sdk/src/core/goview/convert.ts index f1335b1..98198b0 100644 --- a/packages/sdk/src/core/goview/convert.ts +++ b/packages/sdk/src/core/goview/convert.ts @@ -678,7 +678,7 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt const urlLike = pickUrlLike(option); const urlLooksLikeEmbedPage = !!urlLike && - /(youtube\.com|youtu\.be|vimeo\.com|twitch\.tv|tiktok\.com|douyin\.com|kuaishou\.com|ixigua\.com|huya\.com|douyu\.com|bilibili\.com|live\.bilibili\.com|player\.bilibili\.com|youku\.com|iqiyi\.com|mgtv\.com|tencent|qq\.com|music\.163\.com)/i.test( + /(youtube\.com|youtu\.be|vimeo\.com|twitch\.tv|tiktok\.com|douyin\.com|kuaishou\.com|ixigua\.com|huya\.com|douyu\.com|bilibili\.com|live\.bilibili\.com|player\.bilibili\.com|youku\.com|iqiyi\.com|mgtv\.com|tencent|qq\.com|music\.163\.com|spotify\.com|soundcloud\.com|figma\.com|google\.com\/maps|amap\.com|gaode\.com|map\.baidu\.com)/i.test( urlLike, ) && // Keep actual media URLs as video.