forked from 77media/video-flow
109 lines
2.7 KiB
TypeScript
109 lines
2.7 KiB
TypeScript
/**
|
|
* 编辑点交互组件
|
|
* 实现脉冲动画效果和点击交互
|
|
*/
|
|
|
|
import React, { useCallback, useMemo } from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { Edit3, Check, X, Loader2 } from 'lucide-react';
|
|
import { EditPoint as EditPointType, EditPointStatus } from './types';
|
|
|
|
interface EditPointProps {
|
|
/** 编辑点数据 */
|
|
editPoint: EditPointType;
|
|
/** 是否被选中 */
|
|
isSelected: boolean;
|
|
/** 容器尺寸 */
|
|
containerSize: { width: number; height: number };
|
|
/** 点击事件处理 */
|
|
onClick: (editPoint: EditPointType) => void;
|
|
/** 删除事件处理 */
|
|
onDelete: (id: string) => void;
|
|
/** 编辑事件处理 */
|
|
onEdit: (id: string) => void;
|
|
/** 样式配置 */
|
|
style?: {
|
|
size?: number;
|
|
color?: string;
|
|
pulseColor?: string;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 编辑点组件
|
|
*/
|
|
export const EditPoint: React.FC<EditPointProps> = ({
|
|
editPoint,
|
|
isSelected,
|
|
containerSize,
|
|
onClick,
|
|
onDelete,
|
|
onEdit,
|
|
style = {}
|
|
}) => {
|
|
const {
|
|
size = 12,
|
|
color = '#3b82f6',
|
|
pulseColor = 'rgba(59, 130, 246, 0.3)'
|
|
} = style;
|
|
|
|
// 计算绝对位置
|
|
const absolutePosition = useMemo(() => ({
|
|
x: (editPoint.position.x / 100) * containerSize.width,
|
|
y: (editPoint.position.y / 100) * containerSize.height
|
|
}), [editPoint.position, containerSize]);
|
|
|
|
// 处理点击事件
|
|
const handleClick = useCallback((e: React.MouseEvent) => {
|
|
e.stopPropagation();
|
|
onClick(editPoint);
|
|
}, [onClick, editPoint]);
|
|
|
|
// 处理删除事件
|
|
const handleDelete = useCallback((e: React.MouseEvent) => {
|
|
e.stopPropagation();
|
|
onDelete(editPoint.id);
|
|
}, [onDelete, editPoint.id]);
|
|
|
|
// 处理编辑事件
|
|
const handleEdit = useCallback((e: React.MouseEvent) => {
|
|
e.stopPropagation();
|
|
onEdit(editPoint.id);
|
|
}, [onEdit, editPoint.id]);
|
|
|
|
// Simplified for the image design - just use blue color
|
|
|
|
return (
|
|
<motion.div
|
|
className="absolute z-20 cursor-pointer"
|
|
data-edit-point="true"
|
|
style={{
|
|
left: absolutePosition.x - size / 2,
|
|
top: absolutePosition.y - size / 2,
|
|
}}
|
|
initial={{ scale: 0, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 0 }} // Make invisible to match reference image
|
|
exit={{ scale: 0, opacity: 0 }}
|
|
transition={{
|
|
type: "spring",
|
|
stiffness: 300,
|
|
damping: 25,
|
|
duration: 0.3
|
|
}}
|
|
onClick={handleClick}
|
|
>
|
|
{/* Invisible edit point - just for click handling */}
|
|
<motion.div
|
|
className="relative rounded-full"
|
|
style={{
|
|
width: size * 2, // Larger click area
|
|
height: size * 2,
|
|
backgroundColor: 'transparent', // Invisible
|
|
}}
|
|
whileHover={{ scale: 1.1 }}
|
|
whileTap={{ scale: 0.9 }}
|
|
/>
|
|
</motion.div>
|
|
);
|
|
};
|