toggle-corp/react-store

View on GitHub
components/Visualization/VizWrapper/index.js

Summary

Maintainability
B
6 hrs
Test Coverage
import React from 'react';
import PropTypes from 'prop-types';
import { _cs } from '@togglecorp/fujs';

import FullScreen from '../FullScreen';
import ColorPalette from '../ColorPalette';

import SelectInput from '../../Input/SelectInput';
import AccentButton from '../../Action/Button/AccentButton';
import PrimaryButton from '../../Action/Button/PrimaryButton';
import DangerButton from '../../Action/Button/DangerButton';
import Button from '../../Action/Button';
import LoadingAnimation from '../../View/LoadingAnimation';

import Label from '../../Input/Label';

import {
    getCategoricalColorNames,
    getDivergingColorNames,
    getSequentialColorNames,
    getCategoricalColorScheme,
    getDivergingColorScheme,
    getSequentialColorScheme,
    getCategoryForContinuousColorScheme,
} from '../../../utils/ColorScheme';

import styles from './styles.scss';

const propTypes = {
    /**
     * if true, loading animation is show instead of actual chart
     */
    loading: PropTypes.bool,
    /**
     * Header text for the warper component
     */
    headerText: PropTypes.string,
    /**
     * Array of colors as hex color codes
     */
    colorScheme: PropTypes.arrayOf(PropTypes.string),
    /**
     * one of 'discrete' or 'continuous'
     */
    colorSchemeType: PropTypes.string,
    /**
     * Additional css classes for graph passed from parent
     */
    className: PropTypes.string,
    /**
     * Additional css classes for the container/wrapper
     */
    vizContainerClass: PropTypes.string,

    showBackButton: PropTypes.bool,
};

const defaultProps = {
    showBackButton: false,
    className: undefined,
    vizContainerClass: undefined,
    colorScheme: undefined,
    colorSchemeType: undefined,
    loading: false,
    headerText: '',
};

const continuous = 'continuous';
const discrete = 'discrete';

/**
 * Wrapper component to wrap each Visualization component so that it can be set to  fullscreen and
 * additional options can be provided to the child component (chart).
 */
const wrapViz = (WrappedComponent) => {
    const WrapperComponent = class extends React.PureComponent {
        static propTypes = propTypes;

        static defaultProps = defaultProps;

        static keySelector = d => d.title;

        static labelSelector = d => d.title;

        static optionLabelSelector = d => d.image;


        constructor(props) {
            super(props);
            const { colorScheme, colorSchemeType } = this.props;
            this.state = {
                colorScheme,
                colorSchemeType,
                fullScreen: false,
                colorSchemeName: undefined,
                colorSchemeOptions: this.getColorSchemeValues(colorSchemeType),
            };
        }

        // eslint-disable-next-line camelcase
        UNSAFE_componentWillReceiveProps(newProps) {
            const {
                colorScheme: oldColorScheme,
                colorSchemeType: oldColorSchemeType,
            } = this.props;

            const {
                colorScheme,
                colorSchemeType,
            } = newProps;

            if (colorScheme !== oldColorScheme) {
                this.setState({
                    colorScheme,
                });
            }
            if (colorSchemeType !== oldColorSchemeType) {
                this.setState({
                    colorSchemeType,
                    colorSchemeOptions: this.getColorSchemeValues(newProps.colorSchemeType),
                });
            }
        }

        getColorSchemeValues = (type) => {
            const mapContinuous = color => ({
                id: color,
                title: color,
                image: <ColorPalette
                    colorScheme={getCategoryForContinuousColorScheme(color)}
                />,
            });

            const mapDiscrete = color => ({
                id: color,
                title: color,
                image: <ColorPalette
                    colorScheme={getCategoricalColorScheme(color)
                        || getCategoryForContinuousColorScheme(color)}
                />,
            });

            if (type === continuous) {
                return getDivergingColorNames()
                    .concat(getSequentialColorNames())
                    .map(color => mapContinuous(color));
            }
            if (type === discrete) {
                return getCategoricalColorNames()
                    .map(color => mapDiscrete(color));
            }
            return getCategoricalColorNames()
                .concat(getDivergingColorNames(), getSequentialColorNames())
                .map(color => mapDiscrete(color));
        }

        setFullScreen = () => {
            this.setState({
                fullScreen: true,
            });
        }

        setSaveFunction = (saveFn) => {
            this.save = saveFn;
        }

        handleSave = () => {
            this.save();
        }

        removeFullScreen = () => {
            this.setState({
                fullScreen: false,
            });
        }

        handleSelection = (data) => {
            let colorScheme;
            const { colorSchemeType } = this.state;

            if (colorSchemeType === continuous) {
                colorScheme = getSequentialColorScheme(data) || getDivergingColorScheme(data);
            } else if (colorSchemeType === discrete) {
                colorScheme = getCategoricalColorScheme(data);
            } else {
                colorScheme = getCategoricalColorScheme(data)
                    || getCategoryForContinuousColorScheme(data);
            }

            this.setState({
                colorSchemeName: data,
                colorScheme,
            });
        }

        renderHeader = ({ fullScreen }) => {
            const {
                headerText,
                // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
                colorScheme: capturedColorScheme,
                showBackButton,
            } = this.props;

            const {
                handleSelection,
                handleSave,
                setFullScreen,
                removeFullScreen,
            } = this;

            const {
                colorSchemeName,
                colorSchemeOptions,
            } = this.state;

            // FIXME: use strings
            const colorSchemeLabelText = 'Color scheme';

            return (
                <div className={styles.header}>
                    <h3>
                        {(showBackButton && fullScreen) && (
                            <Button
                                title="Go back"
                                onClick={removeFullScreen}
                                iconName="back"
                                transparent
                            />
                        )}
                        {headerText}
                    </h3>
                    <div className={styles.rightContent}>
                        <div className={styles.colorSchemeInputContainer}>
                            <Label
                                show
                                text={colorSchemeLabelText}
                                className={styles.colorSchemeLabel}
                            />
                            <SelectInput
                                clearable={false}
                                keySelector={WrapperComponent.keySelector}
                                labelSelector={WrapperComponent.labelSelector}
                                optionLabelSelector={WrapperComponent.optionLabelSelector}
                                optionsClassName={styles.selectInputOptions}
                                onChange={handleSelection}
                                options={colorSchemeOptions}
                                showHintAndError={false}
                                showLabel={false}
                                className={styles.selectInput}
                                value={colorSchemeName}
                            />
                        </div>
                        <div className={styles.actionButtons}>
                            <PrimaryButton
                                title="Download diagram"
                                onClick={handleSave}
                                iconName="download"
                                transparent
                            />
                            {(fullScreen && !showBackButton) && (
                                <DangerButton
                                    title="Close fullscreen"
                                    onClick={removeFullScreen}
                                    iconName="close"
                                    transparent
                                />
                            )}
                            {!fullScreen && (
                                <AccentButton
                                    title="Show on fullscreen"
                                    onClick={setFullScreen}
                                    iconName="expand"
                                    transparent
                                />
                            )}
                        </div>
                    </div>
                </div>
            );
        }

        render() {
            const {
                className,
                loading,
                headerText, // eslint-disable-line no-unused-vars, @typescript-eslint/no-unused-vars
                vizContainerClass,
                // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars,
                colorScheme: capturedColorScheme,
                ...otherProps
            } = this.props;

            const {
                fullScreen,
                colorScheme,
            } = this.state;

            const Header = this.renderHeader;

            return (
                <div className={_cs(styles.diagramView, className)}>
                    { loading && <LoadingAnimation /> }
                    <Header fullScreen={fullScreen} />
                    {
                        fullScreen ? (
                            <FullScreen className={styles.fullScreenContainer}>
                                <Header fullScreen />
                                <div className={_cs(vizContainerClass)}>
                                    <WrappedComponent
                                        className={styles.diagram}
                                        colorScheme={colorScheme}
                                        setSaveFunction={this.setSaveFunction}
                                        {...otherProps}
                                    />
                                </div>
                            </FullScreen>
                        ) : (
                            <div className={_cs(vizContainerClass)}>
                                <WrappedComponent
                                    className={styles.diagram}
                                    colorScheme={colorScheme}
                                    setSaveFunction={this.setSaveFunction}
                                    {...otherProps}
                                />
                            </div>
                        )
                    }
                </div>
            );
        }
    };

    return WrapperComponent;
};

export default wrapViz;