From e04f2fa36887b89fef00e77dedf2cbf99ad1d47a Mon Sep 17 00:00:00 2001 From: Aaron Boodman Date: Mon, 15 Feb 2021 21:50:45 -1000 Subject: [PATCH] Add very, very slow drag+drop. We push all state changes through Replicache to gain access to its conflict resolution. If another client is moving an object at the same time, we should merge their movements, or something otherwise reasonable should happen. Replicache is not yet optimized for this type of usage - all writes go to disk. So it's not very pretty right now, but it's correct. Will fix Replicache subsequently. --- src/Designer2.tsx | 59 ++++++++++++++++++++++++++++++++++++++++--- src/Handler2.tsx | 22 +++++++++++----- src/objects/Rect2.tsx | 12 ++++++--- 3 files changed, 79 insertions(+), 14 deletions(-) diff --git a/src/Designer2.tsx b/src/Designer2.tsx index 1977c13..fe272f3 100644 --- a/src/Designer2.tsx +++ b/src/Designer2.tsx @@ -1,4 +1,4 @@ -import React, {CSSProperties, useState} from 'react'; +import React, {CSSProperties, MouseEvent, useState} from 'react'; import {Rect2} from './objects/Rect2'; import {Handler} from './Handler2'; import {Data} from './data'; @@ -9,12 +9,63 @@ export function Designer2({data}: {data: Data}) { // TODO: This should be stored in Replicache too, since we will be rendering // other users' selections. const [selectedID, setSelectedID] = useState(''); + const [lastDrag, setLastDrag] = useState(null); - return
- setSelectedID('')}}/> + const onMouseEnter = (e: MouseEvent, id: string) => { + if (fromClass(e, 'shape')) { + if (lastDrag == null) { + setSelectedID(id); + } + } + }; + + const onMouseLeave = (e: MouseEvent) => { + if (fromClass(e, 'handler')) { + if (lastDrag == null) { + setSelectedID(''); + } + } + }; + + const onMouseDown = (e: MouseEvent) => { + updateLastDrag(e); + }; + + const onMouseMove = (e: MouseEvent) => { + if (lastDrag == null) { + return; + } + data.moveShape({ + id: selectedID, + dx: e.clientX - lastDrag.x, + dy: e.clientY - lastDrag.y + }); + updateLastDrag(e); + }; + + const onMouseUp = (e: MouseEvent) => { + setLastDrag(null); + }; + + const fromClass = (e: MouseEvent, className: string) => { + const target = e.target as HTMLElement; + return target.classList.contains(className); + }; + + const updateLastDrag = (e: MouseEvent) => { + setLastDrag({x: e.clientX, y: e.clientY}); + } + + return
+ {ids.map( - id => setSelectedID(id)}}/>)} + id => onMouseEnter(e, id)}}/>)}
; } diff --git a/src/Handler2.tsx b/src/Handler2.tsx index 7e919d6..d8b870e 100644 --- a/src/Handler2.tsx +++ b/src/Handler2.tsx @@ -1,13 +1,19 @@ -import React, {CSSProperties} from 'react'; +import React, {CSSProperties, MouseEventHandler} from 'react'; import {Data} from './data'; -export function Handler({data, selectedID, onMouseLeave}: {data: Data, selectedID: string, onMouseLeave: () => void}) { +export function Handler({data, selectedID, onMouseDown, onMouseLeave}: + { + data: Data, + selectedID: string, + onMouseDown: MouseEventHandler, + onMouseLeave: MouseEventHandler, + }) { const shape = data.useShapeByID(selectedID); if (!shape) { return null; } - let handlerStyle = { + const handlerStyle = { ...styles.handler, width: shape.width + 4, height: shape.height + 4, @@ -16,10 +22,12 @@ export function Handler({data, selectedID, onMouseLeave}: {data: Data, selectedI transform: `rotate(${shape.rotate}deg)` }; - return
-
; + return
; } const styles = { diff --git a/src/objects/Rect2.tsx b/src/objects/Rect2.tsx index cbf319c..1a0a6bc 100644 --- a/src/objects/Rect2.tsx +++ b/src/objects/Rect2.tsx @@ -1,11 +1,17 @@ -import React from 'react'; +import React, { MouseEventHandler } from 'react'; import {Data} from '../data'; import {getObjectAttributes} from './attribs'; -export function Rect2({data, id, onMouseEnter}: {data: Data, id: string, onMouseEnter: () => void}) { +export function Rect2( + {data, id, onMouseEnter}: { + data: Data, + id: string, + onMouseEnter: MouseEventHandler + }) +{ const shape = data.useShapeByID(id); if (!shape) { return null; } - return ; + return ; }