| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- 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<PPTElement, PPTLineElement>,
- 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<SlideContent, Slide>((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<string, any> = {
- [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 (
- <div
- className={`operate absolute z-43 select-none ${isMultiSelect && !isActive ? 'opacity-20' : ''}`}
- style={{
- top: elementInfo.top * canvasScale + 'px',
- left: elementInfo.left * canvasScale + 'px',
- transform: `rotate(${rotate}deg)`,
- transformOrigin: `${(elementInfo.width * canvasScale) / 2}px ${(height * canvasScale) / 2}px`,
- pointerEvents: 'auto', // Enable mouse events for operate controls
- }}
- >
- {/* eslint-disable @typescript-eslint/no-explicit-any -- dynamic component dispatch requires type widening */}
- {isSelected && CurrentOperateComponent && (
- <CurrentOperateComponent
- elementInfo={elementInfo as any}
- handlerVisible={handlerVisible}
- rotateElement={rotateElement as any}
- scaleElement={scaleElement as any}
- dragLineElement={dragLineElement as any}
- moveShapeKeypoint={moveShapeKeypoint as any}
- />
- )}
- {/* eslint-enable @typescript-eslint/no-explicit-any */}
- {/* Animation index display */}
- {toolbarState === 'elAnimation' && elementIndexListInAnimation.length > 0 && (
- <div className="animation-index absolute top-0 -left-6 text-xs">
- {elementIndexListInAnimation.map((index) => (
- <div
- key={index}
- className="index-item w-[18px] h-[18px] bg-white text-primary border border-primary flex justify-center items-center mt-[5px] first:mt-0"
- >
- {index + 1}
- </div>
- ))}
- </div>
- )}
- </div>
- );
- }
|