import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import type { ImageCanvasActionResult } from '@/src/components/image-editor/ImageCanvasActionsContext.ts'; import { ImageCanvasEditorPortal } from '../ImageCanvasEditorPortal.tsx'; import { contextAssetMediaSrc, type EditorAgentContextAsset, EditorAgentRightClickAction, type RightClickMenuTarget, } from './common.ts'; type MessageBubbleRightClickMenuProps = { x: number; y: number; target: RightClickMenuTarget; pendingAction: EditorAgentRightClickAction | null; resultAction: EditorAgentRightClickAction | null; result: ImageCanvasActionResult | null; onAction: (action: EditorAgentRightClickAction) => void; onClose: () => void; }; const VIEWPORT_MARGIN = 8; function actionLabel({ action, idleLabel, pendingAction, resultAction, result, }: { action: EditorAgentRightClickAction; idleLabel: string; pendingAction: EditorAgentRightClickAction | null; resultAction: EditorAgentRightClickAction | null; result: ImageCanvasActionResult | null; }) { if (pendingAction === action) { switch (action) { case EditorAgentRightClickAction.FocusCanvas: return '定位中'; case EditorAgentRightClickAction.DownloadAsset: return '下载中'; case EditorAgentRightClickAction.ReferenceImage: return '引用中'; case EditorAgentRightClickAction.CopyText: case EditorAgentRightClickAction.CopyImage: return '复制中'; } } const actionResult = resultAction === action ? result : null; if (!actionResult) { return idleLabel; } switch (action) { case EditorAgentRightClickAction.FocusCanvas: if (actionResult.successed) return '已定位'; switch (actionResult.reason) { case 'not-found-on-canva': return '画布上不存在'; case 'other': default: return '失败'; } case EditorAgentRightClickAction.DownloadAsset: if (actionResult.successed) return '已下载'; return '下载失败'; case EditorAgentRightClickAction.ReferenceImage: if (actionResult.successed) return '已引用'; return '引用失败'; case EditorAgentRightClickAction.CopyText: case EditorAgentRightClickAction.CopyImage: if (actionResult.successed) return '已复制'; return '复制失败'; } } function assetDownloadLabel(asset: EditorAgentContextAsset) { return `下载${ asset.mediaType === 'image' ? '图片' : asset.mediaType === 'video' ? '视频' : '音频' }`; } export function MessageBubbleRightClickMenu({ x, y, target, pendingAction, resultAction, result, onAction, onClose, }: MessageBubbleRightClickMenuProps) { const menuRef = useRef(null); const [position, setPosition] = useState<{ x: number; y: number } | null>( null, ); useLayoutEffect(() => { const menu = menuRef.current; if (!menu || typeof window === 'undefined') { return; } const rect = menu.getBoundingClientRect(); setPosition({ x: Math.min( Math.max(x, VIEWPORT_MARGIN), Math.max( VIEWPORT_MARGIN, window.innerWidth - rect.width - VIEWPORT_MARGIN, ), ), y: Math.min( Math.max(y, VIEWPORT_MARGIN), Math.max( VIEWPORT_MARGIN, window.innerHeight - rect.height - VIEWPORT_MARGIN, ), ), }); }, [target.kind, x, y]); useEffect(() => { const handlePointerDown = (event: PointerEvent) => { if (!menuRef.current?.contains(event.target as Node)) { onClose(); } }; const handleContextMenu = (event: MouseEvent) => { if (menuRef.current?.contains(event.target as Node)) { event.preventDefault(); } onClose(); }; const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { onClose(); } }; window.addEventListener('pointerdown', handlePointerDown); window.addEventListener('contextmenu', handleContextMenu, true); window.addEventListener('keydown', handleKeyDown); window.addEventListener('scroll', onClose, true); window.addEventListener('resize', onClose); return () => { window.removeEventListener('pointerdown', handlePointerDown); window.removeEventListener('contextmenu', handleContextMenu, true); window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('scroll', onClose, true); window.removeEventListener('resize', onClose); }; }, [onClose]); return (
event.preventDefault()} > {target.kind === 'text' ? ( ) : ( <> {target.asset.kind === 'generated_media' && target.asset.resourceId?.trim() ? ( ) : null} {target.asset.mediaType === 'image' ? ( <> {contextAssetMediaSrc(target.asset).trim() ? ( ) : null} ) : null} )}
); }