import { useMemo } from 'react'; import { useCanvasStore, useSceneSelector } from '@/lib/store'; import { ElementTypes, type PPTElement, type PPTLineElement, type PPTVideoElement, type PPTAudioElement, type PPTShapeElement, type PPTChartElement, type Slide, type PPTAnimation, } from '@/lib/types/slides'; import type { OperateLineHandlers, OperateResizeHandlers } from '@/lib/types/edit'; import { ImageElementOperate } from './ImageElementOperate'; import { TextElementOperate } from './TextElementOperate'; import { ShapeElementOperate } from './ShapeElementOperate'; import { LineElementOperate } from './LineElementOperate'; import { TableElementOperate } from './TableElementOperate'; import { CommonElementOperate } from './CommonElementOperate'; import type { SlideContent } from '@/lib/types/stage'; interface OperateProps { readonly elementInfo: PPTElement; readonly isSelected: boolean; readonly isActive: boolean; readonly isActiveGroupElement: boolean; readonly isMultiSelect: boolean; readonly rotateElement: ( e: React.MouseEvent, element: Exclude< PPTElement, PPTChartElement | PPTLineElement | PPTVideoElement | PPTAudioElement >, ) => void; readonly scaleElement: ( e: React.MouseEvent, element: Exclude, command: OperateResizeHandlers, ) => void; readonly dragLineElement: ( e: React.MouseEvent, element: PPTLineElement, command: OperateLineHandlers, ) => void; readonly moveShapeKeypoint: ( e: React.MouseEvent, element: PPTShapeElement, index: number, ) => void; readonly openLinkDialog: () => void; } export function Operate({ elementInfo, isSelected, isActive, isActiveGroupElement, isMultiSelect, rotateElement, scaleElement, dragLineElement, moveShapeKeypoint, openLinkDialog: _openLinkDialog, }: OperateProps) { const canvasScale = useCanvasStore.use.canvasScale(); const toolbarState = useCanvasStore.use.toolbarState(); // Get the formatted animations using a proper selector to avoid infinite loops const currentSlide = useSceneSelector((content) => content.canvas); const formatedAnimations = useMemo(() => { if (!currentSlide?.animations) return []; const els = currentSlide.elements; const elIds = els.map((el) => el.id); const animations = currentSlide.animations.filter((animation) => elIds.includes(animation.elId), ); const formatedAnimations: { animations: PPTAnimation[]; autoNext: boolean; }[] = []; for (const animation of animations) { if (animation.trigger === 'click' || !formatedAnimations.length) { formatedAnimations.push({ animations: [animation], autoNext: false }); } else if (animation.trigger === 'meantime') { const last = formatedAnimations[formatedAnimations.length - 1]; last.animations = last.animations.filter((item) => item.elId !== animation.elId); last.animations.push(animation); formatedAnimations[formatedAnimations.length - 1] = last; } else if (animation.trigger === 'auto') { const last = formatedAnimations[formatedAnimations.length - 1]; last.autoNext = true; formatedAnimations[formatedAnimations.length - 1] = last; formatedAnimations.push({ animations: [animation], autoNext: false }); } } return formatedAnimations; }, [currentSlide]); const CurrentOperateComponent = useMemo(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- element operate components have varying prop signatures const elementTypeMap: Record = { [ElementTypes.IMAGE]: ImageElementOperate, [ElementTypes.TEXT]: TextElementOperate, [ElementTypes.SHAPE]: ShapeElementOperate, [ElementTypes.LINE]: LineElementOperate, [ElementTypes.TABLE]: TableElementOperate, [ElementTypes.CHART]: CommonElementOperate, [ElementTypes.LATEX]: CommonElementOperate, [ElementTypes.VIDEO]: CommonElementOperate, [ElementTypes.AUDIO]: CommonElementOperate, }; return elementTypeMap[elementInfo.type] || null; }, [elementInfo.type]); const elementIndexListInAnimation = useMemo(() => { if (!formatedAnimations) return []; const indexList = []; for (let i = 0; i < formatedAnimations.length; i++) { const elIds = formatedAnimations[i].animations.map((item) => item.elId); if (elIds.includes(elementInfo.id)) indexList.push(i); } return indexList; }, [formatedAnimations, elementInfo.id]); const rotate = useMemo(() => ('rotate' in elementInfo ? elementInfo.rotate : 0), [elementInfo]); const height = useMemo(() => ('height' in elementInfo ? elementInfo.height : 0), [elementInfo]); const handlerVisible = !elementInfo.lock && (isActiveGroupElement || !isMultiSelect); return (
{/* eslint-disable @typescript-eslint/no-explicit-any -- dynamic component dispatch requires type widening */} {isSelected && CurrentOperateComponent && ( )} {/* eslint-enable @typescript-eslint/no-explicit-any */} {/* Animation index display */} {toolbarState === 'elAnimation' && elementIndexListInAnimation.length > 0 && (
{elementIndexListInAnimation.map((index) => (
{index + 1}
))}
)}
); }