react-dnd#DragObjectWithType TypeScript Examples

The following examples show how to use react-dnd#DragObjectWithType. 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: ElementListItemDraggable.tsx    From openchakra with MIT License 5 votes vote down vote up
ElementListItemDraggable: React.FC<Props> = ({
  type,
  id,
  onSelect,
  moveItem,
  index,
  onHover,
  onUnhover,
  name,
}) => {
  const ref = useRef<HTMLDivElement>(null)
  const [, drop] = useDrop({
    accept: ITEM_TYPE,
    hover(item: DragObjectWithType, monitor) {
      if (!ref.current) {
        return
      }
      // @ts-ignore
      const dragIndex = item.index
      const hoverIndex = index
      if (dragIndex === hoverIndex) {
        return
      }
      const hoverBoundingRect = ref.current.getBoundingClientRect()
      const hoverMiddleY =
        (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
      const clientOffset = monitor.getClientOffset()
      const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top
      if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
        return
      }
      if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
        return
      }
      if (moveItem) {
        moveItem(dragIndex, hoverIndex)
      }
      // @ts-ignore
      item.index = hoverIndex
    },
  })
  const [{ isDragging }, drag] = useDrag({
    item: { type: ITEM_TYPE, id, index },
    collect: monitor => ({
      isDragging: monitor.isDragging(),
    }),
  })

  const opacity = isDragging ? 0 : 1

  drag(drop(ref))

  const onSelectElement = () => {
    onSelect(id)
  }

  const onMouseOver = () => {
    onHover(id)
  }

  return (
    <ElementListItem
      ref={ref}
      onSelect={onSelectElement}
      opacity={opacity}
      onMouseOver={onMouseOver}
      onMouseOut={onUnhover}
      type={type}
      draggable
      name={name}
    />
  )
}
Example #2
Source File: DropZone.tsx    From next-core with GNU General Public License v3.0 4 votes vote down vote up
export function DropZone({
  nodeUid,
  isRoot,
  separateCanvas,
  isPortalCanvas,
  independentPortalCanvas,
  canvasIndex,
  mountPoint,
  fullscreen,
  delegatedContext,
  dropZoneStyle,
  dropZoneBodyStyle,
  slotContentLayout,
  showOutlineIfEmpty,
  hiddenWrapper = true,
  emptyClassName,
}: DropZoneProps): React.ReactElement {
  const dropZoneBody = React.useRef<HTMLDivElement>();
  const [dropPositionCursor, setDropPositionCursor] =
    React.useState<DropPositionCursor>(null);
  const dropPositionCursorRef = React.useRef<DropPositionCursor>();
  const contextMenuStatus = useBuilderContextMenuStatus();
  const manager = useBuilderDataManager();
  const { nodes, edges, wrapperNode } = useBuilderData();
  const isWrapper = hiddenWrapper && isRoot && !isEmpty(wrapperNode);
  const node = useBuilderNode({ nodeUid, isRoot, isWrapper });
  const groupedChildNodes = useBuilderGroupedChildNodes({
    nodeUid,
    isRoot,
    doNotExpandTemplates: isWrapper,
    isWrapper,
  });

  const isGeneralizedPortalCanvas = independentPortalCanvas
    ? canvasIndex > 0
    : isPortalCanvas;
  const hasTabs = separateCanvas || independentPortalCanvas;

  const canDrop = useCanDrop();
  const refinedSlotContentLayout =
    slotContentLayout ?? EditorSlotContentLayout.BLOCK;

  const selfChildNodes = React.useMemo(
    () =>
      groupedChildNodes.find((group) => group.mountPoint === mountPoint)
        ?.childNodes ?? [],
    [groupedChildNodes, mountPoint]
  );

  const canvasList = useCanvasList(selfChildNodes);

  const selfChildNodesInCurrentCanvas = React.useMemo(
    () =>
      separateCanvas
        ? selfChildNodes.filter((child) =>
            Boolean(Number(Boolean(isPortalCanvas)) ^ Number(!child.portal))
          )
        : independentPortalCanvas
        ? canvasList[clamp(canvasIndex ?? 0, 0, canvasList.length - 1)]
        : selfChildNodes,
    [
      canvasIndex,
      independentPortalCanvas,
      isPortalCanvas,
      selfChildNodes,
      canvasList,
      separateCanvas,
    ]
  );

  const canvasSettings = React.useMemo(
    () =>
      selfChildNodesInCurrentCanvas[0]?.$$parsedProperties
        ._canvas_ as BuilderCanvasSettings,
    [selfChildNodesInCurrentCanvas]
  );

  const getDroppingIndexInFullCanvas = React.useCallback(
    (droppingIndexInCurrentCanvas: number) => {
      if (!hasTabs) {
        return droppingIndexInCurrentCanvas;
      }
      if (selfChildNodesInCurrentCanvas.length > 0) {
        const cursorNode =
          selfChildNodesInCurrentCanvas[
            droppingIndexInCurrentCanvas === 0
              ? 0
              : droppingIndexInCurrentCanvas - 1
          ];
        return (
          selfChildNodes.findIndex((child) => child === cursorNode) +
          (droppingIndexInCurrentCanvas === 0 ? 0 : 1)
        );
      }
      return isGeneralizedPortalCanvas ? selfChildNodes.length : 0;
    },
    [
      hasTabs,
      selfChildNodesInCurrentCanvas,
      isGeneralizedPortalCanvas,
      selfChildNodes,
    ]
  );

  const getDroppingContext = React.useCallback(() => {
    if (delegatedContext) {
      const siblingGroups = getBuilderGroupedChildNodes({
        nodeUid: delegatedContext.templateUid,
        nodes,
        edges,
        doNotExpandTemplates: true,
      });
      return {
        droppingParentUid: delegatedContext.templateUid,
        droppingParentInstanceId: nodes.find(
          (item) => item.$$uid === delegatedContext.templateUid
        ).instanceId,
        droppingMountPoint: delegatedContext.templateMountPoint,
        droppingChildNodes:
          siblingGroups.find(
            (group) => group.mountPoint === delegatedContext.templateMountPoint
          )?.childNodes ?? [],
        droppingSiblingGroups: siblingGroups,
      };
    }
    return {
      droppingParentUid: node.$$uid,
      droppingParentInstanceId: isWrapper
        ? wrapperNode.instanceId
        : node.instanceId,
      droppingMountPoint: mountPoint,
      droppingChildNodes: selfChildNodes,
      droppingSiblingGroups: groupedChildNodes,
    };
  }, [
    delegatedContext,
    edges,
    groupedChildNodes,
    mountPoint,
    node,
    nodes,
    selfChildNodes,
    isWrapper,
    wrapperNode,
  ]);

  const [{ isDraggingOverCurrent }, dropRef] = useDrop({
    accept: [
      BuilderDataTransferType.NODE_TO_ADD,
      BuilderDataTransferType.NODE_TO_MOVE,
      BuilderDataTransferType.SNIPPET_TO_APPLY,
    ],
    canDrop: (
      item: DragObjectWithType &
        (
          | BuilderDataTransferPayloadOfNodeToAdd
          | BuilderDataTransferPayloadOfNodeToMove
        )
    ) =>
      independentPortalCanvas && isGeneralizedPortalCanvas
        ? selfChildNodesInCurrentCanvas.length === 0
        : item.type === BuilderDataTransferType.NODE_TO_ADD ||
          item.type === BuilderDataTransferType.SNIPPET_TO_APPLY ||
          isRoot ||
          canDrop(
            (item as BuilderDataTransferPayloadOfNodeToMove).nodeUid,
            nodeUid
          ),
    collect: (monitor) => ({
      isDraggingOverCurrent:
        monitor.isOver({ shallow: true }) && monitor.canDrop(),
    }),
    hover: (item, monitor) => {
      if (monitor.isOver({ shallow: true }) && monitor.canDrop()) {
        const { x, y } = monitor.getClientOffset();
        dropPositionCursorRef.current = getDropPosition(
          x,
          y,
          dropZoneBody.current.parentElement,
          dropZoneBody.current
        );
        setDropPositionCursor(dropPositionCursorRef.current);
      }
    },
    drop: (item, monitor) => {
      if (!monitor.didDrop()) {
        const { type, ...data } = item;
        processDrop({
          type: type as BuilderDataTransferType,
          data,
          droppingIndex: getDroppingIndexInFullCanvas(
            dropPositionCursorRef.current.index
          ),
          isPortalCanvas: isGeneralizedPortalCanvas,
          manager,
          ...getDroppingContext(),
        });
      }
    },
  });

  React.useEffect(() => {
    manager.updateDroppingStatus(
      delegatedContext ? delegatedContext.templateUid : node.$$uid,
      delegatedContext ? delegatedContext.templateMountPoint : mountPoint,
      isDraggingOverCurrent
    );
  }, [isDraggingOverCurrent, mountPoint, manager, delegatedContext, node]);

  const droppable =
    !!delegatedContext ||
    isWrapper ||
    !(node.$$isExpandableTemplate || node.$$isTemplateInternalNode);

  const dropZoneRef = React.useRef<HTMLElement>();

  const dropZoneRefCallback = React.useCallback(
    (element: HTMLElement) => {
      dropZoneRef.current = element;
      if (droppable) {
        dropRef(element);
      }
    },
    [dropRef, droppable]
  );

  const handleContextMenu = React.useCallback(
    (event: React.MouseEvent) => {
      // `event.stopPropagation()` not working across bricks.
      if (
        !isGeneralizedPortalCanvas &&
        isCurrentTargetByClassName(
          event.target as HTMLElement,
          dropZoneRef.current
        )
      ) {
        event.preventDefault();
        manager.contextMenuChange({
          active: true,
          node,
          x: event.clientX,
          y: event.clientY,
        });
      }
    },
    [isGeneralizedPortalCanvas, manager, node]
  );

  return (
    <div
      ref={dropZoneRefCallback}
      className={classNames(
        styles.dropZone,
        isRoot
          ? classNames(
              styles.isRoot,
              canvasSettings?.mode &&
                String(canvasSettings.mode)
                  .split(/\s+/g)
                  .map((mode) => styles[`mode-${mode}`]),
              {
                [styles.fullscreen]: fullscreen,
                [styles.hasTabs]: hasTabs,
              }
            )
          : styles.isSlot,
        {
          [styles.isPortalCanvas]: isGeneralizedPortalCanvas,
          [styles.dropping]: isDraggingOverCurrent,
          [styles.active]:
            isRoot &&
            contextMenuStatus.active &&
            contextMenuStatus.node.$$uid === node.$$uid,
          [styles.showOutlineIfEmpty]:
            !isRoot && showOutlineIfEmpty && selfChildNodes.length === 0,
          [styles.slotContentLayoutBlock]:
            refinedSlotContentLayout === EditorSlotContentLayout.BLOCK,
          [styles.slotContentLayoutInline]:
            refinedSlotContentLayout === EditorSlotContentLayout.INLINE,
          [styles.slotContentLayoutGrid]:
            refinedSlotContentLayout === EditorSlotContentLayout.GRID,
        }
      )}
      style={dropZoneStyle}
      onContextMenu={isRoot ? handleContextMenu : null}
    >
      <div
        ref={dropZoneBody}
        className={classNames(
          styles.dropZoneBody,
          selfChildNodesInCurrentCanvas.length === 0 && emptyClassName
        )}
        data-slot-id={mountPoint}
        style={dropZoneBodyStyle}
      >
        {selfChildNodesInCurrentCanvas.map((child) => (
          <EditorBrickAsComponent
            key={child.$$uid}
            node={child}
            slotContentLayout={refinedSlotContentLayout}
          />
        ))}
      </div>
      {
        <div
          className={classNames(
            styles.dropCursor,
            dropPositionCursor?.isVertical
              ? styles.dropCursorVertical
              : styles.dropCursorHorizontal
          )}
          style={{
            top: dropPositionCursor?.y,
            left: dropPositionCursor?.x,
            height: dropPositionCursor?.height,
          }}
        ></div>
      }
    </div>
  );
}
Example #3
Source File: kanban.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
PureKanban = (props: IKanbanProps) => {
  const { data, boards, execOperation, setBoard, setIsDrag, isDrag, setCurrentCard, currentCard, CardRender, ...rest } =
    props;
  const {
    id: boardId,
    title: boardTitle,
    cards: boardCards,
    pageNo: propsPageNo,
    operations,
    total = 0,
    pageSize = 20,
    ...dataRest
  } = data || {};
  const titleArr = map(boards, 'title');
  const otherTitle = without(titleArr, boardTitle);
  const [pageNo, setPageNo] = React.useState(propsPageNo);
  const [cards, setCards] = React.useState(boardCards || []);
  const [title, setTitle] = React.useState(boardTitle);
  const [showShadow, setShowShadow] = React.useState(false);
  const cardType = 'kanban-info-card';

  const boardDataRef = {
    id: boardId,
    title: boardTitle,
    pageNo: propsPageNo,
    operations,
    total,
    pageSize,
    ...dataRest,
  };

  useUpdateEffect(() => {
    if (propsPageNo > pageNo) {
      boardCards && setCards((prev) => prev.concat(boardCards));
    } else {
      setCards(boardCards || []);
    }
    setPageNo(propsPageNo);
  }, [boardCards, propsPageNo]);

  const boardLoadMoreOp = operations?.boardLoadMore;
  const hasMore = boardLoadMoreOp && total > cards.length;

  const [{ isOver, isAllowDrop }, drop] = useDrop({
    accept: cardType,
    drop: (item: Merge<DragObjectWithType, { data: ICardData }>) => {
      const { cardMoveTo } = item.data.operations;
      const targetKeys = cardMoveTo.serverData?.allowedTargetBoardIDs || [];
      if (!targetKeys?.includes(boardId)) {
        setCurrentCard(null);
        return;
      }
      setCurrentCard(item.data);
      const dragColKey = item.data.boardId;
      const dropColKey = boardId;
      let newTargetKeys = [...targetKeys];
      if (!newTargetKeys.includes(dragColKey)) {
        newTargetKeys.push(dragColKey);
      }
      newTargetKeys = newTargetKeys.filter((t) => t !== dropColKey);
      const newItem = produce(item, (draft: { data: Obj }) => {
        draft.data.operations.cardMoveTo.serverData.allowedTargetBoardIDs = newTargetKeys;
      });
      setBoard((prev) => {
        return prev.map((col) => {
          if (col.id === dropColKey) {
            return {
              ...col,
              cards: col.cards ? [newItem.data, ...col.cards] : [newItem.data],
              total: +(col.total || 0) + 1,
            };
          } else if (col.id === dragColKey) {
            return {
              ...col,
              cards: col.cards?.filter((a) => a.id !== newItem.data.id),
              total: Math.max((col.total || 0) - 1, 0),
            };
          }
          return col;
        });
      });
      execOperation({
        reload: true,
        ...cardMoveTo,
        clientData: { targetBoardID: boardId, dataRef: item.data.dataRef },
      });
    },
    collect: (monitor) => {
      const item = monitor?.getItem && monitor?.getItem();
      const targetKeys = get(item, 'data.operations.cardMoveTo.serverData.allowedTargetBoardIDs') || [];
      let _isAllowDrop = true;
      if (!targetKeys?.length || !targetKeys.includes(boardId)) {
        _isAllowDrop = false;
      }
      return {
        isOver: monitor.isOver(),
        isAllowDrop: _isAllowDrop,
      };
    },
  });

  const changeData = (item: CP_KANBAN.ICard) => {
    const { operations: cardOp } = item;
    return {
      ...item,
      boardId,
      operations: {
        ...(cardOp?.cardMoveTo ? { cardMoveTo: { key: 'cardMoveTo', ...cardOp.cardMoveTo } } : {}),
      },
      dataRef: item,
    };
  };

  let cls = isOver ? 'drag-over' : '';
  cls = isDrag && !isAllowDrop ? `drop-disable ${cls}` : cls;
  cls = isDrag && !isOver ? `not-drag ${cls}` : cls;
  const deleteBoardOp = operations?.boardDelete;
  const deleteAuth = deleteBoardOp?.disabled !== true;
  const updateBoardOp = operations?.boardUpdate;
  const updateAuth = updateBoardOp?.disabled !== true;

  const doUpdate = () => {
    if (title === boardTitle) return;
    if (!title) {
      setTitle(boardTitle);
      return notify('error', i18n.t('can not be empty'));
    }
    if (otherTitle.includes(title)) {
      setTitle(boardTitle);
      return notify('error', i18n.t('{name} already exists', { name: boardTitle }));
    }
    execOperation({
      key: 'boardUpdate',
      reload: true,
      ...operations?.boardUpdate,
      clientData: { dataRef: data, title },
    });
  };

  const handleScroll = (e: any) => {
    setShowShadow(e.target.scrollTop !== 0);
  };

  const loadMore = () => {
    execOperation({
      key: 'boardLoadMore',
      reload: true,
      ...boardLoadMoreOp,
      clientData: {
        pageNo: pageNo + 1,
        pageSize,
        dataRef: boardDataRef,
      },
    } as CP_COMMON.Operation);
  };

  return (
    <div className={classnames(`cp-kanban-col ${cls}`, { 'cp-kanban-col-special-pdd': updateBoardOp })} ref={drop}>
      <div
        className={`flex justify-between items-center cp-kanban-col-header ${showShadow ? 'shadow' : ''} ${
          updateBoardOp ? 'inp' : ''
        }`}
      >
        <div className="text-base font-medium text-default-8 flex-1 flex items-center ">
          {updateBoardOp ? (
            updateAuth ? (
              <Input
                className="text-base font-medium cp-kanban-label-input"
                value={title}
                onChange={(e: React.ChangeEvent<HTMLInputElement>) => setTitle(e.target.value)}
                onPressEnter={doUpdate}
                onBlur={doUpdate}
              />
            ) : (
              <Tooltip title={updateBoardOp.disabledTip || i18n.t('common:No permission to operate')}>
                <Input className="text-base font-medium cp-kanban-label-input update-disabled" readOnly value={title} />
              </Tooltip>
            )
          ) : (
            title
          )}
          <div className="text-default-8 ml-1 text-sm px-2.5 rounded-lg bg-default-06">{total}</div>
        </div>
        {deleteBoardOp ? (
          deleteBoardOp.confirm ? (
            <WithAuth pass={deleteAuth} noAuthTip={deleteBoardOp.disabledTip}>
              <Popconfirm
                title={deleteBoardOp.confirm}
                onConfirm={() =>
                  execOperation({
                    key: 'boardDelete',
                    reload: true,
                    ...deleteBoardOp,
                    clientData: { dataRef: boardDataRef },
                  })
                }
              >
                <ErdaIcon type="delete1" className="ml-3 cursor-pointer" />
              </Popconfirm>
            </WithAuth>
          ) : (
            <WithAuth pass={deleteAuth} noAuthTip={deleteBoardOp.disabledTip}>
              <ErdaIcon
                type="delete1"
                className="ml-3 cursor-pointer"
                onClick={() =>
                  execOperation({
                    key: 'boardDelete',
                    reload: true,
                    ...deleteBoardOp,
                    clientData: { dataRef: boardDataRef },
                  })
                }
              />
            </WithAuth>
          )
        ) : null}
      </div>
      <div className="cp-kanban-col-content" onScroll={handleScroll}>
        {map(cards, (item) => {
          const curDragOp = item.operations?.cardMoveTo;
          return (
            <CardItem
              key={item.id}
              card={changeData(item)}
              CardRender={CardRender}
              cardType={cardType}
              draggable={curDragOp && !curDragOp.disabled}
              className={`${isDrag ? 'hidden' : ''} kanban-list-item ${
                currentCard?.id === item.id ? 'dragged-card' : ''
              }`}
              setIsDrag={setIsDrag}
              onClick={() => {
                rest?.customOp?.clickCard?.(item);
              }}
            />
          );
        })}
        {hasMore && !isDrag ? (
          <div className="hover-active py-1 text-center load-more" onClick={() => loadMore()}>
            {i18n.t('load more')}
          </div>
        ) : null}
      </div>
    </div>
  );
}