Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does react-dnd's connectDragPreview() work?

I've been reviewing the documentation and github issues and connectDragPreview still remains a mystery to me. I would like to define a custom preview for how the item appears when it is being dragged. It should function just as the example here works with the horse image appearing on drag. Here is my code:

export const tokenCollector: DragSourceCollector = (connect, monitor) => {
  return {
    connectDragSource: connect.dragSource(),
    connectDragPreview: connect.dragPreview(),
    isDragging: monitor.isDragging()
  };
};

class TokenClass extends React.Component<TokenProps> {
  componentDidMount() {
    const { connectDragPreview } = this.props;
    const img = new Image();
    img.src = '<encoded image>';
    img.onload = () => connectDragPreview(<div>{img}</div>);
  }
  render() {
    const { connectDragSource, isDragging, children } = this.props;
    return connectDragSource(
      <div style={{ opacity: isDragging ? 0.5 : 1 }}>
        {children}
      </div>
    );
  }
}
const dragSource = DragSource(DropType.Token, tokenSpec, tokenCollector)(TokenClass);
export { dragSource as Token };

The standard preview appears with this code.

I then tried to wrap my connectDragSource in my component's render() method with connectDragPreview, but that only appears to change the drag source where it was picked up from, not how it appears as it's being dragged.

How can I specify the markup that should be used as the dragging visual?

like image 498
im1dermike Avatar asked Feb 15 '18 15:02

im1dermike


1 Answers

Its seems you are using react-dnd-html5-backend. React-dnd-html5-backend provides connectDragSource: https://github.com/react-dnd/react-dnd-html5-backend/blob/85fc956c58a5d1a9fde2fca3c7fca9115a7c87df/src/HTML5Backend.js#L100

And react-dnd-html5-backend works only with html5 drag&drop events: https://github.com/react-dnd/react-dnd-html5-backend/blob/85fc956c58a5d1a9fde2fca3c7fca9115a7c87df/src/HTML5Backend.js#L65-L74

So, you can set up only an Image to you drag effect: https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/setDragImage

You can simply change your img.onload callback:

export const tokenCollector: DragSourceCollector = (connect, monitor) => {
  return {
    connectDragSource: connect.dragSource(),
    connectDragPreview: connect.dragPreview(),
    isDragging: monitor.isDragging()
  };
};

class TokenClass extends React.Component<TokenProps> {
  componentDidMount() {
    const { connectDragPreview } = this.props;
    const img = new Image();
    img.src = 'base64-image';
    img.onload = () => connectDragPreview(img);
  }
  render() {
    const { connectDragSource, isDragging, children } = this.props;
    return connectDragSource(
      <div style={{ opacity: isDragging ? 0.5 : 1 }}>
        {children}
      </div>
    );
  }
}
const dragSource = DragSource(DropType.Token, tokenSpec, tokenCollector)(TokenClass);
export { dragSource as Token };

In case you want to use custom markup, you can call connectDragPreview with getEmptyImage argument, and then, implement CustomDragLayer.

Your TokenClass:

import React, { Component } from "react";
import { DragSource } from "react-dnd";
import logo from "./logo.svg";
import { getEmptyImage } from "react-dnd-html5-backend";

export const tokenCollector = (connect, monitor) => {
  return {
    connectDragSource: connect.dragSource(),
    connectDragPreview: connect.dragPreview(),
    isDragging: monitor.isDragging()
  };
};

class TokenClass extends React.Component {
  componentDidMount() {
    const { connectDragPreview } = this.props;

    // Use empty image as a drag preview so browsers don't draw it
    // and we can draw whatever we want on the custom drag layer instead.
    connectDragPreview(getEmptyImage());
  }
  render() {
    const { connectDragSource, isDragging, children } = this.props;
    return connectDragSource(
      <div style={{ opacity: isDragging ? 0.5 : 1, backgroundColor: "green" }}>
        {children}
      </div>
    );
  }
}

const tokenSpec = {
  beginDrag() {
    return {};
  }
};

export default DragSource("DropType.Markup", tokenSpec, tokenCollector)(
  TokenClass
);

CustomDragLayer implementation:

import React, { Component } from "react";
import { DragLayer } from "react-dnd";

function getItemStyles(props) {
  const { initialOffset, currentOffset } = props;
  if (!initialOffset || !currentOffset) {
    return {
      display: "none"
    };
  }

  let { x, y } = currentOffset;

  const transform = `translate(${x}px, ${y}px)`;
  return {
    transform,
    WebkitTransform: transform
  };
}

class CustomDragLayer extends Component {
  render() {
    const isDragging = this.props.isDragging;
    if (!isDragging) {
      return null;
    }

    // You can specify acceptable type:
    if (this.props.itemType !== "DropType.Markup") {
      return null;
    }

    // The component will work only when dragging
    return (
      <div>
        <div style={getItemStyles(this.props)}>Custom drag layer!</div>
      </div>
    );
  }
}

function collect(monitor) {
  return {
    item: monitor.getItem(),
    itemType: monitor.getItemType(),
    initialOffset: monitor.getInitialSourceClientOffset(),
    currentOffset: monitor.getSourceClientOffset(),
    isDragging: monitor.isDragging()
  };
}

export default DragLayer(collect)(CustomDragLayer); // eslint-disable-line new-cap

Also, I uploaded to github solution which works with both an image and a markup. You can upload them and run: yarn install yarn start Solution: https://github.com/xnimorz/stackoverflow-example/tree/dragAndDrop

like image 92
Nik Avatar answered Oct 19 '22 20:10

Nik