MultiSelectOperate.tsx 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. import { useMemo, useEffect, useState } from 'react';
  2. import { useCanvasStore } from '@/lib/store';
  3. import type { PPTElement } from '@/lib/types/slides';
  4. import { getElementListRange } from '@/lib/utils/element';
  5. import type { OperateResizeHandlers, MultiSelectRange } from '@/lib/types/edit';
  6. import { useCommonOperate } from '../hooks/useCommonOperate';
  7. import { ResizeHandler } from './ResizeHandler';
  8. import { BorderLine } from './BorderLine';
  9. interface MultiSelectOperateProps {
  10. readonly elementList: PPTElement[];
  11. readonly scaleMultiElement: (
  12. e: React.MouseEvent,
  13. range: MultiSelectRange,
  14. command: OperateResizeHandlers,
  15. ) => void;
  16. }
  17. export function MultiSelectOperate({ elementList, scaleMultiElement }: MultiSelectOperateProps) {
  18. const activeElementIdList = useCanvasStore.use.activeElementIdList();
  19. const canvasScale = useCanvasStore.use.canvasScale();
  20. const localActiveElementList = useMemo(
  21. () => elementList.filter((el) => activeElementIdList.includes(el.id)),
  22. [elementList, activeElementIdList],
  23. );
  24. const [range, setRange] = useState<MultiSelectRange>({
  25. minX: 0,
  26. maxX: 0,
  27. minY: 0,
  28. maxY: 0,
  29. });
  30. // Calculate border lines and resize handlers based on the multi-select range on canvas
  31. const width = useMemo(() => (range.maxX - range.minX) * canvasScale, [range, canvasScale]);
  32. const height = useMemo(() => (range.maxY - range.minY) * canvasScale, [range, canvasScale]);
  33. const { resizeHandlers, borderLines } = useCommonOperate(width, height);
  34. // Calculate the overall range of multi-selected elements on canvas
  35. useEffect(() => {
  36. const { minX, maxX, minY, maxY } = getElementListRange(localActiveElementList);
  37. // eslint-disable-next-line react-hooks/set-state-in-effect -- DOM measurement requires effect
  38. setRange({ minX, maxX, minY, maxY });
  39. }, [localActiveElementList]);
  40. // Disable resize in multi-select: only non-rotated images and shapes can be resized
  41. const disableResize = useMemo(() => {
  42. return localActiveElementList.some((item) => {
  43. if ((item.type === 'image' || item.type === 'shape') && !item.rotate) return false;
  44. return true;
  45. });
  46. }, [localActiveElementList]);
  47. return (
  48. <div
  49. className="multi-select-operate absolute top-0 left-0 z-44"
  50. style={{
  51. left: range.minX * canvasScale + 'px',
  52. top: range.minY * canvasScale + 'px',
  53. pointerEvents: 'auto', // Enable mouse events for multi-select controls
  54. }}
  55. >
  56. {borderLines.map((line) => (
  57. <BorderLine key={line.type} type={line.type} style={line.style} />
  58. ))}
  59. {!disableResize &&
  60. resizeHandlers.map((point) => (
  61. <ResizeHandler
  62. key={point.direction}
  63. type={point.direction}
  64. style={point.style}
  65. onMouseDown={(e) => {
  66. e.stopPropagation();
  67. scaleMultiElement(e, range, point.direction);
  68. }}
  69. />
  70. ))}
  71. </div>
  72. );
  73. }