refactor: improve context menu selection sync and goview embed detection
This commit is contained in:
parent
b1ca80d5fe
commit
947946edc6
@ -1,6 +1,12 @@
|
|||||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Typography } from 'antd';
|
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 =
|
export type ContextMenuState =
|
||||||
| {
|
| {
|
||||||
kind: 'canvas';
|
kind: 'canvas';
|
||||||
@ -101,11 +107,18 @@ export function ContextMenu(props: {
|
|||||||
|
|
||||||
if (!ctx || !position) return null;
|
if (!ctx || !position) return null;
|
||||||
|
|
||||||
const hasSelection = props.selectionIds.length > 0;
|
const currentSelectionKey = selectionKeyOf(props.selectionIds);
|
||||||
const canModifySelection = hasSelection && props.selectionHasUnlocked;
|
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 hasTarget = ctx.kind === 'node';
|
||||||
const targetId = hasTarget ? ctx.targetId : undefined;
|
const targetId = hasTarget ? ctx.targetId : undefined;
|
||||||
const targetInSelection = !!targetId && props.selectionIds.includes(targetId);
|
const targetInSelection = !!targetId && selectionIds.includes(targetId);
|
||||||
const canSelectSingle = !!props.onSelectSingle;
|
const canSelectSingle = !!props.onSelectSingle;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -136,7 +149,7 @@ export function ContextMenu(props: {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{canSelectSingle && hasTarget && targetInSelection && props.selectionIds.length > 1 ? (
|
{canSelectSingle && hasTarget && targetInSelection && selectionIds.length > 1 ? (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
label="Select Only"
|
label="Select Only"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -185,7 +198,7 @@ export function ContextMenu(props: {
|
|||||||
? 'Toggle Lock'
|
? 'Toggle Lock'
|
||||||
: 'Lock'
|
: 'Lock'
|
||||||
}
|
}
|
||||||
disabled={!hasSelection || !props.onToggleLockSelected}
|
disabled={!selectionInSync || !hasSelection || !props.onToggleLockSelected}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.onToggleLockSelected?.();
|
props.onToggleLockSelected?.();
|
||||||
onClose();
|
onClose();
|
||||||
@ -199,7 +212,7 @@ export function ContextMenu(props: {
|
|||||||
? 'Toggle Visibility'
|
? 'Toggle Visibility'
|
||||||
: 'Hide'
|
: 'Hide'
|
||||||
}
|
}
|
||||||
disabled={!hasSelection || !props.onToggleHideSelected}
|
disabled={!selectionInSync || !hasSelection || !props.onToggleHideSelected}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.onToggleHideSelected?.();
|
props.onToggleHideSelected?.();
|
||||||
onClose();
|
onClose();
|
||||||
@ -210,7 +223,7 @@ export function ContextMenu(props: {
|
|||||||
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
label="Bring To Front"
|
label="Bring To Front"
|
||||||
disabled={!hasSelection || !props.onBringToFrontSelected}
|
disabled={!selectionInSync || !hasSelection || !props.onBringToFrontSelected}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.onBringToFrontSelected?.();
|
props.onBringToFrontSelected?.();
|
||||||
onClose();
|
onClose();
|
||||||
@ -218,7 +231,7 @@ export function ContextMenu(props: {
|
|||||||
/>
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
label="Send To Back"
|
label="Send To Back"
|
||||||
disabled={!hasSelection || !props.onSendToBackSelected}
|
disabled={!selectionInSync || !hasSelection || !props.onSendToBackSelected}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.onSendToBackSelected?.();
|
props.onSendToBackSelected?.();
|
||||||
onClose();
|
onClose();
|
||||||
@ -235,7 +248,13 @@ export function ContextMenu(props: {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Typography.Text style={{ display: 'block', marginTop: 6, color: 'rgba(255,255,255,0.45)', fontSize: 11 }}>
|
{!selectionInSync ? (
|
||||||
|
<Typography.Text style={{ display: 'block', marginTop: 6, color: 'rgba(255,255,255,0.45)', fontSize: 11 }}>
|
||||||
|
(updating selection…)
|
||||||
|
</Typography.Text>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Typography.Text style={{ display: 'block', marginTop: 2, color: 'rgba(255,255,255,0.45)', fontSize: 11 }}>
|
||||||
({Math.round(ctx.worldX)}, {Math.round(ctx.worldY)})
|
({Math.round(ctx.worldX)}, {Math.round(ctx.worldY)})
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -678,7 +678,7 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt
|
|||||||
const urlLike = pickUrlLike(option);
|
const urlLike = pickUrlLike(option);
|
||||||
const urlLooksLikeEmbedPage =
|
const urlLooksLikeEmbedPage =
|
||||||
!!urlLike &&
|
!!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,
|
urlLike,
|
||||||
) &&
|
) &&
|
||||||
// Keep actual media URLs as video.
|
// Keep actual media URLs as video.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user