konva icon indicating copy to clipboard operation
konva copied to clipboard

[feat] Allow disable DD

Open eXponenta opened this issue 2 years ago • 9 comments

Currently DD always attached to document in module load phase, that dissalow force isBrowser = false for some reason (when there are a virtual events):

https://github.com/konvajs/konva/blob/3219008dfa1de8cfdbfeeb444f38264878df033e/src/DragAndDrop.ts#L152

and there are not ways to detach it from Konva without hacks.

Maybe can be expose a method for detaching/attaching back? Like:

DD.attach = () => {
  if (!Konva.isBrowser || DD._attached) return;
  
  DD._attached = true;
  
  window.addEventListener('mouseup', DD._endDragBefore, true);
  window.addEventListener('touchend', DD._endDragBefore, true);

  window.addEventListener('mousemove', DD._drag);
  window.addEventListener('touchmove', DD._drag);

  window.addEventListener('mouseup', DD._endDragAfter, false);
  window.addEventListener('touchend', DD._endDragAfter, false);
}

DD.detach = () => {
  if (!Konva.isBrowser || !DD._attached) return;

  DD._attached = false;

  window.removeEventListener('mouseup', DD._endDragBefore, true);
  window.removeEventListener('touchend', DD._endDragBefore, true);

  window.removeEventListener('mousemove', DD._drag);
  window.removeEventListener('touchmove', DD._drag);

  window.removeEventListener('mouseup', DD._endDragAfter, false);
  window.removeEventListener('touchend', DD._endDragAfter, false);
}

eXponenta avatar Apr 07 '22 09:04 eXponenta

Why do you need it? What is your use with virtual events?

lavrton avatar Apr 07 '22 14:04 lavrton

We not use a canvas as canvas, we use it as image source and have more that one canvas instances as panels, BUT we have a main canvas for webgl context that capture events for lookup. Syntetic events is proxed to each stage instance. Our current code is:

import Konva from 'konva';
import { RootState } from 'react-ogl';
import { createRef, MutableRefObject } from 'react';

export const PROXED_EVENTS = ['pointermove', 'pointerdown', 'pointerup', 'pointerover', 'pointerout'] as const;

(Konva as any).isBrowser = false;

/**
 * Forward simulated events to canvas scope to specific stage
 */
export const forwardEvents = (event: PointerEvent | WheelEvent, stage: Konva.Stage) => {
	if (!event || !stage) {
		return;
	}

	const { type } = event;
	const syntEvent = {
		type,
		clientX: event.clientX,
		clientY: event.clientY,
		// for wheel
		deltaX: (event as any).deltaX ?? 0,
		deltaY: (event as any).deltaY ?? 0,
	};

	if (type === 'pointerup') {
		Konva.DD._endDragBefore(syntEvent);
	}

	if (type === 'pointermove') {
		Konva.DD._drag(syntEvent);
	}

	// fire as mouse when pointer
	type.includes('pointer') &&
		stage[`_${type}`]?.({
			...syntEvent,
			type: type.replace('pointer', 'mouse'),
		});

	// fire as pointer
	stage[`_${type}`]?.(syntEvent);

	// fire drag after AFTER up event
	if (type === 'pointerup') {
		Konva.DD._endDragAfter(syntEvent);
	}
};

export const attachEventProxy = (context: EventTarget, stage: Konva.Stage) => {
	const scope = (event) => forwardEvents(event, stage);

	PROXED_EVENTS.forEach((type) => context.addEventListener(type, scope));

	return () => PROXED_EVENTS.forEach((type) => context.removeEventListener(type, scope));
};

/**
 * Path Konva for use in OGL state and XR, because RAF must be from rendere
 * Allow to use OffscrenCanvas wehn possible and disable DD
 */
export const patchKonvaJS = (state: RootState) => {
	if (Konva.Util['@__pathed__']) {
		return;
	}

	let konvaQueue: Array<(time: number) => void> = [];
	/**
	 * Use Konva path, raf sould be requiested in valid phase
	 */
	Konva.Util.requestAnimFrame = (callback: any) => konvaQueue.push(callback);
	Konva.Util['@__pathed__'] = true;

	const refCallback: MutableRefObject<any> = createRef();

	refCallback.current = (_state, time) => {
		// update konva frames
		if (konvaQueue.length) {
			const q = konvaQueue;
			konvaQueue = [];
			q.forEach((c) => c && c(time));
		}
	};

	// and register
	state.subscribe(refCallback);

	// try to use offscreen canvas
	// eslint-disable-next-line no-restricted-globals
	if ((self as any).OffscreenCanvas) {
		Konva.Util.createCanvasElement = () => {
			// eslint-disable-next-line no-restricted-globals
			const canvas = new (self as any).OffscreenCanvas(1, 1);
			canvas.style = {};

			return canvas;
		};
	}

	// allso detach events
	// becuase it will corrupt state
	{
		const { DD } = Konva;

		window.removeEventListener('mouseup', DD._endDragBefore, true);
		window.removeEventListener('touchend', DD._endDragBefore, true);

		window.removeEventListener('mousemove', DD._drag);
		window.removeEventListener('touchmove', DD._drag);

		window.removeEventListener('mouseup', DD._endDragAfter, false);
		window.removeEventListener('touchend', DD._endDragAfter, false);
	}
};

eXponenta avatar Apr 07 '22 14:04 eXponenta

So, like this: each panel is new stage + layer for independent update, and each stage can has draggables. Panelles is curved, placed in 3D space, this is why need to proxy drag events from synthetic events, that created from raycast result on specific plane. 2D plan in FPS 3D space. Where mouse events used for camera movement and conflicts with drag events (because drag listen same context)

image

eXponenta avatar Apr 07 '22 14:04 eXponenta

Well, I am not sure about that use case. I see this in the code:

// allso detach events // becuase it will corrupt state

What will it corrupt?

Is it possible for you to create a tiny demo that can reproduce the issue? Can you use current API and just manually unsubscribe as you do in the code?

lavrton avatar Apr 11 '22 23:04 lavrton

Well, I am not sure about that use case. I see this in the code:

// allso detach events // becuase it will corrupt state

What will it corrupt?

Is it possible for you to create a tiny demo that can reproduce the issue? Can you use current API and just manually unsubscribe as you do in the code?

See: https://codesandbox.io/s/blazing-shape-c1eidt?file=/src/index.js

Konva mapped onto cube, but while you not click disable DD then drag and drop will be bugged

eXponenta avatar Apr 12 '22 19:04 eXponenta

In this demo I can't move the star. Because I can't click on it. Looks like you position calculation is not correct. I adopted demo to create dots on clicks: https://codesandbox.io/s/hopeful-zeh-xcg5rg?file=/src/index.js

So you can detach all DD events from the global by yourself, right?

lavrton avatar Apr 13 '22 23:04 lavrton

Looks like you position calculation is not correct.

I forgot disable auto pixel ratio and size of Konva Stage had more than should be (i use screen with 1 DPR, thanks that notice this)

So you can detach all DD events from the global by yourself, right?

Yep, of course, but if DD Api will change - all hacks will down.

eXponenta avatar Apr 14 '22 06:04 eXponenta

I see. So, personally, I am not very interested in this API. The workaround is working just fine. Even when attach/detach methods are added, you are still using private API here: stage[_${type}]?.(syntEvent);.

I have no plans to change DD API or stage._[event] methods names. Just update carefully.

If you REALLY want that as part of the core Konva API, then your initial proposal sounds good to me. Make a Pull Request.

lavrton avatar Apr 14 '22 19:04 lavrton

Actually, it will be good to have an official demo of that use case. Because I remember several users with the same use case (drawing Konva canvas into 3d scene).

@eXponenta can you fix the demo, to make coordinates system work correctly?

lavrton avatar Apr 14 '22 19:04 lavrton