@/utils#$ TypeScript Examples

The following examples show how to use @/utils#$. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: App.tsx    From ant-simple-draw with MIT License 5 votes vote down vote up
App: FC = () => {
  const { allModuleConfigList } = useGetCompentConfigList();

  const { decompose } = useEdit();

  const dispatch = useDispatch();

  const [isClickComponent, curComponent] = useSelector(
    createSelector([(state: storeType) => state.component], (component) => {
      return [component.isClickComponent, component.curComponent] as const;
    }),
  );

  const handleDrop: React.DragEventHandler<HTMLDivElement> = async (e) => {
    e.preventDefault();
    e.stopPropagation();
    const id = e.dataTransfer.getData('id');
    const rectInfo = $('#editor').getBoundingClientRect();
    if (id) {
      const component = deepCopy(
        getSingleArrayVal<templateDateInterface>(allModuleConfigList, 'id', id),
      ) as templateDataType;
      component.style!.top = e.clientY - rectInfo.y;
      component.style!.left = e.clientX - rectInfo.x;
      component.propValue.x = component.style!.left;
      component.propValue.y = component.style!.top;
      component.componentId = getRandomStr();
      dispatch(addComponent(component));
      dispatch(recordSnapshot());
    }
  };

  const handleDragOver: React.DragEventHandler<HTMLDivElement> = (e) => {
    e.preventDefault();
    // console.log(`onDragOver------`, e);
  };

  const handleMouseUp: React.MouseEventHandler<HTMLDivElement> = (e) => {
    // 这里点击空白区域的时候,不选中组件,且按键不显示
    if (!isClickComponent) {
      if (curComponent && curComponent.component === 'Group') {
        decompose(curComponent, [curComponent.componentId!]);
      }
      dispatch(curComponentAction(null));
    }
    if (e.button !== 2) {
      dispatch(hideContextMenuAction());
    }
  };
  return (
    <>
      <Toolbar />
      <div className={styles.main}>
        <div className={styles.left}>
          <SliderNav />
        </div>
        <div className={styles.center}>
          <div
            className={styles.content}
            onDrop={handleDrop}
            onDragOver={handleDragOver}
            onMouseDown={(e) => {
              dispatch(isClickComponentAction(false));
            }}
            onMouseUp={handleMouseUp}
          >
            <Edit />
          </div>
        </div>
        <div className={styles.right}>
          <Attr />
        </div>
      </div>
    </>
  );
}
Example #2
Source File: MarkLineComponent.tsx    From ant-simple-draw with MIT License 4 votes vote down vote up
MarkLine = memo(function MarkLine(props) {
  const diff = 3; // 相距 dff 像素将自动吸附
  const eleRefList = useRef<HTMLDivElement[]>([]);

  const [isDownward, isRightward, timestamp, componentDataList, curComponent] = useSelector(
    createSelector(
      [(state: storeType) => state.markLine, (state: storeType) => state.component],
      ({ isDownward, isRightward, timestamp }, { componentDataList, curComponent }) =>
        [isDownward, isRightward, timestamp, componentDataList, curComponent] as const,
    ),
  );

  const [lineStatus, setLineStatus] = useSetState<Record<markLineType, boolean>>({
    xt: false,
    xc: false,
    xb: false,
    yl: false,
    yc: false,
    yr: false,
  });

  /**
   * @description 吸附
   */
  const isNearly = (dragValue: number, targetValue: number) => {
    return Math.abs(dragValue - targetValue) <= diff;
  };

  const translatecurComponentShift = (
    key: linePosition,
    condition: conditionsType,
    curComponentStyle: MergeCSSProperties,
  ) => {
    const { width, height } = curComponent!.style;
    if (key === 'top') {
      return Math.round(condition.dragShift - (Number(height) - curComponentStyle.height) / 2);
    }

    return Math.round(condition.dragShift - (Number(width) - curComponentStyle.width) / 2);
  };

  /**
   * @description 显示不同的对齐线,并不是全部显示
   */
  const chooseTheTureLine = (
    needToShow: markLineType[],
    isDownward: boolean,
    isRightward: boolean,
  ) => {
    if (isRightward) {
      if (needToShow.includes('yr')) {
        setLineStatus({ yr: true });
      } else if (needToShow.includes('yc')) {
        setLineStatus({ yc: true });
      } else if (needToShow.includes('yl')) {
        setLineStatus({ yl: true });
      }
    } else {
      if (needToShow.includes('yl')) {
        setLineStatus({ yl: true });
      } else if (needToShow.includes('yc')) {
        setLineStatus({ yc: true });
      } else if (needToShow.includes('yr')) {
        setLineStatus({ yr: true });
      }
    }

    if (isDownward) {
      if (needToShow.includes('xb')) {
        setLineStatus({ xb: true });
      } else if (needToShow.includes('xc')) {
        setLineStatus({ xc: true });
      } else if (needToShow.includes('xt')) {
        setLineStatus({ xt: true });
      }
    } else {
      if (needToShow.includes('xt')) {
        setLineStatus({ xt: true });
      } else if (needToShow.includes('xc')) {
        setLineStatus({ xc: true });
      } else if (needToShow.includes('xb')) {
        setLineStatus({ xb: true });
      }
    }
  };

  const showLine = (
    isDownward: boolean,
    isRightward: boolean,
    curComponent: templateDataType,
    componentDataList: templateDataType[],
  ) => {
    const curComponentStyle = getComponentRotatedStyle(curComponent!.style);
    const curComponentHalfwidth = curComponentStyle.width / 2;
    const curComponentHalfHeight = curComponentStyle.height / 2;
    hideLine();
    componentDataList.forEach((component) => {
      if (component === curComponent) return;
      const componentStyle = getComponentRotatedStyle(component.style);
      const { top, left, bottom, right } = componentStyle;
      const componentHalfwidth = componentStyle.width / 2;
      const componentHalfHeight = componentStyle.height / 2;
      const conditions: Record<linePosition, conditionsType[]> = {
        top: [
          {
            isNearly: isNearly(curComponentStyle.top, top),
            lineNode: $('#xt'), // xt
            line: 'xt',
            dragShift: top,
            lineShift: top,
          },
          {
            isNearly: isNearly(curComponentStyle.bottom, top),
            lineNode: $('#xt'), // xt
            line: 'xt',
            dragShift: top - curComponentStyle.height,
            lineShift: top,
          },
          {
            // 组件与拖拽节点的中间是否对齐
            isNearly: isNearly(
              curComponentStyle.top + curComponentHalfHeight,
              top + componentHalfHeight,
            ),
            lineNode: $('#xc'), // xc
            line: 'xc',
            dragShift: top + componentHalfHeight - curComponentHalfHeight,
            lineShift: top + componentHalfHeight,
          },
          {
            isNearly: isNearly(curComponentStyle.top, bottom),
            lineNode: $('#xb'), // xb
            line: 'xb',
            dragShift: bottom,
            lineShift: bottom,
          },
          {
            isNearly: isNearly(curComponentStyle.bottom, bottom),
            lineNode: $('#xb'), // xb
            line: 'xb',
            dragShift: bottom - curComponentStyle.height,
            lineShift: bottom,
          },
        ],
        left: [
          {
            isNearly: isNearly(curComponentStyle.left, left),
            lineNode: $('#yl'), // yl
            line: 'yl',
            dragShift: left,
            lineShift: left,
          },
          {
            isNearly: isNearly(curComponentStyle.right, left),
            lineNode: $('#yl'), // yl
            line: 'yl',
            dragShift: left - curComponentStyle.width,
            lineShift: left,
          },
          {
            // 组件与拖拽节点的中间是否对齐
            isNearly: isNearly(
              curComponentStyle.left + curComponentHalfwidth,
              left + componentHalfwidth,
            ),
            lineNode: $('#yc'), // yc
            line: 'yc',
            dragShift: left + componentHalfwidth - curComponentHalfwidth,
            lineShift: left + componentHalfwidth,
          },
          {
            isNearly: isNearly(curComponentStyle.left, right),
            lineNode: $('#yr'), // yr
            line: 'yr',
            dragShift: right,
            lineShift: right,
          },
          {
            isNearly: isNearly(curComponentStyle.right, right),
            lineNode: $('#yr'), // yr
            line: 'yr',
            dragShift: right - curComponentStyle.width,
            lineShift: right,
          },
        ],
      };

      const needToShow: markLineType[] = [];
      const { rotate } = curComponent!.style;
      const conditionsKey = Object.keys(conditions) as linePosition[];
      conditionsKey.forEach((key: linePosition) => {
        // 遍历符合的条件并处理
        conditions[key].forEach((condition) => {
          if (!condition.isNearly) return;
          // 图形旋转之后,对齐线有些闪动
          // dispatch(
          //   setShapeSingleStyleAction({
          //     key,
          //     value:
          //       Number(rotate) !== 0
          //         ? translatecurComponentShift(key, condition, curComponentStyle)
          //         : condition.dragShift,
          //   }),
          // );

          condition.lineNode.style[key] = `${condition.lineShift}px`;
          needToShow.push(condition.line);
        });
      });

      // 同一方向上同时显示三条线可能不太美观,因此才有了这个解决方案
      // 同一方向上的线只显示一条,例如多条横条只显示一条横线
      if (needToShow.length) {
        chooseTheTureLine(needToShow, isDownward, isRightward);
      }
    });
  };
  const hideLine = () => {
    Object.keys(lineStatus).forEach((line) => {
      setLineStatus({ [line]: false });
    });
  };
  useEffect(() => {
    if (timestamp > 0) {
      // 正在拖动图形
      showLine(isDownward, isRightward, curComponent!, componentDataList);
    } else if (timestamp === -1) {
      // 停止拖动图形
      hideLine();
    }
  }, [timestamp, isDownward, isRightward, curComponent, componentDataList]);

  return (
    <div className={styles.markLine}>
      {lines.map((item, index) => (
        <div
          className={`${styles.line} ${item.includes('x') ? styles.xline : styles.yline}`}
          ref={(ref: HTMLDivElement) => eleRefList.current!.push(ref)}
          style={{ display: lineStatus[item] === false ? 'none' : 'block' }}
          key={index}
          data-line={item}
          id={item}
        ></div>
      ))}
    </div>
  );
})
Example #3
Source File: ShapeComponent.tsx    From ant-simple-draw with MIT License 4 votes vote down vote up
Shape: FC<ShapeType> = memo(function Shape({ children, style, element, defaultStyle }) {
  const currentElement = useRef<HTMLDivElement | null>(null);
  const [cursors, setCursors] = useState<Record<pointType, string>>();
  const [curComponent, active] = useSelector(
    createSelector([(state: storeType) => state.component], (component) => {
      const active = element?.componentId === component.curComponent?.componentId ? true : false;
      return [component.curComponent, active] as const;
    }),
  );
  const dispatch = useDispatch();
  /**
  @description 拖拽图形
   */
  const handleMouseDownOnShape: React.MouseEventHandler<HTMLDivElement> = async (e) => {
    e.stopPropagation();
    dispatch(isClickComponentAction(true));
    dispatch(curComponentAction(element));
    setCursors(getCursor(element));
    const pos = { ...defaultStyle };
    const startY = e.clientY;
    const startX = e.clientX;
    // 如果直接修改属性,值的类型会变为字符串,所以要转为数值型
    const startTop = Number(pos.top);
    const startLeft = Number(pos.left);

    // 如果元素没有移动,则不保存快照
    let hasMove = false;
    const move = async (moveEvent: MouseEvent) => {
      hasMove = true;
      const curX = moveEvent.clientX;
      const curY = moveEvent.clientY;
      pos.top = curY - startY + startTop;
      pos.left = curX - startX + startLeft;
      await dispatch(setShapeStyleAction(pos));
      // 触发元素移动事件,用于显示标线、吸附功能
      // curY - startY > 0 true 表示向下移动 false 表示向上移动
      // curX - startX > 0 true 表示向右移动 false 表示向左移动
      await dispatch(
        showMarkLineAction({
          isDownward: curY - startY > 0,
          isRightward: curX - startX > 0,
        }),
      );
    };

    const up = () => {
      hasMove && dispatch(recordSnapshot());
      // 触发元素停止移动事件,用于隐藏标线
      dispatch(hideMarkLineAction());
      document.removeEventListener('mousemove', move);
      document.removeEventListener('mouseup', up);
    };

    document.addEventListener('mousemove', move);
    document.addEventListener('mouseup', up);
  };

  /**
   * @description 动态的布局连接桩
   */
  const getPointStyle = (point: string) => {
    const { width, height } = defaultStyle as { width: number; height: number };
    const hasT = /t/.test(point);
    const hasB = /b/.test(point);
    const hasL = /l/.test(point);
    const hasR = /r/.test(point);
    let newLeft = 0;
    let newTop = 0;
    // 四个角的点
    if (point.length === 2) {
      newLeft = hasL ? 0 : width;
      newTop = hasT ? 0 : height;
    } else {
      // 上下两点的点,宽度居中
      if (hasT || hasB) {
        newLeft = width / 2;
        newTop = hasT ? 0 : height;
      }
      // 左右两边的点,高度居中
      if (hasL || hasR) {
        newLeft = hasL ? 0 : width;
        newTop = Math.floor(height / 2);
      }
    }
    const eleStyle = {
      marginLeft: hasR ? '-4px' : '-4px',
      marginTop: '-4px',
      left: `${newLeft}px`,
      top: `${newTop}px`,
      cursor: cursors && (cursors as any)[point],
    };
    return eleStyle;
  };
  /**
   * @description 更具不同的八个点,显示不同的cursor属性值
   */
  const getCursor = (curComponent: templateDataType) => {
    const rotate = mod360(Number(curComponent?.style?.rotate)); // 取余 360
    const result = Object.create({});
    let lastMatchIndex = -1; // 从上一个命中的角度的索引开始匹配下一个,降低时间复杂度
    pointList.forEach((point) => {
      const angle = mod360(initialAngle[point] + rotate);
      const len = angleToCursor.length;
      while (true) {
        lastMatchIndex = (lastMatchIndex + 1) % len;
        const angleLimit = angleToCursor[lastMatchIndex];
        if (angle < 23 || angle >= 338) {
          result[point] = 'nw-resize';
          return;
        }
        if (angleLimit.start <= angle && angle < angleLimit.end) {
          result[point] = angleLimit.cursor + '-resize';
          return;
        }
      }
    });
    return result;
  };
  /**
   * @description 八个点,每个点按下的事件
   */
  const handleMouseDownOnPoint = (point: pointType, e: MergeEvent) => {
    e.stopPropagation();
    e.preventDefault();
    dispatch(isClickComponentAction(true));
    const style = { ...defaultStyle } as Required<MergeCSSProperties>;

    // 组件宽高比
    const proportion = Number(style.width) / Number(style.height);

    // 组件中心点
    const center = {
      x: Number(style.left) + Number(style.width) / 2,
      y: Number(style.top) + Number(style.height) / 2,
    };

    // 获取画布位移信息
    const editorRectInfo = $('#editor').getBoundingClientRect();

    // 获取 point 与实际拖动基准点的差值
    const pointRect = e.target.getBoundingClientRect();
    // 当前点击圆点相对于画布的中心坐标
    const curPoint = {
      x: Math.round(pointRect.left - editorRectInfo.left + e.target.offsetWidth / 2),
      y: Math.round(pointRect.top - editorRectInfo.top + e.target.offsetHeight / 2),
    };

    // 获取对称点的坐标
    const symmetricPoint = {
      x: center.x - (curPoint.x - center.x),
      y: center.y - (curPoint.y - center.y),
    };

    // 是否需要保存快照
    let needSave = false;
    let isFirst = true;

    const move = (moveEvent: MouseEvent) => {
      // 第一次点击时也会触发 move,所以会有“刚点击组件但未移动,组件的大小却改变了”的情况发生
      // 因此第一次点击时不触发 move 事件
      if (isFirst) {
        isFirst = false;
        return;
      }

      needSave = true;
      const curPositon = {
        x: moveEvent.clientX - editorRectInfo.left,
        y: moveEvent.clientY - editorRectInfo.top,
      };

      calculateComponentPositonAndSize(point, style, curPositon, proportion, false, {
        center,
        curPoint,
        symmetricPoint,
      });
      dispatch(setShapeStyleAction(style));
    };

    const up = () => {
      document.removeEventListener('mousemove', move);
      document.removeEventListener('mouseup', up);
      needSave && dispatch(recordSnapshot());
    };

    document.addEventListener('mousemove', move);
    document.addEventListener('mouseup', up);
  };
  /**
   * @description 图形旋转
   */
  const handleRotate: React.MouseEventHandler<HTMLDivElement> = (e) => {
    e.preventDefault();
    e.stopPropagation();
    dispatch(isClickComponentAction(true));
    const pos = { ...defaultStyle };
    const startY = e.clientY;
    const startX = e.clientX;
    const startRotate = Number(pos.rotate);
    // 获取元素中心点位置
    const rect = currentElement.current!.getBoundingClientRect();
    const centerX = rect.left + rect.width / 2;
    const centerY = rect.top + rect.height / 2;

    // 旋转前的角度
    const rotateDegreeBefore = Math.atan2(startY - centerY, startX - centerX) / (Math.PI / 180);

    // 如果元素没有移动,则不保存快照
    let hasMove = false;
    const move = (moveEvent: MouseEvent) => {
      hasMove = true;
      const curX = moveEvent.clientX;
      const curY = moveEvent.clientY;
      // 旋转后的角度
      const rotateDegreeAfter = Math.atan2(curY - centerY, curX - centerX) / (Math.PI / 180);
      // 获取旋转的角度值
      pos.rotate = startRotate + rotateDegreeAfter - rotateDegreeBefore;
      // 修改当前组件样式
      dispatch(setShapeStyleAction(pos));
    };

    const up = () => {
      hasMove && dispatch(recordSnapshot());
      document.removeEventListener('mousemove', move);
      document.removeEventListener('mouseup', up);
      // 根据旋转角度重新获取光标位置,cursor
      setCursors(getCursor(element));
    };

    document.addEventListener('mousemove', move);
    document.addEventListener('mouseup', up);
  };
  const handleClick: React.MouseEventHandler<HTMLDivElement> = (e) => {
    // 阻止向父组件冒泡
    e.stopPropagation();
    e.preventDefault();
    dispatch(hideContextMenuAction());
  };
  return (
    <div
      className={`${styles.shape} ${active ? styles.shapeActive : null}`}
      style={style}
      onMouseDown={handleMouseDownOnShape}
      onClick={handleClick}
      ref={currentElement}
    >
      {active && (
        <>
          <ReloadOutlined className={styles.shapeRotate} onMouseDown={handleRotate} />
          {pointList.map((item, index) => (
            <div
              key={index}
              className={styles.shapePoint}
              style={getPointStyle(item)}
              onMouseDown={(e: any) => handleMouseDownOnPoint(item, e)}
            ></div>
          ))}
        </>
      )}
      {children}
    </div>
  );
})
Example #4
Source File: index.tsx    From ant-simple-draw with MIT License 4 votes vote down vote up
Edit = memo(function Edit(props) {
  const { editHandle, decompose } = useEdit();

  const forUpdate = useMandatoryUpdate();

  const editorPosition = useRef<xyTYpe>({ x: 0, y: 0 });

  const areaPosition = useRef<xyTYpe>({ x: 0, y: 0 });

  const areawh = useRef<whType>({ width: 0, height: 0 });

  const [isShowArea, setIsShowArea] = useState<boolean>(false);

  const [ratioValue, setRatioValue] = useState(1);

  const dispatch = useDispatch();

  const [componentListData, curComponent, canvasInformation] = useSelector(
    createSelector(
      [(state: storeType) => state.component, (state: storeType) => state.config],
      (component, config) =>
        [component.componentDataList, component.curComponent, config.canvasInformation] as const,
    ),
  );

  const { resultStyle } = useStyle(canvasInformation);
  /**
   * @description 按键操作
   */
  useHotkeys(
    allKeyValueCode,
    (event, handler) => {
      editHandle(handler.key as keyCodeType);
    },
    [componentListData, curComponent],
  );
  /**
   * @description 将值转化为对应的style样式,拼接单位
   */
  const getShapeStyle = (style: React.CSSProperties) => {
    const result = Object.create({});
    ['width', 'height', 'top', 'left', 'rotate'].forEach((attr) => {
      if (attr !== 'rotate') {
        result[attr] = (style as any)[attr] + 'px';
      } else {
        result.transform = 'rotate(' + style[attr] + 'deg)';
      }
    });
    return result;
  };
  /**
   * @description 右键菜单
   */
  const handleContextMenu: React.MouseEventHandler<HTMLDivElement> = (e) => {
    e.stopPropagation();
    e.preventDefault();
    // 计算菜单相对于编辑器的位移
    let target = e.target! as any;
    // 注意react中没有offsetY属性,react将event事件重写了,要想获取到元素的offsetY属性,用nativeEvent
    let top = e.nativeEvent.offsetY;
    let left = e.nativeEvent.offsetX;
    while (target instanceof SVGElement) {
      target = target.parentNode;
    }
    while (!target.className.includes('editor')) {
      left += target.offsetLeft;
      top += target.offsetTop;
      target = target.parentNode;
    }
    dispatch(showContextMenuAction({ top, left }));
  };
  /**
   * @description 隐藏组选择器
   */
  const hideArea = () => {
    setIsShowArea(false);
    areawh.current = { width: 0, height: 0 };
  };
  /**
   * @description 得到在组选择器范围内的组件
   */
  const getSelectArea = useMemo(() => {
    return () => {
      const result: templateDataType[] = [];
      const { x, y } = areaPosition.current;
      // 计算所有的组件数据,判断是否在选中区域内
      componentListData.forEach((component) => {
        const { left, top, width, height } = getComponentRotatedStyle(component.style);
        if (
          x <= left &&
          y <= top &&
          left + width <= x + areawh.current.width &&
          top + height <= y + areawh.current.height
        ) {
          result.push(component);
        }
      });
      return result;
    };
  }, [componentListData]);
  /**
   * @description 创建组
   */
  const createGroup = () => {
    // 获取选中区域的组件数据
    const areaData = getSelectArea();
    if (areaData.length <= 1) {
      hideArea();
      return;
    }

    // 根据选中区域和区域中每个组件的位移信息来创建 Group 组件
    // 要遍历选择区域的每个组件,获取它们的 left top right bottom 信息来进行比较
    let top = Infinity,
      left = Infinity;
    let right = -Infinity,
      bottom = -Infinity;
    areaData.forEach((component) => {
      let style: MergeCSSProperties = {};
      style = getComponentRotatedStyle(component.style);
      if (style.left < left) left = style.left;
      if (style.top < top) top = style.top;
      if (style.right > right) right = style.right;
      if (style.bottom > bottom) bottom = style.bottom;
    });
    const updateAreaWh = {
      width: right - left,
      height: bottom - top,
    };
    areaPosition.current = {
      x: left,
      y: top,
    };
    areawh.current = updateAreaWh;
    // useRef只会更新数据,但是并不会重新渲染,所以要强制更新渲染
    forUpdate();
    compose({
      style: {
        left,
        top,
        ...updateAreaWh,
      },
      components: areaData,
    });
  };
  /**
   * @description 组合合并组件
   * @notice 当旋转的时候,合并组合的时候,样式有bug
   */
  const compose = (areaData: areaDataType) => {
    const components: templateDataType[] = [];
    areaData.components.forEach((component) => {
      if (component.component !== 'Group') {
        components.push(component);
      } else {
        // 如果要组合的组件中,已经存在组合数据,则需要提前拆分
        decompose(
          component,
          component.groupComponents!.map((item) => item.componentId!),
        );
        components.push(...component.groupComponents!);
      }
    });
    const groupComponent: templateDataType = {
      category: 'compose',
      type: 'Group',
      id: 'GroupComponent',
      component: 'Group',
      label: 'Group',
      ...commonAttr,
      groupComponents: components,
      style: {
        ...commonStyle,
        ...areaData.style,
      },
      componentId: getRandomStr(),
    };

    const createGroupStyleComponent = createGroupStyle(groupComponent);
    // 将已经放到 Group 组件数据删除,也就是在 componentData 中删除,因为它们已经放到 Group 组件中了
    dispatch(deleteComponentAction(areaData.components.map((item) => item.componentId!)));

    dispatch(addComponent(createGroupStyleComponent));

    dispatch(curComponentAction(createGroupStyleComponent));

    hideArea();
  };

  const handleMouseDown: React.MouseEventHandler<HTMLDivElement> = (e) => {
    // 如果没有选中组件 在画布上点击时需要调用 e.preventDefault() 防止触发 drop 事件
    // if (!curComponent) {
    //   e.preventDefault();
    // }
    e.preventDefault();
    hideArea();
    // 获取编辑器的位移信息,每次点击时都需要获取一次。主要是为了方便开发时调试用。
    const rectInfo = $('#editor').getBoundingClientRect();

    editorPosition.current = { x: rectInfo.x, y: rectInfo.y };

    const startX = e.clientX;

    const startY = e.clientY;

    areaPosition.current = {
      x: startX - (editorPosition.current.x || rectInfo.x),
      y: startY - (editorPosition.current.y || rectInfo.y),
    };

    // 展示选中区域
    setIsShowArea(true);

    const move = (moveEvent: MouseEvent) => {
      // useRef只会更新数据,但是并不会重新渲染,所以要强制更新渲染
      forUpdate();
      areawh.current = {
        width: Math.abs(moveEvent.clientX - startX),
        height: Math.abs(moveEvent.clientY - startY),
      };
      if (moveEvent.clientX < startX) {
        areaPosition.current.x = moveEvent.clientX - editorPosition.current.x;
      }

      if (moveEvent.clientY < startY) {
        areaPosition.current.y = moveEvent.clientY - editorPosition.current.y;
      }
    };

    const up = (e: MouseEvent) => {
      document.removeEventListener('mousemove', move);
      document.removeEventListener('mouseup', up);
      if (e.clientX === startX && e.clientY === startY) {
        hideArea();
        return;
      }
      createGroup();
    };

    document.addEventListener('mousemove', move);
    document.addEventListener('mouseup', up);
  };
  const getComponentStyle = (style: MergeCSSProperties) => {
    return getStyle(style, ['top', 'left', 'width', 'height', 'rotate']);
  };
  return (
    <div
      id="editor"
      style={{
        width: canvasInformation.width + 'px',
        height: canvasInformation.height + 'px',
        ...resultStyle,
      }}
      className={style.editor}
      onContextMenu={handleContextMenu}
      onMouseDown={handleMouseDown}
    >
      <div className={styles.scaleplateTop}>
        <Scaleplate direction="up" id="scaleplateUp" ratio={ratioValue} />
      </div>
      <div className={styles.scaleplateLeft}>
        <Scaleplate direction="left" id="scaleplateLeft" ratio={ratioValue} />
      </div>
      {canvasInformation.gridlines ? <Grid /> : null}

      {componentListData.length
        ? componentListData.map((item) => (
            <Shape
              key={item.componentId}
              style={getShapeStyle(item.style!)}
              element={item}
              defaultStyle={item.style!}
            >
              <RenderTemplate
                {...item}
                style={getComponentStyle(item.style)}
                propValue={item.propValue!}
              />
            </Shape>
          ))
        : null}
      <ContextMenu />
      <MarkLine />
      {isShowArea && <AreaComponent {...areawh.current} {...areaPosition.current} />}
    </div>
  );
})
Example #5
Source File: useEdit.ts    From ant-simple-draw with MIT License 4 votes vote down vote up
useEdit = () => {
  const [curComponent, componentDataList] = useSelector(
    createSelector(
      [(state: storeType) => state.component],
      ({ curComponent, componentDataList }) => [curComponent, componentDataList] as const,
    ),
  );

  const dispatch = useDispatch();

  const cutHandle = () => dispatch(cut());

  const copyHandle = () => dispatch(copy());

  const pasteHandle = (isContextMenuMouse: boolean = false) => [
    dispatch(paste(isContextMenuMouse)),
    dispatch(recordSnapshot()),
  ];

  const undoHandle = () => dispatch(undo());

  const redoHandle = () => dispatch(redo());

  const deleteHandle = (flag: string) => {
    if (!componentDataList.length) {
      return;
    }
    if (curComponent?.componentId && flag === 'one') {
      dispatch(deleteComponentAction([curComponent.componentId]));
    }
    if (flag === 'clearAll') {
      dispatch(deleteComponentAction(['clearAll']));
    }
    dispatch(recordSnapshot());
  };

  const layerTopHandle = () => [
    dispatch(getNotIncludedCurComponentHandle()),
    dispatch(topComponentAction()),
  ];

  const bottomLayerHandle = () => [
    dispatch(getNotIncludedCurComponentHandle()),
    dispatch(bottomComponentAction()),
  ];

  const upLayerHandle = () => dispatch(upComponentAction());

  const downLayerHandle = () => dispatch(downComponentAction());

  const zenModeActionHandle = () => dispatch(zenModeAction());

  const editHandle = (key: keyCodeType, otherParameters?: { isContextMenuMouse?: boolean }) => {
    let isContextMenuMouse = false;

    if (otherParameters) {
      isContextMenuMouse = otherParameters.isContextMenuMouse
        ? otherParameters.isContextMenuMouse
        : false;
    }
    switch (key) {
      case 'Ctrl+X':
        cutHandle();
        break;
      case 'Ctrl+C':
        copyHandle();
        break;
      case 'Ctrl+V':
        pasteHandle(isContextMenuMouse);
        break;
      case 'Ctrl+Z':
        undoHandle();
        break;
      case 'Shift+Z':
        redoHandle();
        break;
      case 'Delete':
        deleteHandle('one');
        break;
      case 'Shift+A':
        deleteHandle('clearAll');
        break;
      case 'Ctrl+Shift+Up':
        layerTopHandle();
        break;
      case 'Ctrl+Up':
        upLayerHandle();
        break;
      case 'Ctrl+Down':
        downLayerHandle();
        break;
      case 'Ctrl+Shift+Down':
        bottomLayerHandle();
        break;
      case 'Alt+Z':
        zenModeActionHandle();
        break;
      default:
        break;
    }
  };

  /**
   * @description 取消组合合并的组件
   */
  const decompose = (GroupComponents: templateDataType, deleteMergeComponent: string[]) => {
    /* if (curComponent && curComponent.component === 'Group') {
      const parentStyle = { ...curComponent.style };
      const components: templateDataType[] = curComponent.groupComponents!;
      const editorRect = $('#editor').getBoundingClientRect();
      components.forEach((component) => {
        // 将组合中的各个子组件拆分出来,并计算它们新的 style
        const decomposeComponentStyle = decomposeComponent(component, editorRect, parentStyle);

        dispatch(addComponent(decomposeComponentStyle));
      });
      // 组合的子组件已近添加了,这个时候组合组件得删除,没用了
      dispatch(deleteComponentAction([curComponent.componentId!]));
      dispatch(recordSnapshot());
    } */

    const parentStyle = { ...GroupComponents.style };
    const components: templateDataType[] = GroupComponents.groupComponents!;
    const editorRect = $('#editor').getBoundingClientRect();
    components.forEach((component) => {
      const decomposeComponentStyle = decomposeComponent(component, editorRect, parentStyle);

      dispatch(addComponent(decomposeComponentStyle));
    });
    dispatch(deleteComponentAction(deleteMergeComponent));
    dispatch(recordSnapshot());
  };

  return {
    editHandle,
    cutHandle,
    copyHandle,
    pasteHandle,
    undoHandle,
    redoHandle,
    deleteHandle,
    layerTopHandle,
    bottomLayerHandle,
    decompose,
    downLayerHandle,
    upLayerHandle,
  };
}