Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use drop zone when dragging an image to an empty paragraph #29145

Closed
Tracked by #33683
mtias opened this issue Feb 19, 2021 · 12 comments · Fixed by #42722
Closed
Tracked by #33683

Use drop zone when dragging an image to an empty paragraph #29145

mtias opened this issue Feb 19, 2021 · 12 comments · Fixed by #42722
Assignees
Labels
[Block] Image Affects the Image Block [Feature] Drag and Drop Drag and drop functionality when working with blocks [Feature] Media Anything that impacts the experience of managing media [Status] In Progress Tracking issues with work in progress

Comments

@mtias
Copy link
Member

mtias commented Feb 19, 2021

It should be possible to drag an image to an empty paragraph. Instead of this:

image

We should show something like this (fitting the empty paragraph size):

image

@mtias mtias added [Feature] Media Anything that impacts the experience of managing media [Feature] Drag and Drop Drag and drop functionality when working with blocks [Block] Image Affects the Image Block labels Feb 19, 2021
@Quintis1212
Copy link
Contributor

Hello ) I will help to fix this

@Quintis1212
Copy link
Contributor

Quintis1212 commented Mar 8, 2021

It seems I founded the source of problem. When you drag over on empty paragraf hoveredDropZone object at 132 line in provider.js file has key isRelative ,and value of isRelative is false , but after you have drag on an image isRelative becomes true
Знімок екрана з 2021-03-08 13-42-40

Can any one explain what does isRelative mean ? All p tags are already have position relative.

@Mamaduka
Copy link
Member

Hi, @Quintis1212

Let me know if you need any help with this.

@Quintis1212
Copy link
Contributor

Hi @Mamaduka ) I still do not understand where is the source of this bug ) please read my comment above . From where I must start to fix this bug

@Mamaduka
Copy link
Member

Hi, @Quintis1212 can you share the code you're using for testing?

Currently, I don't see the dropzone component in the paragraph block.

@Quintis1212
Copy link
Contributor

Quintis1212 commented Mar 16, 2021

@Mamaduka console.trace() showed me that dropzone component is called by provider :
path - gutenberg/packages/components/src/drop-zone/provider.js
`
/**
* External dependencies
*/
import { find, some, filter, includes, throttle } from 'lodash';

      /**
       * WordPress dependencies
       */
      import { createContext, useRef, useContext } from '@wordpress/element';
      import { getFilesFromDataTransfer } from '@wordpress/dom';
      import isShallowEqual from '@wordpress/is-shallow-equal';
      import { useRefEffect } from '@wordpress/compose';
      
      export const Context = createContext();
      
      const { Provider } = Context;
      
      function getDragEventType( { dataTransfer } ) {
          if ( dataTransfer ) {
	          // Use lodash `includes` here as in the Edge browser `types` is implemented
	          // as a DomStringList, whereas in other browsers it's an array. `includes`
	          // happily works with both types.
	          if (
		          includes( dataTransfer.types, 'Files' ) ||
		          getFilesFromDataTransfer( dataTransfer ).length > 0
	          ) {
		          return 'file';
	          }
      
	          if ( includes( dataTransfer.types, 'text/html' ) ) {
		          return 'html';
	          }
          }
      
          return 'default';
      }
                      
                      function isTypeSupportedByDropZone( type, dropZone ) {
                          return Boolean(
	                          ( type === 'file' && dropZone.onFilesDrop ) ||
		                          ( type === 'html' && dropZone.onHTMLDrop ) ||
		                          ( type === 'default' && dropZone.onDrop )
                          );
                      }
      
            function isWithinElementBounds( element, x, y ) {
                const rect = element.getBoundingClientRect();
                /// make sure the rect is a valid rect
                if ( rect.bottom === rect.top || rect.left === rect.right ) {
	                return false;
                }
            
                return (
	                x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom
                );
            }

  function getPosition( event ) {
      // In some contexts, it may be necessary to capture and redirect the
      // drag event (e.g. atop an `iframe`). To accommodate this, you can
      // create an instance of CustomEvent with the original event specified
      // as the `detail` property.
      //
// See: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
const detail =
	window.CustomEvent && event instanceof window.CustomEvent
		? event.detail
		: event;
        
            return { x: detail.clientX, y: detail.clientY };
        }

  function getHoveredDropZone( dropZones, position, dragEventType ) {
      const hoveredDropZones = filter(
	Array.from( dropZones ),
	( dropZone ) =>
		isTypeSupportedByDropZone( dragEventType, dropZone ) &&
		isWithinElementBounds(
			dropZone.element.current,
			position.x,
			position.y
		)
);

// Find the leaf dropzone not containing another dropzone
return find( hoveredDropZones, ( zone ) => {
	const parentClassName = zone.element.current.parentElement.className;
	if ( parentClassName.indexOf( 'components-disabled' ) === -1 ) {
		const container = zone.isRelative
			? zone.element.current.parentElement
			: zone.element.current;

		return ! some(
			hoveredDropZones,
			( subZone ) =>
				subZone !== zone &&
				container.contains( subZone.element.current )
		                  );
	                  }
                  } );
              }
        
        export const INITIAL_DROP_ZONE_STATE = {
            isDraggingOverDocument: false,
            isDraggingOverElement: false,
            x: null,
            y: null,
            type: null,
        };
        
        export function useDrop() {
            const dropZones = useContext( Context );

return useRefEffect(
	( node ) => {
		const { ownerDocument } = node;
		const { defaultView } = ownerDocument;

		let lastRelative;

		function updateDragZones( event ) {
			if ( lastRelative && lastRelative.contains( event.target ) ) {
				return;
			}

			const dragEventType = getDragEventType( event );
			const position = getPosition( event );
			const hoveredDropZone = getHoveredDropZone(
				dropZones,
				position,
				dragEventType
			);

			if ( hoveredDropZone && hoveredDropZone.isRelative ) {
				lastRelative = hoveredDropZone.element.current.offsetParent;
			} else {
				lastRelative = null;
			}

			// Notifying the dropzones
			dropZones.forEach( ( dropZone ) => {
				const isDraggingOverDropZone = dropZone === hoveredDropZone;
				const newState = {
					isDraggingOverDocument: isTypeSupportedByDropZone(
						dragEventType,
						dropZone
					),
					isDraggingOverElement: isDraggingOverDropZone,
					x:
						isDraggingOverDropZone && dropZone.withPosition
							? position.x
							: null,
					y:
						isDraggingOverDropZone && dropZone.withPosition
							? position.y
							: null,
					type: isDraggingOverDropZone ? dragEventType : null,
				};

				dropZone.setState( ( state ) => {
					if ( isShallowEqual( state, newState ) ) {
						return state;
					}

					return newState;
				} );
			} );

			event.preventDefault();
		}

		const throttledUpdateDragZones = throttle( updateDragZones, 200 );

		function onDragOver( event ) {
			throttledUpdateDragZones( event );
			event.preventDefault();
		}

		function resetDragState() {
			// Avoid throttled drag over handler calls
			throttledUpdateDragZones.cancel();

			dropZones.forEach( ( dropZone ) =>
				dropZone.setState( INITIAL_DROP_ZONE_STATE )
			);
		}

		function onDrop( event ) {
			// This seemingly useless line has been shown to resolve a Safari issue
			// where files dragged directly from the dock are not recognized
			event.dataTransfer && event.dataTransfer.files.length; // eslint-disable-line no-unused-expressions

			const dragEventType = getDragEventType( event );
			const position = getPosition( event );
			const hoveredDropZone = getHoveredDropZone(
				dropZones,
				position,
				dragEventType
			);

			resetDragState();

			if ( hoveredDropZone ) {
				switch ( dragEventType ) {
					case 'file':
						hoveredDropZone.onFilesDrop(
							getFilesFromDataTransfer( event.dataTransfer ),
							position
						);
						break;
					case 'html':
						hoveredDropZone.onHTMLDrop(
							event.dataTransfer.getData( 'text/html' ),
							position
						);
						break;
					case 'default':
						hoveredDropZone.onDrop( event, position );
				}
			}

			event.stopPropagation();
			event.preventDefault();
		}

		node.addEventListener( 'drop', onDrop );
		defaultView.addEventListener( 'dragover', onDragOver );
		defaultView.addEventListener( 'mouseup', resetDragState );
		// Note that `dragend` doesn't fire consistently for file and HTML drag
		// events where the drag origin is outside the browser window.
		// In Firefox it may also not fire if the originating node is removed.
		defaultView.addEventListener( 'dragend', resetDragState );

		return () => {
			node.removeEventListener( 'drop', onDrop );
			defaultView.removeEventListener( 'dragover', onDragOver );
			defaultView.removeEventListener( 'mouseup', resetDragState );
			defaultView.removeEventListener( 'dragend', resetDragState );
		};
	},
	[ dropZones ]
          );
      }
      
      export function DropZoneContextProvider( props ) {
          const ref = useRef( new Set( [] ) );
          return <Provider { ...props } value={ ref.current } />;
      }

      function DropContainer( { children } ) {
          const ref = useDrop();
          return (
	          <div ref={ ref } className="components-drop-zone__provider">
		          { children }
	          </div>
          );
      }

  export default function DropZoneProvider( { children } ) {
      return (
	      <DropZoneContextProvider>
		      <DropContainer>{ children }</DropContainer>
	      </DropZoneContextProvider>
      );
  }

`
If you have any idea how fix the bug please share it to me )

@Mamaduka
Copy link
Member

Hi @Quintis1212,

Sorry for the confusion. I meant code for PR where you added dropzone to a paragraph block.

P.S. You can also use the <details> element when sharing large pieces of code. See https://gist.github.com/ericclemmons/b146fe5da72ca1f706b2ef72a20ac39d

@Quintis1212
Copy link
Contributor

Quintis1212 commented Mar 17, 2021

Hi @Mamaduka )

Sorry for the confusion. I meant code for PR where you added dropzone to a paragraph block.

I dont created a PR because I create PR when I have at least one approach to fix the problem .If you want get my test code for this bug just copy all code above in my comment in provider.js) I don't do any changes to another files for this issue

@paaljoachim
Copy link
Contributor

Drag and drop of image onto an empty paragraph block would/should look similar to dragging and dropping an image onto an existing image. Like so:

Drag-drop-image-on-image.mp4

@apeatling
Copy link
Contributor

apeatling commented Mar 31, 2022

I want to make sure that I understand this correctly. When you use the empty paragraph example in the issue comment, is that a single paragraph block that has empty new shift+return lines in it?

I ask because If a user creates space between two paragraph blocks, they are going to insert multiple empty block placeholders like this:

Screen Shot 2022-03-31 at 12 54 19 PM

So should the drop zone sit over top of the height of those empty block placeholders (shown with the green outline), and replace them once dropped? My thought is yes, because it seems like the most common situation based on people mashing the return key.

@apeatling
Copy link
Contributor

Feedback from Matías is that it should just stick to covering one paragraph/empty block at a time.

@paaljoachim
Copy link
Contributor

Drop zones for one paragraph/empty block at a time sound right! As it would give a more precise zone to where something is being dropped.


Btw there are a few existing drop zone issues. I noticed @richtabor has created a few.
The following is one that would be very helpful to get fixed.

Add visual cue when dragging a block into an empty Group block
#39064

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Block] Image Affects the Image Block [Feature] Drag and Drop Drag and drop functionality when working with blocks [Feature] Media Anything that impacts the experience of managing media [Status] In Progress Tracking issues with work in progress
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants