File size: 2,986 Bytes
f56a29b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import { useMemo, useEffect, useState } from 'react';
import { useCanvasStore } from '@/lib/store';
import type { PPTElement } from '@/lib/types/slides';
import { getElementListRange } from '@/lib/utils/element';
import type { OperateResizeHandlers, MultiSelectRange } from '@/lib/types/edit';
import { useCommonOperate } from '../hooks/useCommonOperate';
import { ResizeHandler } from './ResizeHandler';
import { BorderLine } from './BorderLine';

interface MultiSelectOperateProps {
  readonly elementList: PPTElement[];
  readonly scaleMultiElement: (
    e: React.MouseEvent,
    range: MultiSelectRange,
    command: OperateResizeHandlers,
  ) => void;
}

export function MultiSelectOperate({ elementList, scaleMultiElement }: MultiSelectOperateProps) {
  const activeElementIdList = useCanvasStore.use.activeElementIdList();
  const canvasScale = useCanvasStore.use.canvasScale();

  const localActiveElementList = useMemo(
    () => elementList.filter((el) => activeElementIdList.includes(el.id)),
    [elementList, activeElementIdList],
  );

  const [range, setRange] = useState<MultiSelectRange>({
    minX: 0,
    maxX: 0,
    minY: 0,
    maxY: 0,
  });

  // Calculate border lines and resize handlers based on the multi-select range on canvas
  const width = useMemo(() => (range.maxX - range.minX) * canvasScale, [range, canvasScale]);
  const height = useMemo(() => (range.maxY - range.minY) * canvasScale, [range, canvasScale]);
  const { resizeHandlers, borderLines } = useCommonOperate(width, height);

  // Calculate the overall range of multi-selected elements on canvas
  useEffect(() => {
    const { minX, maxX, minY, maxY } = getElementListRange(localActiveElementList);
    // eslint-disable-next-line react-hooks/set-state-in-effect -- DOM measurement requires effect
    setRange({ minX, maxX, minY, maxY });
  }, [localActiveElementList]);

  // Disable resize in multi-select: only non-rotated images and shapes can be resized
  const disableResize = useMemo(() => {
    return localActiveElementList.some((item) => {
      if ((item.type === 'image' || item.type === 'shape') && !item.rotate) return false;
      return true;
    });
  }, [localActiveElementList]);

  return (
    <div
      className="multi-select-operate absolute top-0 left-0 z-44"
      style={{
        left: range.minX * canvasScale + 'px',
        top: range.minY * canvasScale + 'px',
        pointerEvents: 'auto', // Enable mouse events for multi-select controls
      }}
    >
      {borderLines.map((line) => (
        <BorderLine key={line.type} type={line.type} style={line.style} />
      ))}

      {!disableResize &&
        resizeHandlers.map((point) => (
          <ResizeHandler
            key={point.direction}
            type={point.direction}
            style={point.style}
            onMouseDown={(e) => {
              e.stopPropagation();
              scaleMultiElement(e, range, point.direction);
            }}
          />
        ))}
    </div>
  );
}