mirror of
https://github.com/bendtherules/piktochart-create-editor.git
synced 2026-08-18 13:52:56 +00:00
Complete DragContainer and DraggableNode components + Helpers
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
|
||||
.App-intro {
|
||||
font-size: large;
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
|
||||
@@ -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 (
|
||||
<div className="App">
|
||||
<DraggableNode>
|
||||
<DragContainer id="test">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<h1 className="App-title">Welcome to React</h1>
|
||||
</header>
|
||||
<DraggableNode id="test-img" containerId="test">
|
||||
<img src={logo} className="App-logo" alt="logo" draggable={false} />
|
||||
</DraggableNode>
|
||||
<DraggableNode id="test-text" containerId="test">
|
||||
<h1 className="App-title">Welcome to React</h1>
|
||||
</DraggableNode>
|
||||
</header>
|
||||
</DragContainer>
|
||||
<p className="App-intro">
|
||||
To get started, edit <code>src/App.tsx</code> and save to reload.
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.drag-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -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<DragContainerProps, DragContainerState> {
|
||||
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<HTMLDivElement>): 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<HTMLDivElement>) {
|
||||
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<HTMLDivElement>) {
|
||||
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<DragSharedInfo | undefined> {
|
||||
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 (
|
||||
<CustomProvider value={providerValue}>
|
||||
<div
|
||||
className="drag-container"
|
||||
onMouseMove={this.handleDragMove}
|
||||
onMouseUp={this.handleDragStop}
|
||||
// tslint:disable-next-line:no-console
|
||||
onFocus={() => { console.log('focus'); }}
|
||||
// tslint:disable-next-line:no-console
|
||||
onBlur={() => { console.log('blur'); }}
|
||||
>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</CustomProvider >
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { DragContainer } from './DragContainer';
|
||||
@@ -0,0 +1,4 @@
|
||||
.draggable-node {
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
}
|
||||
@@ -1,25 +1,51 @@
|
||||
import * as React from 'react';
|
||||
import './DraggableNode.css';
|
||||
import { DragContextProvider, DragSharedInfo, XYPair } from '../../Helpers';
|
||||
|
||||
interface OnClickProps {
|
||||
onClick(event: React.MouseEvent<HTMLDivElement>): void;
|
||||
interface DraggableNodeProps extends React.HTMLProps<HTMLDivElement> {
|
||||
id: string;
|
||||
containerId: string;
|
||||
}
|
||||
|
||||
export class DraggableNode extends React.Component {
|
||||
getModifiedChildrenWithAddedClickHandler(): React.ReactElement<OnClickProps>[] {
|
||||
return React.Children.map(this.props.children, (eachChild) => {
|
||||
// tslint:disable-next-line:no-console
|
||||
return React.cloneElement(
|
||||
eachChild as React.ReactElement<OnClickProps>,
|
||||
{
|
||||
onClick: function (event: React.MouseEvent<HTMLDivElement>) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(arguments);
|
||||
export class DraggableNode extends React.Component<DraggableNodeProps, {}> {
|
||||
|
||||
calculateTransform(offset: XYPair) {
|
||||
return { transform: `translate(${offset.x}px, ${offset.y}px)` };
|
||||
}
|
||||
|
||||
getContext(): React.Context<DragSharedInfo | undefined> {
|
||||
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<HTMLDivElement>) => {
|
||||
dragSharedInfo.dragStartHandler(this.props.id, ev);
|
||||
});
|
||||
});
|
||||
|
||||
const offsetForThisNode: XYPair = (dragSharedInfo.offsetMap[this.props.id] || { x: 0, y: 0 });
|
||||
const tmpTransform = this.calculateTransform(offsetForThisNode);
|
||||
|
||||
return (
|
||||
<div className="draggable-node" style={tmpTransform} onMouseDown={wrappedDragStartHandler}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.getModifiedChildrenWithAddedClickHandler();
|
||||
const CustomConsumer = this.getContext().Consumer;
|
||||
|
||||
return (
|
||||
<CustomConsumer>
|
||||
{(dragSharedInfo) => {
|
||||
return this.renderChildren(dragSharedInfo);
|
||||
}
|
||||
}
|
||||
</CustomConsumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<HTMLDivElement>) => void;
|
||||
|
||||
interface StringContextMap {
|
||||
[key: string]: React.Context<DragSharedInfo | undefined>;
|
||||
}
|
||||
|
||||
export class DragContextProvider {
|
||||
private static idContextMap: StringContextMap = {
|
||||
|
||||
};
|
||||
|
||||
public static get(id: string): React.Context<DragSharedInfo | undefined> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { DragContextProvider, DragStartFn, DragSharedInfo } from './DragContextProvider';
|
||||
export { XYPair } from './utils';
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface XYPair {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
Reference in New Issue
Block a user