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 (
-
+
-
- Welcome to React
+
+
+
+
+ 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