From 48e86ac50c3d1c93c9bdde095c4f1bb4eab832c2 Mon Sep 17 00:00:00 2001 From: Alex Reardon Date: Fri, 22 Jun 2018 13:57:36 +1000 Subject: [PATCH] stable base --- src/state/create-store.js | 2 +- src/view/draggable/draggable.jsx | 6 +- src/view/moveable/moveable.draft.jsx | 126 +++++++++++++++++++++++++++ src/view/moveable/moveable.jsx | 64 +++++++------- src/view/moveable/moveable.old.jsx | 101 +++++++++++++++++++++ 5 files changed, 262 insertions(+), 37 deletions(-) create mode 100644 src/view/moveable/moveable.draft.jsx create mode 100644 src/view/moveable/moveable.old.jsx diff --git a/src/state/create-store.js b/src/state/create-store.js index 3ff41192b6..41b5e589e3 100644 --- a/src/state/create-store.js +++ b/src/state/create-store.js @@ -48,7 +48,7 @@ export default ({ // debugging timer // require('./debug-middleware/action-timing-middleware').default, // average action timer - // require('./debug-middleware/action-timing-average-middleware').default(500), + require('./debug-middleware/action-timing-average-middleware').default(500), // ## Application middleware diff --git a/src/view/draggable/draggable.jsx b/src/view/draggable/draggable.jsx index 371731b686..64764b31d9 100644 --- a/src/view/draggable/draggable.jsx +++ b/src/view/draggable/draggable.jsx @@ -287,14 +287,14 @@ export default class Draggable extends Component { return 'STANDARD'; } - // if dragging and can animate - then move quickly if (isDragging && shouldAnimateDragMovement) { return 'FAST'; } - // Animation taken care of by css + // Animating taken care of by CSS or not at all return 'INSTANT'; - }) + } + ) renderChildren = (movementStyle: MovementStyle, dragHandleProps: ?DragHandleProps): ?Node => { const { diff --git a/src/view/moveable/moveable.draft.jsx b/src/view/moveable/moveable.draft.jsx new file mode 100644 index 0000000000..1d5b7aa6de --- /dev/null +++ b/src/view/moveable/moveable.draft.jsx @@ -0,0 +1,126 @@ +// @flow +import React, { Component } from 'react'; +import memoizeOne from 'memoize-one'; +import { type Position } from 'css-box-model'; +import { Motion, spring } from 'react-motion'; +import { physics } from '../animation'; +import type { Props, DefaultProps, Style } from './moveable-types'; + +type PositionLike = {| + x: any, + y: any, +|}; + +const origin: Position = { + x: 0, + y: 0, +}; + +const noTransition: Style = { + transform: null, +}; + +const isAtOrigin = (x: mixed, y: mixed): boolean => + x === origin.x && y === origin.y; + +export default class Movable extends Component { + /* eslint-disable react/sort-comp */ + + static defaultProps: DefaultProps = { + destination: origin, + } + /* eslint-enable */ + + onRest = () => { + const { onMoveEnd } = this.props; + + if (!onMoveEnd) { + return; + } + + // This needs to be async otherwise Motion will not re-execute if + // offset or start change + + // Could check to see if another move has started + // and abort the previous onMoveEnd + setTimeout(onMoveEnd); + } + + getTranslate = memoizeOne((x: number, y: number): Style => ({ + transform: `translate(${x}px, ${y}px)`, + })) + + getFinal = (): PositionLike => { + const destination: Position = this.props.destination; + const speed = this.props.speed; + + if (speed === 'INSTANT') { + return destination; + } + + const selected = speed === 'FAST' ? physics.fast : physics.standard; + + return { + x: spring(destination.x, selected), + y: spring(destination.y, selected), + }; + } + + // renderChildren = (current: { [string]: number }): any => { + // const destination: Position = this.props.destination; + // // If moving instantly then we can just move straight to the destination + // // Sadly react-motion does a double call in this case so we need to explictly control this + // if (this.props.speed === 'INSTANT') { + // return this.props.children( + // this.getTranslate(destination.x, destination.y) + // ); + // } + + // // If moving to the origin we can just clear the transition + // if (isAtOrigin(current)) { + // return this.props.children(noTransition); + // } + + // // Rather than having a translate of 0px, 0px we just clear the transition + // if (isAtOrigin(destination)) { + // return this.props.children(noTransition); + // } + + // return this.props.children(this.getTranslate(current.x, current.y)); + // } + + render() { + const final: PositionLike = this.getFinal(); + const { speed, destination } = this.props; + const shouldIgnore: boolean = speed === 'INSTANT' && isAtOrigin(destination); + + // bug with react-motion: https://github.com/chenglou/react-motion/issues/437 + // even if both defaultStyle and style are {x: 0, y: 0 } if there was + // a previous animation it uses the last value rather than the final value + const isMovingToOrigin: boolean = isAtOrigin(final); + + return ( + + {(current: { [string]: number }): any => { + // the default for Draggables + if (shouldIgnore) { + return this.props.children(noTransition); + } + + if (speed === 'INSTANT') { + return this.props.children( + this.getTranslate(destination.x, destination.y) + ); + } + // Rather than having a translate of 0px, 0px we just clear the transition + // If moving to the origin we can just clear the transition + if (isAtOrigin(current)) { + return this.props.children(noTransition); + } + + return this.props.children(this.getTranslate(current.x, current.y)); + }} + + ); + } +} diff --git a/src/view/moveable/moveable.jsx b/src/view/moveable/moveable.jsx index 2a66ebb561..d3525a080e 100644 --- a/src/view/moveable/moveable.jsx +++ b/src/view/moveable/moveable.jsx @@ -1,6 +1,6 @@ // @flow import React, { Component } from 'react'; -import memoizeOne from 'memoize-one'; +import type { SpringHelperConfig } from 'react-motion/lib/Types'; import { type Position } from 'css-box-model'; import { Motion, spring } from 'react-motion'; import { physics } from '../animation'; @@ -16,17 +16,29 @@ const origin: Position = { y: 0, }; -const noTransition: Style = { +const noMovement: Style = { transform: null, }; -const getTranslate = memoizeOne((x: number, y: number): Style => ({ - transform: `translate(${x}px, ${y}px)`, -})); - -const isAtOrigin = (point: { [string]: number }): boolean => +const isAtOrigin = (point: PositionLike): boolean => point.x === origin.x && point.y === origin.y; +const getStyle = (isNotMoving: boolean, x: number, y: number): Style => { + if (isNotMoving) { + return noMovement; + } + + const point: Position = { x, y }; + // not applying any transforms when not moving + if (isAtOrigin(point)) { + return noMovement; + } + const style: Style = { + transform: `translate(${point.x}px, ${point.y}px)`, + }; + return style; +}; + export default class Movable extends Component { /* eslint-disable react/sort-comp */ @@ -47,7 +59,7 @@ export default class Movable extends Component { // Could check to see if another move has started // and abort the previous onMoveEnd - setTimeout(onMoveEnd); + setTimeout(() => onMoveEnd()); } getFinal = (): PositionLike => { @@ -58,11 +70,11 @@ export default class Movable extends Component { return destination; } - const selected = speed === 'FAST' ? physics.fast : physics.standard; + const config: SpringHelperConfig = speed === 'FAST' ? physics.fast : physics.standard; return { - x: spring(destination.x, selected), - y: spring(destination.y, selected), + x: spring(destination.x, config), + y: spring(destination.y, config), }; } @@ -72,31 +84,17 @@ export default class Movable extends Component { // bug with react-motion: https://github.com/chenglou/react-motion/issues/437 // even if both defaultStyle and style are {x: 0, y: 0 } if there was // a previous animation it uses the last value rather than the final value - const isMovingToOrigin: boolean = isAtOrigin(final); + const isNotMoving: boolean = isAtOrigin(final); return ( + // Expecting a flow error + // React Motion type: children: (interpolatedStyle: PlainStyle) => ReactElement + // Our type: children: (Position) => (Style) => React.Node - {(current: { [string]: number }): any => { - // If moving instantly then we can just move straight to the destination - // Sadly react-motion does a double call in this case so we need to explictly control this - if (this.props.speed === 'INSTANT') { - return this.props.children( - getTranslate(this.props.destination.x, this.props.destination.y) - ); - } - - // If moving to the origin we can just clear the transition - if (isMovingToOrigin) { - return this.props.children(noTransition); - } - - // Rather than having a translate of 0px, 0px we just clear the transition - if (isAtOrigin(current)) { - return this.props.children(noTransition); - } - - return this.props.children(getTranslate(current.x, current.y)); - }} + {(current: { [string]: number }): any => + this.props.children( + getStyle(isNotMoving, current.x, current.y) + )} ); } diff --git a/src/view/moveable/moveable.old.jsx b/src/view/moveable/moveable.old.jsx new file mode 100644 index 0000000000..d3525a080e --- /dev/null +++ b/src/view/moveable/moveable.old.jsx @@ -0,0 +1,101 @@ +// @flow +import React, { Component } from 'react'; +import type { SpringHelperConfig } from 'react-motion/lib/Types'; +import { type Position } from 'css-box-model'; +import { Motion, spring } from 'react-motion'; +import { physics } from '../animation'; +import type { Props, DefaultProps, Style } from './moveable-types'; + +type PositionLike = {| + x: any, + y: any, +|}; + +const origin: Position = { + x: 0, + y: 0, +}; + +const noMovement: Style = { + transform: null, +}; + +const isAtOrigin = (point: PositionLike): boolean => + point.x === origin.x && point.y === origin.y; + +const getStyle = (isNotMoving: boolean, x: number, y: number): Style => { + if (isNotMoving) { + return noMovement; + } + + const point: Position = { x, y }; + // not applying any transforms when not moving + if (isAtOrigin(point)) { + return noMovement; + } + const style: Style = { + transform: `translate(${point.x}px, ${point.y}px)`, + }; + return style; +}; + +export default class Movable extends Component { + /* eslint-disable react/sort-comp */ + + static defaultProps: DefaultProps = { + destination: origin, + } + /* eslint-enable */ + + onRest = () => { + const { onMoveEnd } = this.props; + + if (!onMoveEnd) { + return; + } + + // This needs to be async otherwise Motion will not re-execute if + // offset or start change + + // Could check to see if another move has started + // and abort the previous onMoveEnd + setTimeout(() => onMoveEnd()); + } + + getFinal = (): PositionLike => { + const destination: Position = this.props.destination; + const speed = this.props.speed; + + if (speed === 'INSTANT') { + return destination; + } + + const config: SpringHelperConfig = speed === 'FAST' ? physics.fast : physics.standard; + + return { + x: spring(destination.x, config), + y: spring(destination.y, config), + }; + } + + render() { + const final = this.getFinal(); + + // bug with react-motion: https://github.com/chenglou/react-motion/issues/437 + // even if both defaultStyle and style are {x: 0, y: 0 } if there was + // a previous animation it uses the last value rather than the final value + const isNotMoving: boolean = isAtOrigin(final); + + return ( + // Expecting a flow error + // React Motion type: children: (interpolatedStyle: PlainStyle) => ReactElement + // Our type: children: (Position) => (Style) => React.Node + + {(current: { [string]: number }): any => + this.props.children( + getStyle(isNotMoving, current.x, current.y) + )} + + ); + } +}