mysticPrg/catch-light

View on GitHub
src/components/light/Light.js

Summary

Maintainability
A
0 mins
Test Coverage
import React, { Component } from 'react';

import { getRandomNumber } from '~/utils/common';
import { combineChecker, rangeCheck, compareWithOtherProps } from '~/utils/propTypeChecker';

const rangeCheck0to1000 = rangeCheck(0, 1000);
const compareWithMax = (min, max, component) => {
    if ( min > max ) {
        return new Error(`${component}: Min(${min}) is greater than max(${max}).`);
    }
};
const compareCheckerForXmin = combineChecker(rangeCheck0to1000, compareWithOtherProps('x-max', compareWithMax));
const compareCheckerForYmin = combineChecker(rangeCheck0to1000, compareWithOtherProps('y-max', compareWithMax));

class Light extends Component {
    constructor(props) {
        super(props);

        this.state = {
            x: getRandomNumber(props['x-min'], props['x-max']),
            y: getRandomNumber(props['y-min'], props['y-max']),
            alpha: 0
        };

        this.onClick = this.onClick.bind(this);
        this.alphaAddFactor = 1.0 / (this.props.fps - 0.01);
        this.speed = 0.15 * this.props.speed;
    }

    componentDidMount() {
        this.intervalKey = setInterval(() => {
            this.animate();
            this.props.onAnimate();
        }, 1000/this.props.fps);
    }

    componentWillUnmount() {
        clearInterval(this.intervalKey);
    }
    
    animate() {
        const x = this.state.x;
        const y = this.state.y;
        const next = {
            alpha: this.state.alpha + this.alphaAddFactor,
            x: x + this.speed * (this.props['target-x'] - x),
            y: y + this.speed * (this.props['target-y'] - y)
        };

        if ( next.alpha > this.props.alpha ) {
            next.alpha = this.props.alpha;
        }
        
        this.setState({
            alpha: next.alpha,
            x: parseFloat(next.x.toFixed(3)),
            y: parseFloat(next.y.toFixed(3))
        });
    }

    onClick() {
        this.props.onClick(this.props.id);
    }

    render() {
        this.speed = 0.15 * this.props.speed;
        const style = {
            position: 'absolute',
            left: `${this.state.x}px`,
            top: `${this.state.y}px`,
            backgroundColor: this.props.color,
            width: `${this.props.size}px`,
            height: `${this.props.size}px`,
            opacity: this.state.alpha,
            borderRadius: '50%',
            cursor: 'pointer'
        };

        return (
            <div
                style={style}
                data-test="light"
                onClick={this.onClick}
            />
        );
    }
}

Light.defaultProps = {
    'x-min': 0,
    'x-max': 1,
    'y-min': 0,
    'y-max': 1,

    color: '#FFFFFF',
    size: 10,
    alpha: 1.0,
    fps: 30,
    speed: 1.0,
    
    'target-x': 0,
    'target-y': 0,
    
    onAnimate: () => {},
    onClick: () => {},

    id: undefined
};

Light.propTypes = {
    'x-min': compareCheckerForXmin,
    'x-max': rangeCheck0to1000,
    'y-min': compareCheckerForYmin,
    'y-max': rangeCheck0to1000
};

export default Light;