React-dnd: Menghubungkan monitor.getClientOffset ke DropTarget tidak menyegarkan alat peraga

Dibuat pada 3 Jun 2015  ·  22Komentar  ·  Sumber: react-dnd/react-dnd

Saya membuat DropTarget dan menghubungkan getClientOffset sebagai berikut:

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

Namun, saat memindahkan seret di dalam komponen, target tidak menerima properti yang diperbarui dengan setiap gerakan mouse (hanya saat masuk dan keluar). Namun, hover _is_ spesifikasi target dipanggil berulang kali. Apakah ada alasan bahwa prop clientOffset tidak disuntikkan setiap kali diubah?

bug wontfix

Komentar yang paling membantu

+1. Mengalami hal ini dan membuang banyak waktu untuk mencoba memahami mengapa getClientOffset () berfungsi dengan baik di canDrop () - yang dipanggil berulang kali ... - tetapi tidak ada cara untuk meneruskan ke kendali saya 'di mana' atas item kita. Saya harus bisa menyisipkan di atas dan di bawah treenode, bukan hanya "di" saja. canDrop () tidak memiliki cara yang dapat saya temukan yang memungkinkan saya benar-benar memberi tahu komponen saya yang mana dari banyak "jenis" drop yang dimiliki node sehingga kontrol saya dapat ditampilkan kembali sesuai. Mengubah prop di canDrop mengakibatkan kesalahan. Berlangganan onMouseMove tidak berfungsi selama operasi seret dan lepas. Sejauh ini hal ini membuang-buang waktu ... bantuan apa pun akan sangat kami hargai.

@ibash , maukah Anda memposting inti dari apa yang Anda lakukan untuk menyelesaikan masalah Anda?

Semua 22 komentar

Oh, poin yang bagus. Benar, saya tidak mengantisipasi penggunaan ini. Saat ini, satu-satunya cara untuk ikut serta melacak offset klien adalah dengan menggunakan DragLayer . Sebaliknya, untuk alasan kinerja, itu hanya memperbarui props jika sesuatu yang berhubungan dengan _drop target itu sendiri_ telah berubah.

Menurut saya ini masalah API, dan mungkin ada beberapa solusi:

  • Larang menjangkau ke getClientOffset() dan metode serupa dari dalam collect fungsi dan suruh untuk menggunakan DragLayer sebagai gantinya (mudah tapi bodoh);
  • Secara otomatis mencari tahu bahwa pengguna ingin melacak offset dan berlangganan perubahan offset jika memang demikian (lebih sulit tetapi lebih konsisten).

Saya pikir saya akan menerima PR melakukan opsi kedua. Anda dapat memeriksa implementasi DragLayer untuk melihat bagaimana itu berlangganan perubahan offset.

Saya melihat ke opsi kedua, tetapi itu rumit karena kumpulkan adalah sebuah fungsi, jadi tidak mungkin / mudah untuk mengetahui fungsi monitor mana yang mungkin dipanggil.

Kasus penggunaan saya adalah menyeret sekitar widget yang diambil ke lokasi, mungkin menggeser widget lain dengan cara yang relatif rumit (pikirkan menyeret ikon program di Android). Ini benar-benar target yang harus mengatur ulang dirinya sendiri tergantung pada lokasi hover yang tepat, sementara drag itu sendiri sangat baik sebagai snapshot HTML5. Saya dapat melakukannya dengan hover spesifikasi target yang memperbarui status komponen target. Jadi saya akan meninggalkan ini untuk saat ini dan mengatasinya. Terima kasih untuk perpustakaan yang luar biasa; Saya memiliki pekerjaan yang layak dalam beberapa jam kerja.

Biarkan ini tetap terbuka untuk saat ini. Ini sama dengan # 172: Saya tidak mengantisipasi orang menggunakan delta di dalam canDrop atau fungsi pengumpulan. Saya tidak akan segera memperbaikinya, tetapi mungkin setelah saya bebas dalam sebulan atau lebih.

Saya juga mengalami masalah ini. Apa pendapat Anda tentang membiarkan klien mengirimkan tanda ke spesifikasi DropTarget untuk berlangganan perubahan offset? Itu satu lagi "gotcha" bagi pengguna, tetapi lebih sederhana daripada berlangganan / berhenti berlangganan offset secara manual.

Mungkinkah Anda tidak dapat menggunakan RxJS-DOM untuk mengamati kejadian dragover pada elemen dan memperbarui status dengan koordinat mouse? Solusi hacky mungkin.

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

Saya akhirnya mengambil monitor dan berlangganan perubahan offset seperti DragLayer. Saya juga akhirnya memanfaatkan registri internal di beberapa tempat. Saya tidak berpikir apa yang ingin saya lakukan adalah _too_ tidak biasa, jadi IMO itu dapat diatasi dengan perubahan api sederhana.

Apakah ada hasil dari ini? Saya ingin memantau offset klien di DropTarget saya daripada di lapisan seret khusus karena saya merasa lebih mudah untuk mengubah struktur DropTarget saat saya dapat mengakses lokasi hover dari DropTarget daripada di lapisan seret khusus.

+1 Saya ingin melakukan persis seperti yang coba dilakukan @jchristman .

+1. Mengalami hal ini dan membuang banyak waktu untuk mencoba memahami mengapa getClientOffset () berfungsi dengan baik di canDrop () - yang dipanggil berulang kali ... - tetapi tidak ada cara untuk meneruskan ke kendali saya 'di mana' atas item kita. Saya harus bisa menyisipkan di atas dan di bawah treenode, bukan hanya "di" saja. canDrop () tidak memiliki cara yang dapat saya temukan yang memungkinkan saya benar-benar memberi tahu komponen saya yang mana dari banyak "jenis" drop yang dimiliki node sehingga kontrol saya dapat ditampilkan kembali sesuai. Mengubah prop di canDrop mengakibatkan kesalahan. Berlangganan onMouseMove tidak berfungsi selama operasi seret dan lepas. Sejauh ini hal ini membuang-buang waktu ... bantuan apa pun akan sangat kami hargai.

@ibash , maukah Anda memposting inti dari apa yang Anda lakukan untuk menyelesaikan masalah Anda?

Beralih menggunakan hover akhirnya berhasil untuk saya.

`hover: (props: MyProps, monitor: DropTargetMonitor, komponen: ReportItemRow) => {
if (komponen! = null) {
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 , sementara menunggu sesuatu yang akan datang dari ini, Anda dapat memecahkan masalah Anda dengan membuat sejumlah Target Jatuhkan dan meletakkannya di bawah kode layar Anda. Sesuatu seperti ini:

         _____
         _____| <-- 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)

Anda berakhir dengan target penurunan 2n + 1 , di mana n adalah jumlah elemen dalam daftar Anda. Kemudian, berdasarkan div mana yang sedang diarahkan, Anda dapat mengubah tampilan daftar hierarki. Saya telah melakukan sesuatu yang sangat mirip dengan ini untuk menyiasati karena tidak dapat mengakses getClientOffset () untuk saat ini, dan saya tidak melihat adanya penurunan kinerja. Tinggi setiap div drop harus 1/2 tinggi baris treenode1 dan div drop pertama harus ditempatkan 1/4 dari tinggi baris treenode1 lebih tinggi dari baris pertama. Artinya, CSS Anda akan terlihat seperti ini:

.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>

Apakah itu masuk akal?

Inilah inti dari bagaimana saya menyelesaikan ini. Triknya adalah dengan menjangkau file
react-dnd internal dan gunakan monitor global secara langsung. Coba lihat
DragDropMonitor.js untuk mendapatkan gambaran tentang metode apa yang tersedia.
ref: https://github.com/gaearon/dnd-core/blob/master/src/DragDropMonitor.js

Anda harus memaafkan 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))

Apakah ini masuk akal? Jika tidak, saya dapat menambahkan lebih banyak detail

Saya juga mengalami ini hari ini dan menyia-nyiakan beberapa jam. Bagaimana dengan meninggalkan catatan tentang itu dalam dokumentasi di bawah DropTarget -> Fungsi Mengumpulkan? Itu setidaknya akan menghindarkan beberapa orang lain karena frustrasi.

Btw. satu lagi peretasan yang sangat buruk seputar masalah ini adalah mengirim koordinat _as state_ dari 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() { /* ... */ },
};

Tentu saja ada banyak cara yang lebih tepat untuk melakukan ini yang akan menghindari penyalahgunaan setState seperti itu. Tetapi setidaknya peretasan kecil ini sangat kental dan dapat melakukan pekerjaan sampai masalah ini pada akhirnya diperbaiki. Ini memungkinkan Anda meretas bug tanpa mengubah komponen dan / atau file lain dan tanpa bergantung pada internal perpustakaan.

Sunting: ini pada dasarnya sama dengan noah79, saya hanya tidak membaca kodenya sebelumnya.

Apakah benar-benar tidak ada cara untuk membuat posisi mouse jatuh? Komponen target saya tidak perlu mengetahuinya, jadi setState bukanlah pilihan bagi saya. Saya hanya perlu koordinat untuk memicu tindakan redux.

Mohon abaikan pesan terakhir saya. Saya melihat sekarang bahwa drop dan endDrag adalah dua fungsi yang berbeda, dan mendapatkan posisi kursor dalam drop berfungsi seperti yang diharapkan. :)

Dari pengujian saya, react-dnd memakan peristiwa gerakan mouse segera setelah item diseret ke target. Jika html-backend mungkin tidak menggunakan peristiwa ini, maka komponen target bisa saja menempatkan listener mousemove normal pada dom-nya secara bersyarat saat properti isOver disetel ke true. Setelah itu, pendengar ini dapat mengambil posisi mouse (dengan cara biasa) ketika ada sesuatu yang diseret ke atasnya. Saya mendapatkan ini untuk bekerja sementara menggunakan setState dari metode drag () bereaksi memberikan peringatan tentang pengaturan keadaan di tengah transisi render.

Ini membuat saya tersandung juga. +1 untuk solusi API atau setidaknya sesuatu di FAQ.

Apakah ada rencana untuk mendukung ini?
Saya punya ide untuk solusi bebas peretasan: buat pembungkus untuk DropTarget yang juga membungkus hover untuk memicu panggilan lain ke fungsi pengumpulan dan render ulang jika fungsi pengumpulan mengembalikan nilai baru .

1 untuk masalah ini. kasus penggunaan yang saya miliki adalah saya memiliki beberapa target penurunan yang berdekatan satu sama lain. Tampaknya aneh bahwa saat melayang, target penurunan yang tepat menyoroti tetapi sebenarnya menjatuhkan akan jatuh pada target yang lebih rendah tergantung dari arah mana Anda mengarahkan.

Saya tersandung pada masalah ini meneliti mengapa offset tidak diperbarui, sepertinya masih perlu solusi.
Saya mengambil ide @ jedwards1211 dan inilah yang saya dapatkan. HOC sebagian besar memiliki antarmuka yang sama dengan vanilla DropTarget , tetapi menerima collectRapidly dalam opsinya yang merupakan daftar alat peraga untuk ditanyakan melalui fungsi pengumpulan + diturunkan pada setiap acara hover. Meneruskan semua props tanpa pandang bulu dari fungsi pengumpulan menyebabkan beberapa keanehan dan kami tidak dapat memberikan connect ke kolektor sama sekali, jadi ada penjaga agar tidak menanyakan connectDropTarget "dengan cepat".

Jadi alih-alih DropTarget(types, target, collect) Anda akan menggunakan RapidFireDropTarget(types, target, collect, {collectRapidly: ['offset']}) , di mana offset mungkin sesuatu yang menerima nilainya dari fungsi keluarga 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;

Masalah ini secara otomatis ditandai sebagai usang karena tidak ada aktivitas terbaru. Ini akan ditutup jika tidak ada aktivitas lebih lanjut. Terima kasih atas kontribusi Anda.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat