From b9279d4ff9a122bec9fe146db58bdd8f22ddfd5b Mon Sep 17 00:00:00 2001 From: Abhas Bhattacharya Date: Sun, 8 Apr 2018 12:10:39 +0530 Subject: [PATCH] Complete DragContainer and DraggableNode components + Helpers --- .../CreateEditorApp/CreateEditorApp.css | 1 + .../CreateEditorApp/CreateEditorApp.tsx | 18 ++- .../DragContainer/DragContainer.css | 3 + .../DragContainer/DragContainer.tsx | 148 ++++++++++++++++++ src/Components/DragContainer/index.ts | 1 + .../DraggableNode/DraggableNode.css | 4 + .../DraggableNode/DraggableNode.tsx | 56 +++++-- src/Helpers/DragContextProvider.ts | 36 +++++ src/Helpers/index.ts | 2 + src/Helpers/utils.ts | 4 + 10 files changed, 254 insertions(+), 19 deletions(-) create mode 100644 src/Components/DragContainer/DragContainer.css create mode 100644 src/Components/DragContainer/DragContainer.tsx create mode 100644 src/Components/DragContainer/index.ts create mode 100644 src/Components/DraggableNode/DraggableNode.css create mode 100644 src/Helpers/DragContextProvider.ts create mode 100644 src/Helpers/index.ts create mode 100644 src/Helpers/utils.ts diff --git a/src/Components/CreateEditorApp/CreateEditorApp.css b/src/Components/CreateEditorApp/CreateEditorApp.css index c5c6e8a..42bf584 100644 --- a/src/Components/CreateEditorApp/CreateEditorApp.css +++ b/src/Components/CreateEditorApp/CreateEditorApp.css @@ -20,6 +20,7 @@ .App-intro { font-size: large; + background-color: gray; } @keyframes App-logo-spin { diff --git a/src/Components/CreateEditorApp/CreateEditorApp.tsx b/src/Components/CreateEditorApp/CreateEditorApp.tsx index 18b04f0..36f4631 100644 --- a/src/Components/CreateEditorApp/CreateEditorApp.tsx +++ b/src/Components/CreateEditorApp/CreateEditorApp.tsx @@ -1,19 +1,29 @@ import * as React from 'react'; import './CreateEditorApp.css'; +import { DragContainer } from '../DragContainer'; import { DraggableNode } from '../DraggableNode'; const logo = require('./logo.svg'); export class CreateEditorApp extends React.Component { + logFn() { + // tslint:disable-next-line:no-console + console.log(arguments); + } + render() { return (
- +
- logo -

Welcome to React

+ + logo + + +

Welcome to React

+
-
+

To get started, edit src/App.tsx and save to reload.

diff --git a/src/Components/DragContainer/DragContainer.css b/src/Components/DragContainer/DragContainer.css new file mode 100644 index 0000000..c483210 --- /dev/null +++ b/src/Components/DragContainer/DragContainer.css @@ -0,0 +1,3 @@ +.drag-container { + overflow: hidden; +} \ No newline at end of file diff --git a/src/Components/DragContainer/DragContainer.tsx b/src/Components/DragContainer/DragContainer.tsx new file mode 100644 index 0000000..9c61ac8 --- /dev/null +++ b/src/Components/DragContainer/DragContainer.tsx @@ -0,0 +1,148 @@ +import * as React from 'react'; +import './DragContainer.css'; +import { DragContextProvider, DragSharedInfo, XYPair } from '../../Helpers'; + +interface DragContainerProps { + id: string; +} + +type TotalOffset = XYPair; + +interface DragContainerState { + ongoing: { + dragging: boolean; + draggableId: string | undefined; + startMousePosition: XYPair | undefined; + currentMousePosition: XYPair | undefined; + }; + pastCumulativeOffsetMap: { + [draggableId: string]: TotalOffset + }; +} + +export class DragContainer extends React.Component { + constructor(props: DragContainerProps) { + super(props); + + this.state = { + ongoing: { + dragging: false, + draggableId: undefined, + startMousePosition: undefined, + currentMousePosition: undefined, + }, + pastCumulativeOffsetMap: { + + } + }; + + this.handleDragStartOnDraggableNode = this.handleDragStartOnDraggableNode.bind(this); + this.handleDragMove = this.handleDragMove.bind(this); + this.handleDragStop = this.handleDragStop.bind(this); + } + + handleDragStartOnDraggableNode(draggableId: string, ev: React.MouseEvent): void { + this.setState({ + ongoing: { + dragging: true, + draggableId: draggableId, + startMousePosition: { x: ev.clientX, y: ev.clientY }, + currentMousePosition: { x: ev.clientX, y: ev.clientY }, + } + }); + } + + handleDragMove(ev: React.MouseEvent) { + if (this.state.ongoing.dragging) { + const oldStateOngoing = this.state.ongoing; + + let newStateOngoing = Object.assign({}, oldStateOngoing); + newStateOngoing.currentMousePosition = { + x: ev.clientX, + y: ev.clientY + }; + + this.setState({ + ongoing: newStateOngoing + }); + } + } + + handleDragStop(ev: React.MouseEvent) { + if (this.state.ongoing.dragging) { + const oldState = this.state; + const draggableId = oldState.ongoing.draggableId as string; + const oldStateStartMousePosition = oldState.ongoing.startMousePosition as XYPair; + const pastCumulativeOffset = oldState.pastCumulativeOffsetMap[draggableId] || { x: 0, y: 0 }; + + let newState: DragContainerState = Object.assign({}, oldState); + newState.ongoing = { + dragging: false, + draggableId: undefined, + currentMousePosition: undefined, + startMousePosition: undefined + }; + + newState.pastCumulativeOffsetMap[draggableId] = { + x: pastCumulativeOffset.x + (ev.clientX - oldStateStartMousePosition.x), + y: pastCumulativeOffset.y + (ev.clientY - oldStateStartMousePosition.y), + }; + + this.setState(newState); + } + } + + getContext(): React.Context { + return DragContextProvider.get(this.props.id); + } + // FIX DragSharedInfo providing by using draggableID map + getCurrentOffsetMap() { + const stateOngoing = this.state.ongoing; + const lastOffsetMap = this.state.pastCumulativeOffsetMap; + + if (stateOngoing.dragging) { + const startMousePosition = stateOngoing.startMousePosition as XYPair; + const currentMousePosition = stateOngoing.currentMousePosition as XYPair; + const draggableId = stateOngoing.draggableId as string; + const lastOffsetForCurrentDraggableId = lastOffsetMap[draggableId] || { x: 0, y: 0 }; + + let newOffsetMap = Object.assign({}, lastOffsetMap); + + newOffsetMap[draggableId] = { + x: lastOffsetForCurrentDraggableId.x + (currentMousePosition.x - startMousePosition.x), + y: lastOffsetForCurrentDraggableId.y + (currentMousePosition.y - startMousePosition.y), + }; + + return newOffsetMap; + } else { + return lastOffsetMap; + } + + } + + render() { + const CustomProvider = this.getContext().Provider; + const currentOffsetMap = this.getCurrentOffsetMap(); + + const providerValue: DragSharedInfo = { + dragStartHandler: this.handleDragStartOnDraggableNode, + offsetMap: currentOffsetMap + }; + + return ( + +
{ console.log('focus'); }} + // tslint:disable-next-line:no-console + onBlur={() => { console.log('blur'); }} + > + {this.props.children} +
+
+ ); + } +} \ No newline at end of file diff --git a/src/Components/DragContainer/index.ts b/src/Components/DragContainer/index.ts new file mode 100644 index 0000000..892af56 --- /dev/null +++ b/src/Components/DragContainer/index.ts @@ -0,0 +1 @@ +export { DragContainer } from './DragContainer'; \ No newline at end of file diff --git a/src/Components/DraggableNode/DraggableNode.css b/src/Components/DraggableNode/DraggableNode.css new file mode 100644 index 0000000..9383e29 --- /dev/null +++ b/src/Components/DraggableNode/DraggableNode.css @@ -0,0 +1,4 @@ +.draggable-node { + cursor: move; + user-select: none; +} \ No newline at end of file diff --git a/src/Components/DraggableNode/DraggableNode.tsx b/src/Components/DraggableNode/DraggableNode.tsx index 0165f03..648b59c 100644 --- a/src/Components/DraggableNode/DraggableNode.tsx +++ b/src/Components/DraggableNode/DraggableNode.tsx @@ -1,25 +1,51 @@ import * as React from 'react'; +import './DraggableNode.css'; +import { DragContextProvider, DragSharedInfo, XYPair } from '../../Helpers'; -interface OnClickProps { - onClick(event: React.MouseEvent): void; +interface DraggableNodeProps extends React.HTMLProps { + id: string; + containerId: string; } -export class DraggableNode extends React.Component { - getModifiedChildrenWithAddedClickHandler(): React.ReactElement[] { - return React.Children.map(this.props.children, (eachChild) => { - // tslint:disable-next-line:no-console - return React.cloneElement( - eachChild as React.ReactElement, - { - onClick: function (event: React.MouseEvent) { - // tslint:disable-next-line:no-console - console.log(arguments); - } - }); +export class DraggableNode extends React.Component { + + calculateTransform(offset: XYPair) { + return { transform: `translate(${offset.x}px, ${offset.y}px)` }; + } + + getContext(): React.Context { + return DragContextProvider.get(this.props.containerId); + } + + renderChildren(dragSharedInfo: DragSharedInfo | undefined): JSX.Element { + if (typeof dragSharedInfo === 'undefined') { + throw new TypeError('Context provider (DragContainer) absent in current JSX tree'); + } + + const wrappedDragStartHandler = ((ev: React.MouseEvent) => { + dragSharedInfo.dragStartHandler(this.props.id, ev); }); + + const offsetForThisNode: XYPair = (dragSharedInfo.offsetMap[this.props.id] || { x: 0, y: 0 }); + const tmpTransform = this.calculateTransform(offsetForThisNode); + + return ( +
+ {this.props.children} +
+ ); } render() { - return this.getModifiedChildrenWithAddedClickHandler(); + const CustomConsumer = this.getContext().Consumer; + + return ( + + {(dragSharedInfo) => { + return this.renderChildren(dragSharedInfo); + } + } + + ); } } \ No newline at end of file diff --git a/src/Helpers/DragContextProvider.ts b/src/Helpers/DragContextProvider.ts new file mode 100644 index 0000000..44522ea --- /dev/null +++ b/src/Helpers/DragContextProvider.ts @@ -0,0 +1,36 @@ +import * as React from 'react'; +import { XYPair } from './utils'; + +export interface DragSharedInfo { + dragStartHandler: DragStartFn; + offsetMap: { + [draggableId: string]: XYPair; + }; +} + +export type DragStartFn = (draggableId: string, ev: React.MouseEvent) => void; + +interface StringContextMap { + [key: string]: React.Context; +} + +export class DragContextProvider { + private static idContextMap: StringContextMap = { + + }; + + public static get(id: string): React.Context { + if (this.has(id)) { + return this.idContextMap[id]; + } else { + const newContext = React.createContext(undefined); + this.idContextMap[id] = newContext; + + return newContext; + } + } + + public static has(id: string): boolean { + return (id in this.idContextMap); + } +} \ No newline at end of file diff --git a/src/Helpers/index.ts b/src/Helpers/index.ts new file mode 100644 index 0000000..092b996 --- /dev/null +++ b/src/Helpers/index.ts @@ -0,0 +1,2 @@ +export { DragContextProvider, DragStartFn, DragSharedInfo } from './DragContextProvider'; +export { XYPair } from './utils'; \ No newline at end of file diff --git a/src/Helpers/utils.ts b/src/Helpers/utils.ts new file mode 100644 index 0000000..9e82d53 --- /dev/null +++ b/src/Helpers/utils.ts @@ -0,0 +1,4 @@ +export interface XYPair { + x: number; + y: number; +} \ No newline at end of file