React-dnd: Conectar monitor.getClientOffset a un DropTarget no actualiza los accesorios

Creado en 3 jun. 2015  ·  22Comentarios  ·  Fuente: react-dnd/react-dnd

Creé un DropTarget y conecté getClientOffset de la siguiente manera:

targetCollect = function (connect, monitor) {
  return {
    connectDropTarget: connect.dropTarget(),
    isOver: monitor.isOver(),
    clientOffset: monitor.getClientOffset()
  }
}

Sin embargo, al mover el arrastre dentro del componente, el objetivo no recibe accesorios actualizados con cada movimiento del mouse (solo al entrar y salir). Sin embargo, se llama al hover de la especificación de destino repetidamente. ¿Hay alguna razón por la que la propiedad clientOffset no se inyecta cada vez que se cambia?

bug wontfix

Comentario más útil

+1. Me encontré con esto y perdí mucho tiempo tratando de entender por qué getClientOffset () funciona bien en canDrop (), que se llama repetidamente ... - pero no hay forma de reenviar a mi control 'dónde' sobre el elemento en el que estamos. Necesito poder insertar arriba y debajo de un árbol, no solo "en" él. canDrop () no tiene forma de encontrar que me permita decirle a mi componente cuál de los muchos "tipos" de gotas tiene el nodo para que mi control pueda volver a mostrarse en consecuencia. Cambiar el accesorio en canDrop resultó en un error. La suscripción a onMouseMove no funciona durante las operaciones de arrastrar y soltar. Esto ha sido una gran pérdida de tiempo hasta ahora ... cualquier ayuda sería apreciada.

@ibash , ¿te importaría publicar un resumen de lo que hiciste para resolver tu problema?

Todos 22 comentarios

Oh, buen punto. Así es, no anticipé este uso. Actualmente, la única forma de optar por el seguimiento de la compensación del cliente es utilizar DragLayer . De lo contrario, por razones de rendimiento, solo actualiza los accesorios si algo relacionado con el _drop target_ ha cambiado.

Yo diría que es un problema de API y puede haber varias soluciones:

  • Prohibir entrar en getClientOffset() y métodos similares desde dentro de la función collect y decirle que use DragLayer lugar (fácil pero tonto);
  • Averigüe automáticamente que el usuario desea realizar un seguimiento del desplazamiento y suscribirse a los cambios de desplazamiento si este es el caso (más difícil pero más consistente).

Creo que aceptaría un PR haciendo la segunda opción. Puede verificar la implementación de DragLayer para ver cómo se suscribe a los cambios de compensación.

Busqué en la segunda opción, pero es complicado ya que recopilar es una función, por lo que no es posible / fácil averiguar qué funciones de monitor se pueden llamar.

Mi caso de uso es arrastrar widgets que se colocan en su ubicación, posiblemente desplazando a otros widgets de una manera relativamente compleja (piense en arrastrar iconos de programas en un Android). Es realmente el objetivo el que tiene que reorganizarse dependiendo de la ubicación exacta de desplazamiento, mientras que el arrastre en sí está perfectamente bien como instantánea HTML5. Puedo hacerlo con el desplazamiento de la especificación de destino actualizando el estado del componente de destino. Así que dejaré esto por ahora y lo solucionaré. Gracias por una biblioteca increíble; Tengo algo decente trabajando en muy pocas horas de trabajo.

Dejemos esto abierto por ahora. Es lo mismo que # 172: no anticipé que las personas usaran deltas dentro de canDrop o la función de recopilación. No voy a arreglar esto de inmediato, pero puede que después de liberarlo en un mes más o menos.

También me encontré con este problema. ¿Qué piensa sobre permitir que los clientes pasen una bandera a la especificación DropTarget para suscribirse a los cambios de compensación? Es una "trampa" más para el usuario, pero es más simple que suscribirse / cancelar la suscripción manualmente a las compensaciones.

¿No podría quizás usar RxJS-DOM para observar el evento dragover en el elemento y actualizar el estado con las coordenadas del mouse? Quizás una solución hacky.

  componentDidMount() {
    this._events.dragOver = DOM.fromEvent(findDOMNode(this.refs.grid), 'dragover')
                            .throttle(200)
                            .subscribe(this.getMouseCoords)
  }

Terminé agarrando el monitor y suscribiéndome a cambios de compensación como el DragLayer. También terminé aprovechando el registro interno en algunos lugares. No creo que lo que quiero hacer sea _demasiado_ inusual, por lo que, en mi opinión, podría solucionarse con simples cambios de API.

¿Ha salido algo de esto? Me gustaría monitorear el desplazamiento del cliente en mi DropTarget en lugar de en la capa de arrastre personalizada porque siento que es más fácil cambiar la estructura de DropTarget cuando puedo acceder a la ubicación de desplazamiento desde DropTarget en lugar de la capa de arrastre personalizada.

+1 Me gustaría hacer exactamente lo que @jchristman está tratando de hacer.

+1. Me encontré con esto y perdí mucho tiempo tratando de entender por qué getClientOffset () funciona bien en canDrop (), que se llama repetidamente ... - pero no hay forma de reenviar a mi control 'dónde' sobre el elemento en el que estamos. Necesito poder insertar arriba y debajo de un árbol, no solo "en" él. canDrop () no tiene forma de encontrar que me permita decirle a mi componente cuál de los muchos "tipos" de gotas tiene el nodo para que mi control pueda volver a mostrarse en consecuencia. Cambiar el accesorio en canDrop resultó en un error. La suscripción a onMouseMove no funciona durante las operaciones de arrastrar y soltar. Esto ha sido una gran pérdida de tiempo hasta ahora ... cualquier ayuda sería apreciada.

@ibash , ¿te importaría publicar un resumen de lo que hiciste para resolver tu problema?

Cambiar a usar el hover finalmente lo hizo por mí.

`hover: (props: MyProps, monitor: DropTargetMonitor, componente: ReportItemRow) => {
if (componente! = nulo) {
const clientOffset = monitor.getClientOffset ();

        // Is the dragging node dragged above/below us as opposed to "on" us?

        const elementRect = document.elementFromPoint(clientOffset.x, clientOffset.y).getBoundingClientRect();

        const percentageY = (clientOffset.y - elementRect.top) / elementRect.height;

        // Divide the box up into .25 .5 .25
        const insertDragAndDropMagnetPercent = .25;

        if (insertDragAndDropMagnetPercent >= percentageY) {
            component.setState({ dropTarget: "above" });
        }
        else if (1 - insertDragAndDropMagnetPercent >= percentageY) {
            component.setState({ dropTarget: "inside" });
        }
        else {
            component.setState({ dropTarget: "below" });
        }
    }
},`

@ noah79 , mientras espera a que

         _____
         _____| <-- drop div1 (above treenode1/below top of list)
treenode1_____| <-- drop div2 (on treenode1)
         _____| <-- drop div3 (below treenode1/above treenode2)
treenode2_____| <-- drop div4 (on treenode 2)
         _____| <-- drop div5 (below treenode2/above bottom of list)

Termina con 2n + 1 destinos de caída, donde n es el número de elementos en su lista. Luego, en función de cuál de los divs se está desplazando, puede cambiar el aspecto de la lista de árbol. He hecho algo muy similar a esto para evitar que no pueda acceder a getClientOffset () por ahora, y no noté un impacto en el rendimiento. La altura de cada div de caída debe ser la mitad de la altura de la línea treenode1 y la primera div de caída debe ubicarse 1/4 de la altura de la línea treenode1 más alta que la primera línea. Es decir, su CSS debería verse así:

.dropdiv-container {
    position: absolute;
    top: -0.25em; /* If the top of this container is aligned with the top of the treenode list initially */
}
.dropdiv {
    height: 0.5em; /* If the height of treenode1 line is 1em with no padding */
}
<div className='dropdiv-container'>
    { renderDropTargets(2 * listLength + 1) }
</div>

¿Tiene sentido?

Aquí hay una idea general de cómo resolví esto. El truco es simplemente alcanzar el
react-dnd internos y utilice el monitor global directamente. Echa un vistazo al
DragDropMonitor.js fuente para tener una idea de qué métodos están disponibles.
ref: https://github.com/gaearon/dnd-core/blob/master/src/DragDropMonitor.js

Tendrás que disculpar el coffeescript :)

  # in the component being dragged, get access to the dragDropManager by adding
  # it to the contextTypes
  #
  # dragDropManager is an instance of this:
  # https://github.com/gaearon/dnd-core/blob/master/src/DragDropManager.js

  <strong i="11">@contextTypes</strong>: {
    dragDropManager: React.PropTypes.object
  }

  # because we can receive events after the component is unmounted, we need to
  # keep track of whether the component is mounted manually.
  #
  # <strong i="12">@_monitor</strong> is what lets us access all the nice internal state - it is an instance of this:
  # https://github.com/gaearon/dnd-core/blob/master/src/DragDropMonitor.js

  componentWillMount: () =>
    <strong i="13">@_isMounted</strong> = true
    <strong i="14">@_monitor</strong> = @context.dragDropManager.getMonitor()

    <strong i="15">@_unsubscribeToStateChange</strong> = @_monitor.subscribeToStateChange(@_onStateChange)
    <strong i="16">@_unsubscribeToOffsetChange</strong> = @_monitor.subscribeToOffsetChange(@_onOffsetChange)

  componentWillUnmount: () =>
    <strong i="17">@_isMounted</strong> = false
    <strong i="18">@_monitor</strong> = null

    @_unsubscribeToStateChange()
    @_unsubscribeToOffsetChange()

  # we can access dragging / dropping state by accessing the monitor 

  _onStateChange: () =>
    return unless <strong i="19">@_isMounted</strong>

    # When we stop dragging reset the counter state and hide all cursors.
    if <strong i="20">@_previousIsDragging</strong> && !@_monitor.isDragging()
      console.log('no longer dragging')
    <strong i="21">@_previousIsDragging</strong> = @_monitor.isDragging()

  _onOffsetChange: () =>
    return unless <strong i="22">@_isMounted</strong>

    # mouse is the x/y coordinates
    mouse = @_monitor.getClientOffset()

    # item is the drag item
    item = @_monitor.getItem()

    # if you want to check if a dragged item is over a target, you need the
    # targetId -- in the DropTarget wrapper you can pass it in like:
    #
    #   (connect, monitor) ->
    #     {
    #       targetId: monitor.targetId,
    #       connectDropTarget: connect.dropTarget()
    #     }
    #
    # and then you can use it like below

    @_monitor.isOverTarget(@props.targetId))

¿Esto tiene sentido? Si no puedo agregar más detalles

También me encontré con esto hoy y perdí algunas horas. ¿Qué tal si deja una nota al respecto en la documentación en DropTarget -> The Collecting Function? Eso al menos evitaría frustraciones a algunos otros.

Por cierto. Otro truco realmente feo en torno a este problema puede ser enviar las coordenadas _ como estado_ desde dropTarget.hover() :

const dropTarget = {
    hover(props, monitor, component) {
        // HACK! Since there is an open bug in react-dnd, making it impossible
        // to get the current client offset through the collect function as the
        // user moves the mouse, we do this awful hack and set the state (!!)
        // on the component from here outside the component.
        component.setState({
            currentDragOffset: monitor.getClientOffset(),
        });
    },
    drop() { /* ... */ },
};

Por supuesto, hay toneladas de formas más correctas de hacer esto que evitarían el abuso de setState de esa manera. Pero al menos este pequeño truco está muy condensado y puede funcionar hasta que este problema se solucione finalmente. Le permite solucionar el error sin cambiar otros componentes y / o archivos y sin depender de los componentes internos de la biblioteca.

Editar: esto es básicamente lo mismo que hace noah79, simplemente no leí su código antes de ahora.

¿Realmente no hay forma de colocar el mouse en posición de caída? Mi componente de destino no necesita saberlo, así que setState no es una opción para mí. Solo necesito las coordenadas para activar una acción de reducción.

Por favor ignore mi último mensaje. Ahora veo que drop y endDrag son dos funciones diferentes, y obtener la posición del cursor dentro de drop funciona como se esperaba. :)

Según mis pruebas, react-dnd consume los eventos de movimiento del mouse tan pronto como un elemento se arrastra sobre el objetivo. Si fuera posible que html-backend no consumiera este evento, entonces el componente de destino podría simplemente colocar un oyente mousemove normal en su dom condicionalmente cuando la propiedad isOver se establece en true. A partir de entonces, este oyente podría tomar la posición del mouse (de la manera normal) cuando se arrastra algo sobre él. Conseguí que esto funcionara temporalmente usando setState del método drag () reaccionar está dando advertencias sobre el estado de configuración en medio de una transición de renderizado.

Esto también me hizo tropezar. +1 para una solución de API o al menos algo en las preguntas frecuentes.

¿Hay algún plan para apoyar esto?
Tengo una idea para una solución alternativa libre de piratería: haga un contenedor para DropTarget que también envuelva hover para activar otra llamada a la función de recopilación y una nueva representación si la función de recopilación devuelve nuevos valores .

+1 para este problema. El caso de uso que tengo es que tengo varios destinos de caída que están muy cerca unos de otros. Parece extraño que al flotar, el objetivo de caída adecuado se resalte, pero en realidad caiga sobre un objetivo más bajo dependiendo de la dirección desde la que haya flotado.

Me encontré con este problema al investigar por qué las compensaciones no se actualizan, parece que todavía necesita una solución.
Tomé la idea de @ jedwards1211 y esto es lo que se me ocurrió. El HOC tiene principalmente la misma interfaz que la vainilla DropTarget , pero acepta collectRapidly en sus opciones, que es una lista de accesorios para consultar a través de la función de recopilación + transmitir en cada evento de desplazamiento. Pasar todos los accesorios indiscriminadamente de la función de recopilación causó algunas rarezas y no podemos pasar connect al recopilador en absoluto, por lo que hay una protección contra consultar connectDropTarget "rápidamente".

Entonces, en lugar de DropTarget(types, target, collect) , usaría RapidFireDropTarget(types, target, collect, {collectRapidly: ['offset']}) , donde offset es presumiblemente algo que recibe su valor de la familia de funciones monitor.getOffset .

import React from 'react';
import {DropTarget} from 'react-dnd';
import pick from 'lodash/pick';

function RapidFireDropTarget(types, spec, collect, options={}) {
  let collectProps = {};
  const prevHover = spec.hover;
  const {collectRapidly = []} = options;

  const dummyConnect = {
    dropTarget: () => () => {
      throw new Error('Rapidly collected props cannot include results from `connect`');
    }
  };

  const WrappedComponent = Component => {
    return class extends React.Component {
      render() {
        return <Component {...this.props} {...collectProps} />;
      }
    };
  };

  spec.hover = (props, monitor, component) => {
    const allCollectProps = collect(dummyConnect, monitor);

    collectProps = pick(allCollectProps, collectRapidly);
    component.forceUpdate();

    if (prevHover instanceof Function) {
      prevHover(props, monitor, component);
    }
  }

  return (Component) => {
    return DropTarget(types, spec, collect, options)(WrappedComponent(Component));
  };
}

export default RapidFireDropTarget;

Este problema se ha marcado automáticamente como obsoleto porque no ha tenido actividad reciente. Se cerrará si no se produce más actividad. Gracias por sus aportaciones.

¿Fue útil esta página
0 / 5 - 0 calificaciones