react-dnd#DropTargetMonitor TypeScript Examples

The following examples show how to use react-dnd#DropTargetMonitor. 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: index.tsx    From hive with MIT License 6 votes vote down vote up
widgetTarget = {
    canDrop() {
        return false;
    },

    hover(props: Props, monitor: DropTargetMonitor) {
        const { id: draggedId } = monitor.getItem();
        const overId = props.model.uuid;

        if (draggedId !== overId) {
            const overIndex = props.dashboardView.currentPage.findWidgetIndex(overId);
            props.dashboardView.currentPage.moveWidget(draggedId, overIndex);
        }
    },
}
Example #2
Source File: navigator-item-dnd-container.tsx    From utopia with MIT License 6 votes vote down vote up
function onDrop(
  propsOfDraggedItem: NavigatorItemDragAndDropWrapperProps,
  propsOfDropTargetItem: NavigatorItemDragAndDropWrapperProps,
  monitor: DropTargetMonitor,
  component: HTMLDivElement | null,
) {
  if (monitor != null && component != null) {
    const dragSelections = propsOfDraggedItem.getDragSelections()
    const filteredSelections = dragSelections.filter((selection) =>
      canDrop(propsOfDraggedItem, selection.elementPath),
    )
    const draggedElements = filteredSelections.map((selection) => selection.elementPath)
    const clearHintAction = showNavigatorDropTargetHint(null, null)
    const target = propsOfDropTargetItem.elementPath

    switch (propsOfDropTargetItem.appropriateDropTargetHint?.type) {
      case 'before':
        return propsOfDraggedItem.editorDispatch(
          [placeComponentsBefore(draggedElements, target), clearHintAction],
          'everyone',
        )
      case 'after':
        return propsOfDraggedItem.editorDispatch(
          [placeComponentsAfter(draggedElements, target), clearHintAction],
          'everyone',
        )
      case 'reparent':
        return propsOfDraggedItem.editorDispatch(
          [reparentComponents(draggedElements, target), clearHintAction],
          'everyone',
        )
      default:
        return propsOfDraggedItem.editorDispatch([clearHintAction], 'everyone')
    }
  }
}
Example #3
Source File: DragItem.tsx    From gio-design with Apache License 2.0 6 votes vote down vote up
DragItem: React.FC<DragItemProps> = (props) => {
  const { label, value, onMoved, index, disabled, ...rest } = props;
  const prefixCls = `${usePrefixCls(PREFIX)}`;
  const ref = useRef<HTMLDivElement>(null);
  const [{ handlerId }, drop] = useDrop({
    accept: 'drag-item',
    collect(monitor) {
      return {
        handlerId: monitor.getHandlerId(),
      };
    },
    hover(item: { index: number; type: string; id: string }, monitor: DropTargetMonitor) {

      const dragIndex = item.index;
      const hoverIndex = index;

      if (dragIndex === hoverIndex) {
        return;
      }
      const hoverBoundingRect = ref.current?.getBoundingClientRect() || { bottom: 0, top: 0 };
      const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
      const clientOffset = monitor.getClientOffset();

      const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top;

      // Dragging upwards or downwards
      if ((dragIndex < hoverIndex && hoverClientY < hoverMiddleY) || (dragIndex > hoverIndex && hoverClientY > hoverMiddleY)) {
        return
      };

      onMoved?.(dragIndex as number, hoverIndex);
      // eslint-disable-next-line no-param-reassign
      item.index = hoverIndex;
    },
  });
  const [, drag] = useDrag({
    item: { type: 'drag-item', id: value, index },
    collect: (monitor: DragSourceMonitor) => ({
      isDragging: monitor.isDragging(),
    }),
    canDrag: !disabled,
  });

  drag(drop(ref));
  return (
    <div
      className={classNames(`${prefixCls}--item`, `${prefixCls}--item--drag`, {
        [`${prefixCls}--item--disabled`]: disabled,
      })}
      ref={ref}
      data-handler-id={handlerId}
    >
      <DragOutlined
        className={classNames(`${prefixCls}--item--drag--icon`, {
          [`${prefixCls}--item--drag--icon--disabled`]: disabled,
        })}
        color="#ADB2C2"
        size="14px"
      />
      <Item label={label} value={value} disabled={disabled} {...rest} />
    </div>
  );
}
Example #4
Source File: useDropComponent.ts    From openchakra with MIT License 6 votes vote down vote up
useDropComponent = (
  componentId: string,
  accept: (ComponentType | MetaComponentType)[] = rootComponents,
  canDrop: boolean = true,
) => {
  const dispatch = useDispatch()

  const [{ isOver }, drop] = useDrop({
    accept,
    collect: monitor => ({
      isOver: monitor.isOver({ shallow: true }) && monitor.canDrop(),
    }),
    drop: (item: ComponentItemProps, monitor: DropTargetMonitor) => {
      if (!monitor.isOver()) {
        return
      }

      if (item.isMoved) {
        dispatch.components.moveComponent({
          parentId: componentId,
          componentId: item.id,
        })
      } else if (item.isMeta) {
        dispatch.components.addMetaComponent(builder[item.type](componentId))
      } else {
        dispatch.components.addComponent({
          parentName: componentId,
          type: item.type,
          rootParentType: item.rootParentType,
        })
      }
    },
    canDrop: () => canDrop,
  })

  return { drop, isOver }
}
Example #5
Source File: DropHolder.tsx    From datart with Apache License 2.0 6 votes vote down vote up
DropHolder: React.FC<DropHolderProps> = memo(
  ({ tabItem, parentId }) => {
    const [{ isOver, canDrop }, refDrop] = useDrop(
      () => ({
        accept: CONTAINER_TAB,
        item: { tabItem, parentId },
        drop: () => ({ tabItem, parentId }),
        canDrop: (item: any) => {
          if (CanDropToWidgetTypes.includes(item.type)) {
            return true;
          }
          return false;
        },
        collect: (monitor: DropTargetMonitor) => ({
          isOver: monitor.isOver(),
          canDrop: monitor.canDrop(),
        }),
      }),
      [],
    );
    const bgColor = useMemo(() => {
      let color = 'transparent';
      if (canDrop) {
        color = '#f1e648c7';
        if (isOver) {
          color = '#1bcf81d3';
        }
      }

      return color;
    }, [isOver, canDrop]);
    return (
      <DropWrap ref={refDrop} bgColor={bgColor}>
        <div className="center">将组件拖入该区域</div>
      </DropWrap>
    );
  },
)
Example #6
Source File: use-hooks.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
useListDnD = ({ type, index, onMove, collect }: IDragProps) => {
  const dragRef = useRef<HTMLDivElement>(null);

  const [, drop] = useDrop({
    accept: type,
    hover(item: DragItem, monitor: DropTargetMonitor) {
      if (!dragRef.current) {
        return;
      }
      const dragIndex = item.index;
      const hoverIndex = index;

      // Don't replace items with themselves
      if (dragIndex === hoverIndex) {
        return;
      }

      // Determine rectangle on screen
      const hoverBoundingRect = dragRef.current!.getBoundingClientRect();

      // Get vertical middle
      const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;

      // Determine mouse position
      const clientOffset = monitor.getClientOffset();

      // Get pixels to the top
      const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top;

      // Only perform the move when the mouse has crossed half of the items height
      // When dragging downwards, only move when the cursor is below 50%
      // When dragging upwards, only move when the cursor is above 50%

      // Dragging downwards
      if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
        return;
      }

      // Dragging upwards
      if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
        return;
      }

      // Time to actually perform the action
      onMove(dragIndex, hoverIndex);

      // Note: we're mutating the monitor item here!
      // Generally it's better to avoid mutations,
      // but it's good here for the sake of performance
      // to avoid expensive index searches.
      item.index = hoverIndex;
    },
  });

  const [collectedProps, drag, previewRef] = useDrag({
    item: { index, type },
    collect,
  });

  drag(drop(dragRef));

  return [dragRef, previewRef, collectedProps];
}
Example #7
Source File: DragAndDropTreeComponent.tsx    From frontend-sample-showcase with MIT License 5 votes vote down vote up
DragAndDropNode: React.FC<TreeNodeRendererProps> = (props) => {
  const { treeModelSource, treeNodeLoader, isDragging, setDragging } = React.useContext(dragDropContext)!;

  // Make node draggable
  const [, dragSourceRef] = useDrag({
    item: { type: "tree-node", id: props.node.id },
    begin: () => setDragging(true),
    end: () => setDragging(false),
  });

  // Make node accept drop events
  const [{ isHovered }, dropTargetRef] = useDrop({
    accept: "tree-node",
    hover: handleHover,
    canDrop: handleCanDrop,
    drop: handleDrop,
    collect: (monitor) => ({ isHovered: monitor.isOver() }),
  });

  const [canDrop, setCanDrop] = React.useState(false);
  function handleCanDrop(item: NodeDragObject): boolean {
    // Do not allow dropping a node in such a way that would create a parent-child relationship cycle
    const isDroppingOnSelf = props.node.id === item.id && dropArea === DropArea.Inside;
    const newCanDrop = !isDroppingOnSelf && !areNodesRelated(treeModelSource.getModel(), item.id, props.node.id);
    setCanDrop(newCanDrop);
    return newCanDrop;
  }

  const elementRef = React.useRef<HTMLDivElement>(null);
  const [dropArea, setDropArea] = React.useState<DropArea>(DropArea.Inside);
  function handleHover(item: NodeDragObject, monitor: DropTargetMonitor): void {
    // Determine which drop area is hovered and whether we can drop the node there
    const cursorY = monitor.getClientOffset()?.y;
    const dropTargetY = elementRef.current?.getBoundingClientRect().y;
    if (cursorY !== undefined && dropTargetY !== undefined) {
      setDropArea(determineDropArea(25, 7, cursorY - dropTargetY));
      handleCanDrop(item);
    }
  }

  function handleDrop(item: NodeDragObject): void {
    // The main entry point for drop event handling
    const { parentId, index } = getDropLocation(treeModelSource.getModel(), props.node, dropArea);
    moveNode(treeModelSource, treeNodeLoader, item.id, parentId, index)
      .catch((error) => {
        // eslint-disable-next-line no-console
        console.error(error);
      });
  }

  const isDropAreaDisplayed = isHovered && canDrop;
  const nodeStyle: React.CSSProperties = {
    height: 25,
    ...(isDropAreaDisplayed && dropArea === DropArea.Inside && { background: "var(--buic-row-hover)" }),
  };
  return (
    <div ref={mergeRefs([dragSourceRef, dropTargetRef, elementRef])}>
      <div style={{ height: 0 }}>
        {isDropAreaDisplayed && dropArea === DropArea.Above && <NodeInsertMarker topOffset={0} />}
        {isDropAreaDisplayed && dropArea === DropArea.Below && <NodeInsertMarker topOffset={25} />}
      </div>
      <BasicTreeNode style={nodeStyle} isHoverDisabled={isDragging} {...props} />
    </div>
  );
}
Example #8
Source File: navigator-item-dnd-container.tsx    From utopia with MIT License 4 votes vote down vote up
function onHover(
  propsOfDraggedItem: NavigatorItemDragAndDropWrapperProps,
  propsOfDropTargetItem: NavigatorItemDragAndDropWrapperProps,
  monitor: DropTargetMonitor | null,
  component: HTMLDivElement | null,
): void {
  if (
    monitor != null &&
    component != null &&
    propsOfDraggedItem
      .getDragSelections()
      .some((selection) => canDrop(propsOfDraggedItem, selection.elementPath))
  ) {
    // React DnD necessitates the two divs around the actual navigator item,
    // so we need to drill down to the navigator elements themselves which have real dimensions.
    const dropDomNode = ReactDOM.findDOMNode(component)
    const dragDomNode = dropDomNode?.firstChild
    const domNode = dragDomNode?.firstChild
    if (domNode == null || typeof domNode === 'string') {
      return
    }
    const dropTargetRectangle = (domNode as HTMLElement).getBoundingClientRect()
    const cursor = monitor.getClientOffset()
    const targetAction = propsOfDraggedItem.highlighted
      ? []
      : [EditorActions.setHighlightedView(propsOfDraggedItem.elementPath)]
    const canReparent = propsOfDraggedItem
      .getDragSelections()
      .every(
        (dragSelectedItem: DragSelection) =>
          !EP.pathsEqual(propsOfDraggedItem.elementPath, dragSelectedItem.elementPath) &&
          propsOfDraggedItem.supportsChildren,
      )
    const numberOfAreasToCut = canReparent ? 3 : 2

    if (cursor == null) {
      return
    }

    if (isCursorInTopArea(dropTargetRectangle, cursor.y, numberOfAreasToCut)) {
      if (propsOfDraggedItem.appropriateDropTargetHint?.type !== 'before') {
        propsOfDraggedItem.editorDispatch(
          [
            ...targetAction,
            showNavigatorDropTargetHint('before', propsOfDropTargetItem.elementPath),
          ],
          'leftpane',
        )
      }
    } else if (
      isCursorInBottomArea(dropTargetRectangle, cursor.y, numberOfAreasToCut) &&
      (propsOfDraggedItem.noOfChildren === 0 || propsOfDraggedItem.collapsed)
    ) {
      if (
        isCursorInLeftAreaOfItem(cursor.x, propsOfDraggedItem.elementPath) &&
        EP.parentPath(propsOfDraggedItem.elementPath) != null
      ) {
        const maximumTargetDepth = propsOfDraggedItem.getMaximumDistance(
          EP.toComponentId(propsOfDraggedItem.elementPath),
          0,
        )
        const cursorTargetDepth = getTargetDepthFromMousePosition(
          cursor.x,
          propsOfDraggedItem.elementPath,
        )
        const targetDistance = Math.min(cursorTargetDepth, maximumTargetDepth)
        const targetTP = EP.getNthParent(propsOfDraggedItem.elementPath, targetDistance)
        if (
          propsOfDraggedItem.appropriateDropTargetHint?.type !== 'after' ||
          !EP.pathsEqual(propsOfDraggedItem.appropriateDropTargetHint?.target, targetTP)
        ) {
          propsOfDraggedItem.editorDispatch(
            [...targetAction, showNavigatorDropTargetHint('after', targetTP)],
            'leftpane',
          )
        }
      } else if (
        propsOfDraggedItem.appropriateDropTargetHint?.type !== 'after' ||
        !EP.pathsEqual(
          propsOfDraggedItem.appropriateDropTargetHint?.target,
          propsOfDropTargetItem.elementPath,
        )
      ) {
        propsOfDraggedItem.editorDispatch(
          [
            ...targetAction,
            showNavigatorDropTargetHint('after', propsOfDropTargetItem.elementPath),
          ],
          'leftpane',
        )
      }
    } else if (canReparent) {
      if (propsOfDraggedItem.appropriateDropTargetHint?.type !== 'reparent') {
        propsOfDraggedItem.editorDispatch(
          [
            ...targetAction,
            showNavigatorDropTargetHint('reparent', propsOfDropTargetItem.elementPath),
          ],
          'leftpane',
        )
      }
    } else if (propsOfDraggedItem.appropriateDropTargetHint?.type !== null) {
      propsOfDraggedItem.editorDispatch([showNavigatorDropTargetHint(null, null)], 'leftpane')
    }
  }
}
Example #9
Source File: DropTarget.tsx    From gear-js with GNU General Public License v3.0 4 votes vote down vote up
DropTarget = ({ type, setDroppedFile }: Props) => {
  const alert = useAlert();

  const [wrongFormat, setWrongFormat] = useState(false);
  const inputRef = useRef<HTMLInputElement>(null);

  if (wrongFormat) {
    setTimeout(() => setWrongFormat(false), 3000);
  }

  const checkFileFormat = useCallback((files: any) => {
    if (typeof files[0]?.name === 'string') {
      const fileExt: string = files[0].name.split('.').pop().toLowerCase();
      return fileExt !== 'wasm';
    }
    return true;
  }, []);

  const handleFilesUpload = useCallback(
    (file: File) => {
      setDroppedFile({ file, type });
    },
    [setDroppedFile, type]
  );

  const emulateInputClick = () => {
    inputRef.current?.click();
  };

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const {
      target: { files },
    } = event;
    if (files?.length) {
      const isCorrectFormat = checkFileFormat(files);
      setWrongFormat(isCorrectFormat);
      if (!isCorrectFormat) {
        handleFilesUpload(files[0]);
        // since type='file' input can't be controlled,
        // reset it's value to trigger onChange again in case the same file selected twice
        event.target.value = '';
      } else {
        alert.error('Wrong file format');
        setWrongFormat(false);
      }
    }
  };

  const handleFileDrop = useCallback(
    (item) => {
      if (item) {
        const { files } = item;
        const isCorrectFormat = checkFileFormat(files);
        setWrongFormat(isCorrectFormat);
        if (!isCorrectFormat) {
          handleFilesUpload(files[0]);
        } else {
          alert.error('Wrong file format');
          setWrongFormat(false);
        }
      }
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [checkFileFormat, handleFilesUpload]
  );

  const [{ canDrop, isOver }, drop] = useDrop(
    () => ({
      accept: [NativeTypes.FILE],
      drop(item: { files: any[] }) {
        if (handleFileDrop) {
          handleFileDrop(item);
        }
      },
      collect: (monitor: DropTargetMonitor) => ({
        isOver: monitor.isOver(),
        canDrop: monitor.canDrop(),
      }),
    }),
    [handleFileDrop]
  );

  const isActive = canDrop && isOver;
  const className = clsx(styles.drop, isActive && styles.active);
  const isProgramUpload = type === UploadTypes.PROGRAM;
  const buttonText = `Upload ${type}`;

  return (
    <div className={className} ref={drop}>
      {isActive ? (
        <div className={styles.file}>
          <span className={styles.text}>Drop your .wasm files here to upload</span>
        </div>
      ) : (
        <div className={styles.noFile}>
          <input className={styles.input} ref={inputRef} type="file" onChange={handleChange} />
          <Button
            text={buttonText}
            icon={isProgramUpload ? upload : editor}
            color={isProgramUpload ? 'primary' : 'secondary'}
            onClick={emulateInputClick}
          />
          <div className={styles.text}>{`Click “${buttonText}” to browse or drag and drop your .wasm files here`}</div>
        </div>
      )}
    </div>
  );
}
Example #10
Source File: DraggableItem.tsx    From querybook with Apache License 2.0 4 votes vote down vote up
export function DraggableItem<T extends { id: any }>({
    children,
    className,
    index,
    originalIndex,
    onHoverMove,
    onMove,
    draggableItemType,
    resetHoverItems,

    itemInfo,
    canDrop,
}: IDraggableItemProps<T> & { children: React.ReactNode }) {
    const ref = useRef<HTMLLIElement>(null);
    const [{ isDragging }, drag] = useDrag({
        type: draggableItemType,
        item: { type: draggableItemType, index, originalIndex, itemInfo },
        collect: (monitor) => ({
            isDragging: !!monitor.isDragging(),
        }),
        end: (_, monitor) => {
            resetHoverItems();
        },
    });
    const [, drop] = useDrop({
        accept: draggableItemType,
        canDrop,
        drop: (item: IDragItem) => {
            onMove(item.originalIndex, item.index);
        },
        hover: (item: IDragItem, monitor: DropTargetMonitor) => {
            if (!ref.current) {
                return;
            }
            if (!monitor.canDrop()) {
                return;
            }
            const dragIndex = item.index;
            const hoverIndex = index;
            if (dragIndex === hoverIndex) {
                return;
            }
            // Determine rectangle on screen
            const hoverBoundingRect = ref.current?.getBoundingClientRect();

            // Get vertical middle
            const hoverMiddleY =
                (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;

            // Determine mouse position
            const clientOffset = monitor.getClientOffset();

            // Get pixels to the top
            const hoverClientY =
                (clientOffset as XYCoord).y - hoverBoundingRect.top;

            // Only perform the move when the mouse has crossed half of the items height
            // When dragging downwards, only move when the cursor is below 50%
            // When dragging upwards, only move when the cursor is above 50%

            // Dragging downwards
            if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
                return;
            }

            // Dragging upwards
            if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
                return;
            }

            // Time to actually perform the action
            onHoverMove(dragIndex, hoverIndex);

            // Note: we're mutating the monitor item here!
            // Generally it's better to avoid mutations,
            // but it's good here for the sake of performance
            // to avoid expensive index searches.
            item.index = hoverIndex;
        },
    });
    drag(drop(ref));

    // To hide the item that is being dragged
    const opacity = isDragging ? 0 : 1;

    return (
        <li
            className={'DraggableItem ' + (className ?? '')}
            style={{ opacity }}
            ref={ref}
        >
            {children}
        </li>
    );
}
Example #11
Source File: ChartDraggableTargetContainer.tsx    From datart with Apache License 2.0 4 votes vote down vote up
ChartDraggableTargetContainer: FC<ChartDataConfigSectionProps> =
  memo(function ChartDraggableTargetContainer({
    ancestors,
    modalSize,
    config,
    translate: t = (...args) => args?.[0],
    onConfigChanged,
  }) {
    const { dataset } = useContext(ChartDatasetContext);
    const { drillOption } = useContext(ChartDrillContext);
    const { dataView, availableSourceFunctions } =
      useContext(VizDataViewContext);
    const [currentConfig, setCurrentConfig] = useState(config);
    const [showModal, contextHolder] = useFieldActionModal({
      i18nPrefix: 'viz.palette.data.enum.actionType',
    });
    const { aggregation } = useContext(ChartAggregationContext);

    useEffect(() => {
      setCurrentConfig(config);
    }, [config]);

    const [{ isOver, canDrop }, drop] = useDrop(
      () => ({
        accept: [
          CHART_DRAG_ELEMENT_TYPE.DATASET_COLUMN,
          CHART_DRAG_ELEMENT_TYPE.DATASET_COLUMN_GROUP,
          CHART_DRAG_ELEMENT_TYPE.DATA_CONFIG_COLUMN,
        ],
        drop(item: ChartDataSectionField & DragItem, monitor) {
          let items = Array.isArray(item) ? item : [item];
          let needDelete = true;

          if (
            monitor.getItemType() === CHART_DRAG_ELEMENT_TYPE.DATASET_COLUMN
          ) {
            const currentColumns: ChartDataSectionField[] = (
              currentConfig.rows || []
            ).concat(
              items.map(val => {
                let config: ChartDataSectionField = {
                  uid: uuidv4(),
                  ...val,
                  aggregate: getDefaultAggregate(val),
                };
                if (
                  val.category ===
                  ChartDataViewFieldCategory.DateLevelComputedField
                ) {
                  config.colName = `${val.colName}(${t(val.expression)})`;
                  config.expression = `${val.expression}(${val.colName})`;
                  config.field = val.colName;
                }
                return config;
              }),
            );
            updateCurrentConfigColumns(currentConfig, currentColumns, true);
          } else if (
            monitor.getItemType() ===
            CHART_DRAG_ELEMENT_TYPE.DATASET_COLUMN_GROUP
          ) {
            const hierarchyChildFields = items?.[0]?.children || [];
            const currentColumns: ChartDataSectionField[] = (
              currentConfig.rows || []
            ).concat(
              hierarchyChildFields.map(val => ({
                uid: uuidv4(),
                ...val,
                aggregate: getDefaultAggregate(val),
              })),
            );
            updateCurrentConfigColumns(currentConfig, currentColumns, true);
          } else if (
            monitor.getItemType() === CHART_DRAG_ELEMENT_TYPE.DATA_CONFIG_COLUMN
          ) {
            const originItemIndex = (currentConfig.rows || []).findIndex(
              r => r.uid === item.uid,
            );
            if (originItemIndex > -1) {
              const needRefreshData =
                currentConfig?.type === ChartDataSectionType.GROUP;
              needDelete = false;
              const currentColumns = updateBy(
                currentConfig?.rows || [],
                draft => {
                  draft.splice(originItemIndex, 1);
                  item.aggregate = getDefaultAggregate(item);
                  return draft.splice(item?.index!, 0, item);
                },
              );
              updateCurrentConfigColumns(
                currentConfig,
                currentColumns,
                needRefreshData,
              );
            } else {
              const currentColumns = updateBy(
                currentConfig?.rows || [],
                draft => {
                  item.aggregate = getDefaultAggregate(item);
                  return draft.splice(item?.index!, 0, item);
                },
              );
              updateCurrentConfigColumns(currentConfig, currentColumns);
            }
          }

          return { delete: needDelete };
        },
        canDrop: (item: ChartDataSectionField, monitor) => {
          let items = Array.isArray(item) ? item : [item];
          if (
            [CHART_DRAG_ELEMENT_TYPE.DATASET_COLUMN_GROUP].includes(
              monitor.getItemType() as any,
            ) &&
            ![
              ChartDataSectionType.GROUP,
              ChartDataSectionType.COLOR,
              ChartDataSectionType.MIXED,
            ].includes(currentConfig.type as ChartDataSectionType)
          ) {
            return false;
          }

          if (
            typeof currentConfig.actions === 'object' &&
            !items.every(val => val.type in (currentConfig.actions || {}))
          ) {
            //zh: 判断现在拖动的数据项是否可以拖动到当前容器中 en: Determine whether the currently dragged data item can be dragged into the current container
            return false;
          }

          // if (
          //   typeof currentConfig.actions === 'object' &&
          //   !(item.type in currentConfig.actions)
          // ) {
          //   return false;
          // }

          if (currentConfig.allowSameField) {
            return true;
          }

          if (
            monitor.getItemType() === CHART_DRAG_ELEMENT_TYPE.DATA_CONFIG_COLUMN
          ) {
            return true;
          }

          if (
            items[0].category ===
            ChartDataViewFieldCategory.DateLevelComputedField
          ) {
            const colNames = currentConfig.rows?.map(col => col.colName);
            return colNames
              ? colNames.every(v => !v?.includes(items[0].colName))
              : true;
          }

          const exists = currentConfig.rows?.map(col => col.colName);
          return items.every(i => !exists?.includes(i.colName));
        },
        collect: (monitor: DropTargetMonitor) => ({
          isOver: monitor.isOver(),
          canDrop: monitor.canDrop(),
        }),
      }),
      [onConfigChanged, currentConfig, dataView, dataset],
    );

    const updateCurrentConfigColumns = (
      currentConfig,
      newColumns,
      refreshDataset = false,
    ) => {
      const newCurrentConfig = updateByKey(currentConfig, 'rows', newColumns);
      setCurrentConfig(newCurrentConfig);
      onConfigChanged?.(ancestors, newCurrentConfig, refreshDataset);
    };

    const getDefaultAggregate = (item: ChartDataSectionField) => {
      if (
        currentConfig?.type === ChartDataSectionType.AGGREGATE ||
        currentConfig?.type === ChartDataSectionType.SIZE ||
        currentConfig?.type === ChartDataSectionType.INFO ||
        currentConfig?.type === ChartDataSectionType.MIXED
      ) {
        if (
          currentConfig.disableAggregate ||
          item.category === ChartDataViewFieldCategory.AggregateComputedField
        ) {
          return;
        }
        if (item.aggregate) {
          return item.aggregate;
        }

        let aggType: string = '';
        if (currentConfig?.actions instanceof Array) {
          currentConfig?.actions?.find(
            type =>
              type === ChartDataSectionFieldActionType.Aggregate ||
              type === ChartDataSectionFieldActionType.AggregateLimit,
          );
        } else if (currentConfig?.actions instanceof Object) {
          aggType = currentConfig?.actions?.[item?.type]?.find(
            type =>
              type === ChartDataSectionFieldActionType.Aggregate ||
              type === ChartDataSectionFieldActionType.AggregateLimit,
          );
        }
        if (aggType) {
          return AggregateFieldSubAggregateType?.[aggType]?.[0];
        }
      }
    };

    const onDraggableItemMove = (dragIndex: number, hoverIndex: number) => {
      const draggedItem = currentConfig.rows?.[dragIndex];
      if (draggedItem) {
        const newCurrentConfig = updateBy(currentConfig, draft => {
          const columns = draft.rows || [];
          columns.splice(dragIndex, 1);
          columns.splice(hoverIndex, 0, draggedItem);
        });
        setCurrentConfig(newCurrentConfig);
      } else {
        // const placeholder = {
        //   uid: CHARTCONFIG_FIELD_PLACEHOLDER_UID,
        //   colName: 'Placeholder',
        //   category: 'field',
        //   type: 'STRING',
        // } as any;
        // const newCurrentConfig = updateBy(currentConfig, draft => {
        //   const columns = draft.rows || [];
        //   if (dragIndex) {
        //     columns.splice(dragIndex, 1);
        //   }
        //   columns.splice(hoverIndex, 0, placeholder);
        // });
        // setCurrentConfig(newCurrentConfig);
      }
    };

    const handleOnDeleteItem = config => () => {
      if (config.uid) {
        let newCurrentConfig = updateBy(currentConfig, draft => {
          draft.rows = draft.rows?.filter(c => c.uid !== config.uid);
          if (
            config.category ===
            ChartDataViewFieldCategory.DateLevelComputedField
          ) {
            draft.replacedColName = config.colName;
          }
        });
        setCurrentConfig(newCurrentConfig);
        onConfigChanged?.(ancestors, newCurrentConfig, true);
      }
    };

    const renderDropItems = () => {
      if (
        !currentConfig.rows ||
        !currentConfig?.rows?.filter(Boolean)?.length
      ) {
        const fieldCount = reachLowerBoundCount(currentConfig?.limit, 0);
        if (fieldCount > 0) {
          return (
            <DropPlaceholder>
              {t('dropCount', undefined, { count: fieldCount })}
            </DropPlaceholder>
          );
        }
        return <DropPlaceholder>{t('drop')}</DropPlaceholder>;
      }
      return currentConfig.rows?.map((columnConfig, index) => {
        return (
          <ChartDraggableElement
            key={columnConfig.uid}
            id={columnConfig.uid}
            index={index}
            config={columnConfig}
            content={() => {
              const contentProps = {
                modalSize: modalSize,
                config: currentConfig,
                columnConfig: columnConfig,
                ancestors: ancestors,
                aggregation: aggregation,
                availableSourceFunctions,
                onConfigChanged: onConfigChanged,
                handleOpenActionModal: handleOpenActionModal,
              };
              return columnConfig.category ===
                ChartDataViewFieldCategory.Hierarchy ? (
                <ChartDraggableElementHierarchy {...contentProps} />
              ) : (
                <ChartDraggableElementField {...contentProps} />
              );
            }}
            moveCard={onDraggableItemMove}
            onDelete={handleOnDeleteItem(columnConfig)}
          ></ChartDraggableElement>
        );
      });
    };

    const renderDrillFilters = () => {
      if (currentConfig?.type !== ChartDataSectionType.FILTER) {
        return;
      }
      return getDillConditions()?.map(drill => {
        const field = drill.field;
        return (
          <StyledDillFilter type={field.type}>
            {getColumnRenderName(field)}
          </StyledDillFilter>
        );
      });
    };

    const getDillConditions = () => {
      return drillOption
        ?.getAllDrillDownFields()
        ?.filter(drill => Boolean(drill?.condition));
    };

    const handleFieldConfigChanged = (
      columnUid: string,
      fieldConfig: ChartDataSectionField,
      needRefresh?: boolean,
    ) => {
      if (!fieldConfig) {
        return;
      }
      const newConfig = updateDataConfigByField(
        columnUid,
        currentConfig,
        fieldConfig,
      );
      onConfigChanged?.(ancestors, newConfig, needRefresh);
    };

    const handleOpenActionModal =
      (uid: string) =>
      (actionType: ValueOf<typeof ChartDataSectionFieldActionType>) => {
        (showModal as Function)(
          uid,
          actionType,
          currentConfig,
          handleFieldConfigChanged,
          dataset,
          dataView,
          modalSize,
          aggregation,
        );
      };

    return (
      <StyledContainer ref={drop} isOver={isOver} canDrop={canDrop}>
        {renderDropItems()}
        {renderDrillFilters()}
        {contextHolder}
      </StyledContainer>
    );
  })
Example #12
Source File: DraggableItem.tsx    From datart with Apache License 2.0 4 votes vote down vote up
DraggableItem: FC<DraggableItemProps> = ({
  id,
  text,
  index,
  moveCard,
  onDrop,
}) => {
  const ref = useRef<HTMLDivElement>(null);
  const [{ handlerId }, drop] = useDrop({
    accept: 'ItemTypes.Item',
    collect(monitor) {
      return {
        handlerId: monitor.getHandlerId(),
      };
    },
    hover(item: Item, monitor: DropTargetMonitor) {
      if (!ref.current) {
        return;
      }
      const dragIndex = item.index;
      const hoverIndex = index;

      // Don't replace items with themselves
      if (dragIndex === hoverIndex) {
        return;
      }

      // Determine rectangle on screen
      const hoverBoundingRect = ref.current?.getBoundingClientRect();

      // Get vertical middle
      const hoverMiddleY =
        (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;

      // Determine mouse position
      const clientOffset = monitor.getClientOffset();

      // Get pixels to the top
      const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top;

      // Only perform the move when the mouse has crossed half of the items height
      // When dragging downwards, only move when the cursor is below 50%
      // When dragging upwards, only move when the cursor is above 50%

      // Dragging downwards
      if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
        return;
      }

      // Dragging upwards
      if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
        return;
      }

      // Time to actually perform the action
      moveCard && moveCard(dragIndex, hoverIndex);

      // Note: we're mutating the monitor item here!
      // Generally it's better to avoid mutations,
      // but it's good here for the sake of performance
      // to avoid expensive index searches.
      item.index = hoverIndex;
    },
    drop(item: Item, monitor: DropTargetMonitor) {
      onDrop && onDrop(item.id, item.index);
    },
  });

  const [{ isDragging }, drag] = useDrag({
    type: 'ItemTypes.Item',
    item: () => {
      return { id, index };
    },
    collect: (monitor: any) => ({
      isDragging: monitor.isDragging(),
    }),
  });

  const opacity = isDragging ? 0 : 1;
  drag(drop(ref));
  return (
    <div ref={ref} style={{ ...style, opacity }} data-handler-id={handlerId}>
      {text}
    </div>
  );
}
Example #13
Source File: NameItem.tsx    From datart with Apache License 2.0 4 votes vote down vote up
NameItem: React.FC<NameItemProps> = ({
  index,
  card,
  zIndex,
  moveCard,
  moveEnd,
  widgetType,
}) => {
  const widget = useContext(WidgetContext);
  const ItemRef = useRef<HTMLDivElement>(null);
  const [{ handlerId }, drop] = useDrop({
    accept: 'WidgetNameListDnd',
    collect(monitor) {
      return {
        handlerId: monitor.getHandlerId(),
      };
    },
    hover(item: DragItem, monitor: DropTargetMonitor) {
      if (!ItemRef.current) {
        return;
      }
      const dragIndex = item.index;
      const hoverIndex = index;

      const dragZIndex = item.zIndex;
      const hoverZIndex = card.index;
      const hoverSelected = card.selected;
      // Don't replace items with themselves
      if (dragIndex === hoverIndex) {
        return;
      }

      // Determine rectangle on screen
      const hoverBoundingRect = ItemRef.current?.getBoundingClientRect();

      // Get vertical middle
      const hoverMiddleY =
        (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;

      // Determine mouse position
      const clientOffset = monitor.getClientOffset();

      // Get pixels to the top
      const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top;

      // Only perform the move when the mouse has crossed half of the items height
      // When dragging downwards, only move when the cursor is below 50%
      // When dragging upwards, only move when the cursor is above 50%

      // Dragging downwards
      if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
        return;
      }

      // Dragging upwards
      if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
        return;
      }

      // Time to actually perform the action
      moveCard(dragIndex, hoverIndex, dragZIndex, hoverZIndex);

      // Note: we're mutating the monitor item here!
      // Generally it's better to avoid mutations,
      // but it's good here for the sake of performance
      // to avoid expensive index searches.
      item.index = hoverIndex;
      item.zIndex = hoverZIndex;
      item.selected = hoverSelected;
    },
  });

  const [{ isDragging }, drag] = useDrag({
    type: 'WidgetNameListDnd',
    item: () => {
      return { id: card.id, index, zIndex, selected: card.selected };
    },
    collect: (monitor: any) => ({
      isDragging: monitor.isDragging(),
    }),
    end() {
      moveEnd();
    },
  });
  const opacity = isDragging ? 0.1 : 1;
  // 使用 drag 和 drop 包装 ref
  drag(drop(ItemRef));
  const dispatch = useDispatch();
  const selectWidget = useCallback(
    (e: React.MouseEvent<HTMLDivElement>) => {
      e.stopPropagation();
      let newSelected = !card.selected;
      if (card.selected) {
        newSelected = card.selected;
      }

      dispatch(
        editWidgetInfoActions.selectWidget({
          multipleKey: e.shiftKey,
          id: card.id,
          selected: newSelected,
        }),
      );
    },
    [card, dispatch],
  );

  return (
    <ItemWrap
      selected={card.selected}
      onMouseDown={selectWidget}
      onClick={e => e.stopPropagation()}
    >
      <div
        className={classNames('name-item', {
          'selected-Item': card.selected,
        })}
        ref={ItemRef}
        data-handler-id={handlerId}
        style={{ opacity }}
      >
        <span className="name" title={card.name || 'untitled-widget'}>
          {card.name || 'untitled-widget'}
        </span>

        <WidgetActionDropdown widget={widget} />
      </div>
    </ItemWrap>
  );
}
Example #14
Source File: ThumbnailItem.tsx    From datart with Apache License 2.0 4 votes vote down vote up
ThumbnailItem: React.FC<IProps> = ({
  page,
  index,
  selected,
  moveCard,
  moveEnd,
}) => {
  const dispatch = useDispatch();
  const ItemRef = useRef<HTMLDivElement>(null);
  const dashboard = useSelector((state: { board: BoardState }) =>
    makeSelectBoardConfigById()(state, page.relId),
  );
  useEffect(() => {
    try {
      const { thumbnail, name } = dashboard as Dashboard;
      dispatch(
        storyActions.updateStoryPageNameAndThumbnail({
          storyId: page.storyId,
          pageId: page.id,
          name,
          thumbnail,
        }),
      );
    } catch (error) {}
    // storyActions.updateStoryPageNameAndThumbnail
  }, [dashboard, dispatch, page.id, page.storyId]);
  const [{ handlerId }, drop] = useDrop({
    accept: 'storyBoardListDnd',
    collect(monitor) {
      return {
        handlerId: monitor.getHandlerId(),
      };
    },
    hover(item: DragItem, monitor: DropTargetMonitor) {
      if (!ItemRef.current) {
        return;
      }
      const dragIndex = item.index;
      const hoverIndex = page.index;
      const hoverSelected = item.selected;
      // const dragId = item.id;
      // Don't replace items with themselves
      if (dragIndex === hoverIndex) {
        return;
      }

      // Determine rectangle on screen
      const hoverBoundingRect = ItemRef.current?.getBoundingClientRect();

      // Get vertical middle
      const hoverMiddleY =
        (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;

      // Determine mouse position
      const clientOffset = monitor.getClientOffset();

      // Get pixels to the top
      const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top;

      // Only perform the move when the mouse has crossed half of the items height
      // When dragging downwards, only move when the cursor is below 50%
      // When dragging upwards, only move when the cursor is above 50%

      // Dragging downwards
      if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
        return;
      }

      // Dragging upwards
      if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
        return;
      }

      // Time to actually perform the action
      moveCard(item.id, page.id);

      // Note: we're mutating the monitor item here!
      // Generally it's better to avoid mutations,
      // but it's good here for the sake of performance
      // to avoid expensive index searches.
      item.index = hoverIndex;
      item.selected = hoverSelected;
    },
  });

  const [{ isDragging }, drag] = useDrag({
    type: 'storyBoardListDnd',
    item: () => {
      return {
        id: page.id,
        index: page.index,
        selected: selected,
      };
    },
    collect: (monitor: any) => ({
      isDragging: monitor.isDragging(),
    }),
    end() {
      moveEnd();
    },
  });
  const opacity = isDragging ? 0.1 : 1;
  // 使用 drag 和 drop 包装 ref
  drag(drop(ItemRef));

  return (
    <ItemWrap selected={selected}>
      <div
        className="item"
        ref={ItemRef}
        data-handler-id={handlerId}
        style={{ opacity }}
      >
        <p>{index}</p>
        <h4>{page.name}</h4>
      </div>
    </ItemWrap>
  );
}
Example #15
Source File: useDraggable.ts    From ui-schema with MIT License 4 votes vote down vote up
useDraggable = <C extends HTMLElement = HTMLElement, S extends ItemSpec = ItemSpec>(
    {
        item,
        allowedTypes,
        scope: localScope,
        refRoot,
    }: {
        item: S
        allowedTypes: string[] | List<string> | undefined
        scope?: string
        refRoot: React.MutableRefObject<C | null>
    }
): {
    isOver: boolean
    canDrop: boolean
    drop: ConnectDropTarget
    isDragging: boolean
    drag: ConnectDragSource
    preview: ConnectDragPreview
    setDisableDrag: React.Dispatch<React.SetStateAction<boolean>>
    canDrag: boolean
} => {
    const [disableDrag, setDisableDrag] = React.useState<boolean>(false)
    const {onMove, scope} = useKitDnd<C>()

    const [{isOver, canDrop}, drop] = useDrop<S, S, { handlerId: (Identifier | null), isOver: boolean, canDrop: boolean }>(() => ({
        accept: localScope ? localScope : (scope ? '_' + scope : '_') as string,
        options: {},
        collect: (monitor: DropTargetMonitor<C>) => ({
            isOver: monitor.isOver({shallow: true}),
            canDrop: monitor.canDrop(),
            handlerId: monitor.getHandlerId(),
        }),
        canDrop: (fromItem: S, monitor: DropTargetMonitor<C>) => {
            if (!monitor.isOver({shallow: true})) {
                return false
            }
            return (
                !fromItem.dataKeys.push(fromItem.index).equals(item.dataKeys.push(item.index)) &&
                fromItem.id !== item.id
            ) && (
                !allowedTypes ||
                allowedTypes.indexOf(fromItem.type) !== -1
            )
        },
        hover(fromItem: S, monitor: DropTargetMonitor<C>) {
            if (monitor.canDrop()) {
                onMove({
                    targetElement: refRoot.current as C,
                    toItem: item as unknown as S,
                    fromItem: fromItem as unknown as S,
                    monitor,
                })
            }
        },
    }), [item, localScope, scope, allowedTypes, refRoot])

    const [{canDrag, isDragging}, drag, preview] = useDrag<S, S, { isDragging: boolean, canDrag: boolean }>(() => ({
        type: localScope ? localScope : (scope ? '_' + scope : '_') as string,
        item: {...item},
        collect: (monitor: DragSourceMonitor<S>) => ({
            isDragging: monitor.isDragging(),
            canDrag: monitor.canDrag(),
        }),
        canDrag: () => {
            return !disableDrag
        },
        isDragging: (monitor: DragSourceMonitor<S>) => {
            const {index, dataKeys} = item
            const tmpItem = monitor.getItem() as S
            if (!tmpItem) return false
            let itemDataKeys = tmpItem.dataKeys as List<number>
            if (!List.isList(itemDataKeys)) {
                itemDataKeys = List(itemDataKeys) as List<number>
            }
            return tmpItem.dataKeys.push(tmpItem.index).equals(dataKeys.push(index)) &&
                itemDataKeys.equals(!List.isList(dataKeys) ? List(dataKeys) : dataKeys)
        },
    }), [
        item, localScope, scope, disableDrag,
    ])

    return {
        isOver, canDrop, drop,
        isDragging, drag, preview,
        setDisableDrag,
        // todo: somehow `canDrag` isn't `false` when it is internally `false`
        canDrag: canDrag && !disableDrag,
    }
}