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

Drop zone: fix media lib duplicate issue #29567

Merged
merged 2 commits into from
Mar 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 119 additions & 121 deletions packages/components/src/drop-zone/provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,10 @@ import { find, some, filter, includes, throttle } from 'lodash';
/**
* WordPress dependencies
*/
import {
createContext,
useEffect,
useRef,
useContext,
} from '@wordpress/element';
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();

Expand Down Expand Up @@ -109,135 +105,138 @@ export const INITIAL_DROP_ZONE_STATE = {
type: null,
};

export function useDrop( ref ) {
export function useDrop() {
const dropZones = useContext( Context );

useEffect( () => {
const { ownerDocument } = ref.current;
const { defaultView } = ownerDocument;
return useRefEffect(
( node ) => {
const { ownerDocument } = node;
const { defaultView } = ownerDocument;

let lastRelative;
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;
}
function updateDragZones( event ) {
if ( lastRelative && lastRelative.contains( event.target ) ) {
return;
}

// 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;
}
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;
}

return newState;
// 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();
}
event.preventDefault();
}

const throttledUpdateDragZones = throttle( updateDragZones, 200 );
const throttledUpdateDragZones = throttle( updateDragZones, 200 );

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

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

dropZones.forEach( ( dropZone ) =>
dropZone.setState( INITIAL_DROP_ZONE_STATE )
);
}
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 );
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();
}
event.stopPropagation();
event.preventDefault();
}

defaultView.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 () => {
defaultView.removeEventListener( 'drop', onDrop );
defaultView.removeEventListener( 'dragover', onDragOver );
defaultView.removeEventListener( 'mouseup', resetDragState );
defaultView.removeEventListener( 'dragend', resetDragState );
};
}, [ ref, dropZones ] );
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 ) {
Expand All @@ -246,8 +245,7 @@ export function DropZoneContextProvider( props ) {
}

function DropContainer( { children } ) {
const ref = useRef();
useDrop( ref );
const ref = useDrop();
return (
<div ref={ ref } className="components-drop-zone__provider">
{ children }
Expand Down
6 changes: 2 additions & 4 deletions packages/edit-post/src/components/layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
InterfaceSkeleton,
store as interfaceStore,
} from '@wordpress/interface';
import { useState, useEffect, useCallback, useRef } from '@wordpress/element';
import { useState, useEffect, useCallback } from '@wordpress/element';
import { close } from '@wordpress/icons';
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';

Expand Down Expand Up @@ -172,9 +172,7 @@ function Layout( { styles } ) {
},
[ entitiesSavedStatesCallback ]
);
const ref = useRef();

useDrop( ref );
const ref = useDrop( ref );
const [ inserterDialogRef, inserterDialogProps ] = useDialog( {
onClose: () => setIsInserterOpened( false ),
} );
Expand Down