From 89e564c252d4e1bd3d13f5ac79e80479d37b8ec7 Mon Sep 17 00:00:00 2001 From: Dmitry Samsonov Date: Mon, 27 Aug 2018 15:54:07 +0200 Subject: [PATCH] Add access to current dropEffect for dropTargets --- packages/dnd-core/src/DragDropMonitorImpl.ts | 4 + packages/dnd-core/src/actions/dragDrop.ts | 11 +++ packages/dnd-core/src/interfaces.ts | 9 +++ .../dnd-core/src/reducers/dragOperation.ts | 10 +++ .../Copy or Move restricted/Box.tsx | 71 ++++++++++++++++++ .../Copy or Move restricted/Container.tsx | 25 +++++++ .../Copy or Move restricted/Dustbin.tsx | 73 +++++++++++++++++++ .../Copy or Move restricted/index.tsx | 24 ++++++ packages/documentation/site/Constants.js | 4 + packages/documentation/site/IndexPage.js | 2 + .../src/HTML5Backend.ts | 12 +++ packages/react-dnd/src/createSourceMonitor.ts | 4 + packages/react-dnd/src/createTargetMonitor.ts | 4 + packages/react-dnd/src/interfaces.ts | 5 ++ 14 files changed, 258 insertions(+) create mode 100644 packages/documentation/examples/01 Dustbin/Copy or Move restricted/Box.tsx create mode 100644 packages/documentation/examples/01 Dustbin/Copy or Move restricted/Container.tsx create mode 100644 packages/documentation/examples/01 Dustbin/Copy or Move restricted/Dustbin.tsx create mode 100644 packages/documentation/examples/01 Dustbin/Copy or Move restricted/index.tsx diff --git a/packages/dnd-core/src/DragDropMonitorImpl.ts b/packages/dnd-core/src/DragDropMonitorImpl.ts index 1fea52068c..7ac73bc433 100644 --- a/packages/dnd-core/src/DragDropMonitorImpl.ts +++ b/packages/dnd-core/src/DragDropMonitorImpl.ts @@ -161,6 +161,10 @@ export default class DragDropMonitorImpl implements DragDropMonitor { return this.store.getState().dragOperation.dropResult } + public getCurrentDropEffect() { + return this.store.getState().dragOperation.dropEffect + } + public didDrop() { return this.store.getState().dragOperation.didDrop } diff --git a/packages/dnd-core/src/actions/dragDrop.ts b/packages/dnd-core/src/actions/dragDrop.ts index cbf6226931..e16b1d0ffe 100644 --- a/packages/dnd-core/src/actions/dragDrop.ts +++ b/packages/dnd-core/src/actions/dragDrop.ts @@ -5,6 +5,7 @@ import { BeginDragPayload, BeginDragOptions, SentinelAction, + DragPayload, DropPayload, HoverPayload, HoverOptions, @@ -15,6 +16,7 @@ import isObject from 'lodash/isObject' import matchesType from '../utils/matchesType' export const BEGIN_DRAG = 'dnd-core/BEGIN_DRAG' +export const DRAG = 'dnd-core/DRAG' export const PUBLISH_DRAG_SOURCE = 'dnd-core/PUBLISH_DRAG_SOURCE' export const HOVER = 'dnd-core/HOVER' export const DROP = 'dnd-core/DROP' @@ -90,6 +92,15 @@ export default function createDragDropActions( return { type: PUBLISH_DRAG_SOURCE } }, + drag(dropEffect: string): Action { + return { + type: DRAG, + payload: { + dropEffect, + }, + } + }, + hover( targetIdsArg: string[], { clientOffset }: HoverOptions = {}, diff --git a/packages/dnd-core/src/interfaces.ts b/packages/dnd-core/src/interfaces.ts index 02fe2484aa..08c5b60c8c 100644 --- a/packages/dnd-core/src/interfaces.ts +++ b/packages/dnd-core/src/interfaces.ts @@ -67,6 +67,10 @@ export interface DragDropMonitor { * called outside endDrag(). */ getDropResult(): any + /** + * Returns dropEffect for current drag operation. + */ + getCurrentDropEffect(): string | null /** * Returns true if some drop target has handled the drop event, false otherwise. Even if a target did not return a drop result, * didDrop() returns true. Use it inside endDrag() to test whether any drop target has handled the drop. Returns false if called @@ -154,6 +158,10 @@ export interface HoverOptions { clientOffset?: XYCoord } +export interface DragPayload { + dropEffect: string +} + export interface DropPayload { dropResult: any } @@ -168,6 +176,7 @@ export interface SourceIdPayload { export interface DragDropActions { beginDrag(sourceIds: string[], options?: any): Action + drag(dropEffect: string): Action publishDragSource(): SentinelAction hover(targetIds: string[], options?: any): Action drop(options?: any): void diff --git a/packages/dnd-core/src/reducers/dragOperation.ts b/packages/dnd-core/src/reducers/dragOperation.ts index e41153b3b8..bb166e8a21 100644 --- a/packages/dnd-core/src/reducers/dragOperation.ts +++ b/packages/dnd-core/src/reducers/dragOperation.ts @@ -4,6 +4,7 @@ import { PUBLISH_DRAG_SOURCE, HOVER, END_DRAG, + DRAG, DROP, } from '../actions/dragDrop' import { REMOVE_TARGET } from '../actions/registry' @@ -20,6 +21,7 @@ export interface State { sourceId: string | null targetIds: string[] dropResult: any + dropEffect: string | null didDrop: boolean isSourcePublic: boolean | null } @@ -30,6 +32,7 @@ const initialState: State = { sourceId: null, targetIds: [], dropResult: null, + dropEffect: null, didDrop: false, isSourcePublic: null, } @@ -44,6 +47,7 @@ export default function dragOperation( targetIds: string[] isSourcePublic: boolean dropResult: any + dropEffect: string }>, ) { const { payload } = action @@ -58,6 +62,11 @@ export default function dragOperation( dropResult: null, didDrop: false, } + case DRAG: + return { + ...state, + dropEffect: payload.dropEffect, + } case PUBLISH_DRAG_SOURCE: return { ...state, @@ -90,6 +99,7 @@ export default function dragOperation( item: null, sourceId: null, dropResult: null, + dropEffect: null, didDrop: false, isSourcePublic: null, targetIds: [], diff --git a/packages/documentation/examples/01 Dustbin/Copy or Move restricted/Box.tsx b/packages/documentation/examples/01 Dustbin/Copy or Move restricted/Box.tsx new file mode 100644 index 0000000000..20efe19f76 --- /dev/null +++ b/packages/documentation/examples/01 Dustbin/Copy or Move restricted/Box.tsx @@ -0,0 +1,71 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { + DragSource, + ConnectDragSource, + DragSourceConnector, + DragSourceMonitor, +} from 'react-dnd' +import ItemTypes from '../Single Target/ItemTypes' +import { DragDropManager } from 'dnd-core' + +const style: React.CSSProperties = { + border: '1px dashed gray', + backgroundColor: 'white', + padding: '0.5rem 1rem', + marginRight: '1.5rem', + marginBottom: '1.5rem', + float: 'left', +} + +const boxSource = { + beginDrag(props: BoxProps) { + return { + name: props.name, + } + }, + + endDrag(props: BoxProps, monitor: DragSourceMonitor) { + const item = monitor.getItem() + const dropResult = monitor.getDropResult() + + if (dropResult) { + const isCopyAction = dropResult.dropEffect === 'copy' + const actionName = isCopyAction ? 'copied' : 'moved' + alert(`You ${actionName} ${item.name} into ${dropResult.name}!`) // eslint-disable-line no-alert + } + }, +} + +export interface BoxProps { + name: string + isDragging?: boolean + connectDragSource?: ConnectDragSource +} + +@DragSource( + ItemTypes.BOX, + boxSource, + (connect: DragSourceConnector, monitor: DragSourceMonitor) => ({ + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging(), + }), +) +export default class Box extends React.Component { + public static propTypes = { + connectDragSource: PropTypes.func.isRequired, + isDragging: PropTypes.bool.isRequired, + name: PropTypes.string.isRequired, + } + + public render() { + const { isDragging, connectDragSource } = this.props + const { name } = this.props + const opacity = isDragging ? 0.4 : 1 + + return ( + connectDragSource && + connectDragSource(
{name}
) + ) + } +} diff --git a/packages/documentation/examples/01 Dustbin/Copy or Move restricted/Container.tsx b/packages/documentation/examples/01 Dustbin/Copy or Move restricted/Container.tsx new file mode 100644 index 0000000000..5279a2a587 --- /dev/null +++ b/packages/documentation/examples/01 Dustbin/Copy or Move restricted/Container.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import { DragDropContextProvider } from 'react-dnd' +import HTML5Backend from 'react-dnd-html5-backend' +import Dustbin from './Dustbin' +import Box from './Box' + +export default class Container extends React.Component { + public render() { + return ( + +
+
+ + +
+
+ + + +
+
+
+ ) + } +} diff --git a/packages/documentation/examples/01 Dustbin/Copy or Move restricted/Dustbin.tsx b/packages/documentation/examples/01 Dustbin/Copy or Move restricted/Dustbin.tsx new file mode 100644 index 0000000000..3568910898 --- /dev/null +++ b/packages/documentation/examples/01 Dustbin/Copy or Move restricted/Dustbin.tsx @@ -0,0 +1,73 @@ +import React from 'react' +import PropTypes from 'prop-types' +import {DropTarget, ConnectDropTarget, DropTargetMonitor} from 'react-dnd' +import ItemTypes from '../Single Target/ItemTypes' + +const style: React.CSSProperties = { + height: '12rem', + width: '12rem', + marginRight: '1.5rem', + marginBottom: '1.5rem', + color: 'white', + padding: '1rem', + textAlign: 'center', + fontSize: '1rem', + lineHeight: 'normal', + float: 'left', +} + +const boxTarget = { + drop({ allowedDropEffect }: DustbinProps) { + return { + name: `${allowedDropEffect} Dustbin`, + } + }, + canDrop({ allowedDropEffect } : DustbinProps, monitor: any) { + return allowedDropEffect === monitor.getCurrentDropEffect() + } +} + +export interface DustbinProps { + connectDropTarget?: ConnectDropTarget + canDrop?: boolean + isOver?: boolean + allowedDropEffect: string +} + +@DropTarget(ItemTypes.BOX, boxTarget, (connect, monitor) => ({ + connectDropTarget: connect.dropTarget(), + isOver: monitor.isOver(), + canDrop: monitor.canDrop(), +})) +export default class Dustbin extends React.Component { + public static propTypes = { + connectDropTarget: PropTypes.func.isRequired, + isOver: PropTypes.bool.isRequired, + canDrop: PropTypes.bool.isRequired, + allowedDropEffect: PropTypes.string.isRequired, + } + + public render() { + const { canDrop, isOver, allowedDropEffect, connectDropTarget } = this.props + const isActive = canDrop && isOver + + let backgroundColor = '#222' + if (isActive) { + backgroundColor = 'darkgreen' + } else if (canDrop) { + backgroundColor = 'darkkhaki' + } + + return ( + connectDropTarget && + connectDropTarget( +
+ {`You can only ${allowedDropEffect} items here`} +
+
+ {isActive ? 'Release to drop' : 'Drag a box here'} +
, + ) + ) + } +} diff --git a/packages/documentation/examples/01 Dustbin/Copy or Move restricted/index.tsx b/packages/documentation/examples/01 Dustbin/Copy or Move restricted/index.tsx new file mode 100644 index 0000000000..f8f94b6fa2 --- /dev/null +++ b/packages/documentation/examples/01 Dustbin/Copy or Move restricted/index.tsx @@ -0,0 +1,24 @@ +import React from 'react' +import Container from './Container' + +export default class DustbinCopyOrMoveRestricted extends React.Component { + public render() { + return ( +
+

+ + + Browse the Source + + +

+

+ This example demonstrates drop targets that can be disabled/enabled + based on current drop effect, which users can switch between by + holding down or releasing the alt key as they drag. +

+ +
+ ) + } +} diff --git a/packages/documentation/site/Constants.js b/packages/documentation/site/Constants.js index f7d7af9c87..e31fd1d80f 100644 --- a/packages/documentation/site/Constants.js +++ b/packages/documentation/site/Constants.js @@ -126,6 +126,10 @@ export const ExamplePages = [ location: 'examples-dustbin-copy-or-move.html', title: 'Copy or Move', }, + DUSTBIN_COPY_OR_MOVE_RESTRICTED: { + location: 'examples-dustbin-copy-or-move-restricted.html', + title: 'Copy or Move restricted', + }, DUSTBIN_MULTIPLE_TARGETS: { location: 'examples-dustbin-multiple-targets.html', title: 'Multiple Targets', diff --git a/packages/documentation/site/IndexPage.js b/packages/documentation/site/IndexPage.js index 5ac3392e80..34c7e1409f 100644 --- a/packages/documentation/site/IndexPage.js +++ b/packages/documentation/site/IndexPage.js @@ -34,6 +34,8 @@ const Examples = { DUSTBIN_IFRAME: require('../examples/01 Dustbin/Single Target in iframe') .default, DUSTBIN_COPY_OR_MOVE: require('../examples/01 Dustbin/Copy or Move').default, + DUSTBIN_COPY_OR_MOVE_RESTRICTED: require('../examples/01 Dustbin/Copy or Move restricted') + .default, DUSTBIN_MULTIPLE_TARGETS: require('../examples/01 Dustbin/Multiple Targets') .default, DUSTBIN_STRESS_TEST: require('../examples/01 Dustbin/Stress Test').default, diff --git a/packages/react-dnd-html5-backend/src/HTML5Backend.ts b/packages/react-dnd-html5-backend/src/HTML5Backend.ts index e254021b3b..e71300abdb 100644 --- a/packages/react-dnd-html5-backend/src/HTML5Backend.ts +++ b/packages/react-dnd-html5-backend/src/HTML5Backend.ts @@ -152,6 +152,7 @@ export default class HTML5Backend implements Backend { if (!target.addEventListener) { return } + target.addEventListener('drag', this.handleDrag) target.addEventListener('dragstart', this.handleTopDragStart) target.addEventListener('dragstart', this.handleTopDragStartCapture, true) target.addEventListener('dragend', this.handleTopDragEndCapture, true) @@ -169,6 +170,7 @@ export default class HTML5Backend implements Backend { if (!target.removeEventListener) { return } + target.removeEventListener('drag', this.handleDrag) target.removeEventListener('dragstart', this.handleTopDragStart) target.removeEventListener( 'dragstart', @@ -483,6 +485,16 @@ export default class HTML5Backend implements Backend { } } + @autobind + private handleDrag(e: any) { + if (!this.monitor.isDragging()) { + return + } + + this.altKeyPressed = e.altKey + this.actions.drag(this.getCurrentDropEffect()) + } + @autobind private handleTopDragEndCapture() { if (this.clearCurrentDragSourceNode()) { diff --git a/packages/react-dnd/src/createSourceMonitor.ts b/packages/react-dnd/src/createSourceMonitor.ts index a997530d49..94df291730 100644 --- a/packages/react-dnd/src/createSourceMonitor.ts +++ b/packages/react-dnd/src/createSourceMonitor.ts @@ -109,6 +109,10 @@ class SourceMonitor implements DragSourceMonitor { return this.internalMonitor.getDropResult() } + public getCurrentDropEffect() { + return this.internalMonitor.getCurrentDropEffect() + } + public didDrop() { return this.internalMonitor.didDrop() } diff --git a/packages/react-dnd/src/createTargetMonitor.ts b/packages/react-dnd/src/createTargetMonitor.ts index c3bf594cae..ba1460602c 100644 --- a/packages/react-dnd/src/createTargetMonitor.ts +++ b/packages/react-dnd/src/createTargetMonitor.ts @@ -53,6 +53,10 @@ export class TargetMonitor implements DropTargetMonitor { return this.internalMonitor.getDropResult() } + public getCurrentDropEffect() { + return this.internalMonitor.getCurrentDropEffect() + } + public didDrop() { return this.internalMonitor.didDrop() } diff --git a/packages/react-dnd/src/interfaces.ts b/packages/react-dnd/src/interfaces.ts index 6a1654117a..68b2aa93f7 100644 --- a/packages/react-dnd/src/interfaces.ts +++ b/packages/react-dnd/src/interfaces.ts @@ -147,6 +147,11 @@ export interface DropTargetMonitor { */ getDropResult(): any + /** + * Returns dropEffect for current drag operation. + */ + getCurrentDropEffect(): string | null + /** * Returns true if some drop target has handled the drop event, false otherwise. Even if a target did not return a drop result, * didDrop() returns true. Use it inside drop() to test whether any nested drop target has already handled the drop. Returns false