tsg-ut/mnemo

View on GitHub
lib/data-component.jsx

Summary

Maintainability
B
5 hrs
Test Coverage
const React = require('react');
const PropTypes = require('prop-types');
const GSAP = require('react-gsap-enhancer');
const assert = require('assert');
const {TweenLite, Power0} = require('gsap');
const {default: Measure} = require('react-measure');
const {BLOCK_SIZE} = require('./constants');

class DataComponent extends React.Component {
    static propTypes = {
        direction: PropTypes.string.isRequired,
        isInward: PropTypes.bool.isRequired,
        isAnimating: PropTypes.bool.isRequired,
        isErasing: PropTypes.bool.isRequired,
        value: PropTypes.number.isRequired,
        // props.data should only be used in callback
        // eslint-disable-next-line react/forbid-prop-types
        data: PropTypes.object.isRequired,
        onAnimationComplete: PropTypes.func.isRequired,
        onEraseAnimationComplete: PropTypes.func.isRequired,
        isRapid: PropTypes.bool.isRequired,
        viewBoxScale: PropTypes.number,
    }

    static defaultProps = {
        viewBoxScale: null,
    }

    constructor(props, state) {
        super(props, state);

        this.state = {
            valueDimensions: null,
        };
    }

    componentDidMount() {
        if (this.props.isAnimating) {
            this.handleStartAnimation();
        }

        if (this.props.isErasing) {
            this.handleStartErasion();
        }
    }

    componentDidUpdate(prevProps) {
        if (!prevProps.isAnimating && this.props.isAnimating) {
            this.handleStartAnimation();
        }

        if (prevProps.isAnimating && !this.props.isAnimating) {
            this.handleStopAnimation();
        }

        if (!prevProps.isErasing && this.props.isErasing) {
            this.handleStartErasion();
        }

        if (prevProps.isErasing && !this.props.isErasing) {
            this.handleStopErasion();
        }

        if (!prevProps.isRapid && this.props.isRapid) {
            this.handleStartRapid();
        }
    }

    handleStartAnimation = () => {
        const duration = this.props.isRapid ? 0 : 0.4;

        this.animation = this.addAnimation(({target}) => (
            TweenLite.to(target, duration, Object.assign({
                transformOrigin: 'center center',
                ease: Power0.easeNone,
                onComplete: () => {
                    setTimeout(() => {
                        this.props.onAnimationComplete(this.props.data);
                    }, 0);
                },
            }, this.getAnimationProperties()))
        ));
    }

    handleStopAnimation = () => {
        this.animation.pause();
    }

    handleStartErasion = () => {
        if (this.animation) {
            this.animation.pause();
        }

        const duration = this.props.isRapid ? 0 : 0.4;

        this.erasion = this.addAnimation(({target}) => (
            TweenLite.to(target, duration, Object.assign({
                transformOrigin: 'center center',
                ease: Power0.easeNone,
                scale: 2,
                opacity: 0,
                onComplete: () => {
                    setTimeout(() => {
                        this.props.onEraseAnimationComplete(this.props.data);
                    }, 0);
                },
            }))
        ));
    }

    handleStopErasion = () => {
        this.erasion.pause();
    }

    handleStartRapid = () => {
        if (this.animation) {
            this.animation.seek(this.animation.duration(), false);
        }

        if (this.erasion) {
            this.erasion.seek(this.erasion.duration(), false);
        }
    }

    handleMeasureValue = (dimensions) => {
        if (!this.props.isErasing) {
            this.setState({
                valueDimensions: dimensions.bounds,
            });
        }
    }

    getAnimationProperties = () => {
        if (this.props.isInward) {
            if (this.props.direction === 'top') {
                return {y: `+=${BLOCK_SIZE / 2}`};
            }

            if (this.props.direction === 'right') {
                return {x: `-=${BLOCK_SIZE / 2}`};
            }

            if (this.props.direction === 'left') {
                return {x: `+=${BLOCK_SIZE / 2}`};
            }

            assert(this.props.direction === 'bottom');
            return {y: `-=${BLOCK_SIZE / 2}`};
        }

        if (this.props.direction === 'top') {
            return {y: `-=${BLOCK_SIZE / 2}`};
        }

        if (this.props.direction === 'right') {
            return {x: `+=${BLOCK_SIZE / 2}`};
        }

        if (this.props.direction === 'left') {
            return {x: `-=${BLOCK_SIZE / 2}`};
        }

        assert(this.props.direction === 'bottom');
        return {y: `+=${BLOCK_SIZE / 2}`};
    }

    getDisplayValue = () => {
        if (this.props.value === Infinity) {
            return '∞';
        }

        if (this.props.value === -Infinity) {
            return '-∞';
        }

        return this.props.value.toString();
    }

    getInitialTransform = () => {
        if (this.props.isInward) {
            if (this.props.direction === 'top') {
                return `translate(${BLOCK_SIZE / 2}, 0)`;
            }

            if (this.props.direction === 'right') {
                return `translate(${BLOCK_SIZE}, ${BLOCK_SIZE / 2})`;
            }

            if (this.props.direction === 'left') {
                return `translate(0, ${BLOCK_SIZE / 2})`;
            }

            assert(this.props.direction === 'bottom');
            return `translate(${BLOCK_SIZE / 2}, ${BLOCK_SIZE})`;
        }

        return `translate(${BLOCK_SIZE / 2}, ${BLOCK_SIZE / 2})`;
    }

    getRectangleWidth = () => (
        (this.state.valueDimensions !== null && this.props.viewBoxScale !== null)
            ? this.state.valueDimensions.width / this.props.viewBoxScale + 4
            : (this.getDisplayValue().length * 6 + 4)
    )

    render() {
        return (
            <g transform={this.getInitialTransform()}>
                <rect
                    x={-this.getRectangleWidth() / 2}
                    y="-8"
                    rx="3"
                    width={this.getRectangleWidth()}
                    height="16"
                    fill="darkorange"
                />
                <Measure
                    onResize={this.handleMeasureValue}
                    bounds
                >
                    {({measureRef}) => (
                        <text
                            ref={measureRef}
                            x="0"
                            y="0"
                            fontSize="12"
                            fill="white"
                            textAnchor="middle"
                            dominantBaseline="central"
                        >
                            {this.getDisplayValue()}
                        </text>
                    )}
                </Measure>
            </g>
        );
    }
}

module.exports = GSAP.default()(DataComponent);