Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
header.react.js 7.41 KiB
'use strict';
/*global setTimeout, clearTimeout*/

import ActionButton from './action_button.react';
import AddToDashboard from './add_to_dashboard.react';
import Icon from './icon.react';
import Popover from './popover.react';
import QueryModeToggle from './query_mode_toggle.react';
import Saver from './saver.react';

var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;

export default React.createClass({
    displayName: 'QueryHeader',
    propTypes: {
        card: React.PropTypes.object.isRequired,
        cardApi: React.PropTypes.func.isRequired,
        dashboardApi: React.PropTypes.func.isRequired,
        notifyCardChangedFn: React.PropTypes.func.isRequired,
        setQueryModeFn: React.PropTypes.func.isRequired,
        downloadLink: React.PropTypes.string
    },

    getInitialState: function() {
        return {
            origCard: JSON.stringify(this.props.card),
            recentlySaved: false,
            resetOrigCard: false
        };
    },

    componentWillReceiveProps: function(nextProps) {
        // pre-empt a card update via props
        // we need this here for a specific case where we know the card will be changing
        // and thus we need to reset our :origCard state BEFORE our next render cycle
        if (this.state.resetOrigCard) {
            this.setState({
                origCard: JSON.stringify(nextProps.card),
                recentlySaved: false,
                resetOrigCard: false
            });
        }
    },

    cardIsNew: function() {
        // a card is considered new if it has not ID associated with it
        return (this.props.card.id === undefined);
    },

    cardIsDirty: function() {
        // a card is considered dirty if ANY part of it has been changed
        return (JSON.stringify(this.props.card) !== this.state.origCard);
    },

    resetStateOnTimeout: function() {
        // clear any previously set timeouts then start a new one
        clearTimeout(this.timeout);

        var component = this;
        this.timeout = setTimeout(function() {
            if (component.isMounted()) {
                component.setState({
                    recentlySaved: false
                });
            }
        }.bind(component), 5000);
    },

    save: function() {
        return this.saveCard(this.props.card);
    },

    saveCard: function(card) {
        var component = this,
            apiCall;
        if (card.id === undefined) {
            // creating a new card
            apiCall = this.props.cardApi.create(card, function (newCard) {
                if (component.isMounted()) {
                    component.props.notifyCardCreatedFn(newCard);

                    // update local state to reflect new card state
                    component.setState({
                        origCard: JSON.stringify(card),
                        recentlySaved: true
                    }, component.resetStateOnTimeout);
                }
            });

        } else {
            // updating an existing card
            apiCall = this.props.cardApi.update(card, function (updatedCard) {
                if (component.isMounted()) {
                    component.props.notifyCardUpdatedFn(updatedCard);

                    // update local state to reflect new card state
                    component.setState({
                        origCard: JSON.stringify(card),
                        recentlySaved: true
                    }, component.resetStateOnTimeout);
                }
            });
        }

        return apiCall.$promise;
    },

    setQueryMode: function(mode) {
        // we need to update our dirty state here
        var component = this;
        this.setState({
            resetOrigCard: true
        }, function() {
            component.props.setQueryModeFn(mode);
        });
    },
    permissions: function() {
        var text;
        switch(this.props.card.public_perms) {
            case 0:
                text = 'Private';
                break;
            case 1:
                text = 'Others can read';
                break;
            case 2:
                text = 'Others can modify';
                break;
            default:
                return 'Error';
        }
        return (
            <span className="Badge Badge--permissions ml2">
                {text}
            </span>
        );
    },

    render: function() {
        var title = this.props.card.name || "What would you like to know?";

        var editButton;
        if (!this.cardIsNew()) {
            editButton = (
                <Saver
                    card={this.props.card}
                    saveFn={this.props.notifyCardChangedFn}
                    saveButtonText="Done"
                    className='inline-block ml1 link'
                />
            );
        }

        var saveButton;
        if (this.cardIsNew() && this.cardIsDirty()) {
            // new cards get a custom treatment, like saving a new Excel document
            saveButton = (
                <Saver
                    card={this.props.card}
                    saveFn={this.saveCard}
                    buttonText="Save"
                    saveButtonText="Create card"
                />
            );
        } else if (this.cardIsDirty() || this.state.recentlySaved) {
            // for existing cards we render a very simply ActionButton
            saveButton = (
                <ActionButton
                    actionFn={this.save}
                    className='Button Button--primary'
                />
            );
        }

        // NOTE: we expect our component provider set this to something falsey if download not available
        var downloadButton;
        if (this.props.downloadLink) {
            downloadButton = (
                <a className="mx1" href={this.props.downloadLink} title="Download this data" target="_blank">
                    <Icon name='download'>
                        <Popover>
                            <span>Download data</span>
                        </Popover>
                    </Icon>
                </a>
            );
        }

        var queryModeToggle;
        if (this.cardIsNew() && !this.cardIsDirty()) {
            queryModeToggle = (
                <QueryModeToggle
                    currentQueryMode={this.props.card.dataset_query.type}
                    setQueryModeFn={this.setQueryMode}
                />
            );
        }

        return (
            <div className="QueryHeader QueryBuilder-section flex align-center">
                <div className="QueryHeader-details">
                    <h1 className="QueryName">{title}</h1>
                    {this.permissions()}
                    {editButton}
                </div>

                <div className="QueryHeader-actions">
                    <ReactCSSTransitionGroup transitionName="Transition-qb-section">
                        {saveButton}
                    </ReactCSSTransitionGroup>
                    <ReactCSSTransitionGroup transitionName="Transition-qb-section">
                        {downloadButton}
                    </ReactCSSTransitionGroup>
                    <AddToDashboard
                        card={this.props.card}
                        dashboardApi={this.props.dashboardApi}
                    />
                    <ReactCSSTransitionGroup transitionName="Transition-qb-querybutton">
                        {queryModeToggle}
                    </ReactCSSTransitionGroup>
                </div>
            </div>
        );
    }
});