<commit_before>import React, { PropTypes, Component } from 'react';
import TopNavBar from '../components/ui/TopNavBar';
import ToolBar from '../components/ui/ToolBar';
import CardContentList from '../components/interests/CardContentList';
import CardContentCarousel from '../components/interests/CardContentCarousel';
import FilterContentButtons from '../components/ui/FilterContentButtons';
import TextRadios from '../components/ui/TextRadios';
import ProfilesAvatarConnection from '../components/ui/ProfilesAvatarConnection';
import AuthenticatedComponent from '../components/AuthenticatedComponent';
import translate from '../i18n/Translate';
import connectToStores from '../utils/connectToStores';
import * as UserActionCreators from '../actions/UserActionCreators';
import * as InterestsActionCreators from '../actions/InterestsActionCreators';
import UserStore from '../stores/UserStore';
import InterestStore from '../stores/InterestStore';

function parseId(user) {
    return user.id;
}

function requestData(props) {
    const userId = parseId(props.user);
    UserActionCreators.requestUser(parseInt(props.params.userId), ['username', 'photo', 'status']);
    InterestsActionCreators.resetInterests(parseInt(props.params.userId));
    InterestsActionCreators.requestComparedInterests(userId, parseInt(props.params.userId), 'Link', 1);
}

function getState(props) {
    const otherUserId = props.params.userId;
    const pagination = InterestStore.getPagination(otherUserId) || {};
    const totals = InterestStore.getTotals(otherUserId) || {};
    const interests = InterestStore.get(otherUserId) || [];
    const noInterests = InterestStore.noInterests(otherUserId) || false;
    const isLoadingComparedInterests = InterestStore.isLoadingComparedInterests();
    const otherUser = UserStore.get(otherUserId);
    return {
        pagination,
        totals,
        interests,
        noInterests,
        isLoadingComparedInterests,
        otherUser
    };
}

@AuthenticatedComponent
@translate('OtherInterestsPage')
@connectToStores([UserStore, InterestStore], getState)
export default class OtherInterestsPage extends Component {
    static propTypes = {
        // Injected by React Router:
        params                    : PropTypes.shape({
            userId: PropTypes.string.isRequired
        }).isRequired,
        // Injected by @AuthenticatedComponent
        user                      : PropTypes.object.isRequired,
        // Injected by @translate:
        strings                   : PropTypes.object,
        // Injected by @connectToStores:
        pagination                : PropTypes.object,
        totals                    : PropTypes.object,
        interests                 : PropTypes.array.isRequired,
        isLoadingComparedInterests: PropTypes.bool,
        otherUser                 : PropTypes.object
    };

    constructor(props) {

        super(props);

        this.onFilterCommonClick = this.onFilterCommonClick.bind(this);
        this.onFilterTypeClick = this.onFilterTypeClick.bind(this);
        this.handleScroll = this.handleScroll.bind(this);
        this.onContentClick = this.onContentClick.bind(this);
        this.onNavBarLeftLinkClick = this.onNavBarLeftLinkClick.bind(this);
        this.initSwiper = this.initSwiper.bind(this);

        this.state = {
            type         : '',
            commonContent: 1,
            carousel     : false,
            position     : 0,
            swiper       : null
        }
    }

    componentWillMount() {
        requestData(this.props);
    }

    componentWillUnmount() {
        document.getElementsByClassName('view')[0].removeEventListener('scroll', this.handleScroll);
    }

    componentDidUpdate() {
        if (!this.state.carousel || this.props.interests.length == 0) {
            return;
        }
        if (!this.state.swiper) {
            this.state.swiper = this.initSwiper();
            this.state.carousel = true;

        } else {
            this.state.swiper.updateSlidesSize();
        }
    }

    componentDidMount() {

        if (!this.state.carousel || this.props.interests.length == 0) {
            return;
        }
        this.state = {
            swiper  : this.initSwiper(),
            carousel: true
        };
    }

    onContentClick(contentKey) {
        this.setState({
            carousel: true,
            position: contentKey,
            swiper  : null
        });
    };

    handleScroll() {
        const {pagination, isLoadingComparedInterests} = this.props;
        let nextLink = pagination && pagination.hasOwnProperty('nextLink') ? pagination.nextLink : null;
        let offsetTop = parseInt(document.getElementsByClassName('view')[0].scrollTop + document.getElementsByClassName('view')[0].offsetHeight - 117);
        let offsetTopMax = parseInt(document.getElementById('page-content').offsetHeight);

        if (nextLink && offsetTop >= offsetTopMax && !isLoadingComparedInterests) {
            document.getElementsByClassName('view')[0].removeEventListener('scroll', this.handleScroll);
            InterestsActionCreators.requestNextComparedInterests(parseId(this.props.user), this.props.params.userId, nextLink);
        }
    }

    onNavBarLeftLinkClick() {
        this.setState({
            carousel: false
        });
    }

    initSwiper() {
        var _self = this;
        return nekunoApp.swiper('.swiper-container', {
            onReachEnd    : onReachEnd,
            effect        : 'coverflow',
            slidesPerView : 'auto',
            coverflow     : {
                rotate      : 30,
                stretch     : 0,
                depth       : 100,
                modifier    : 1,
                slideShadows: false
            },
            centeredSlides: true,
            grabCursor    : true,
            initialSlide  : this.state.position
        });

        function onReachEnd() {
            let pagination = _self.props.pagination;
            let nextLink = pagination && pagination.hasOwnProperty('nextLink') ? pagination.nextLink : null;
            InterestsActionCreators.requestNextComparedInterests(parseId(_self.props.user), _self.props.params.userId, nextLink);
        }
    }

    onFilterCommonClick(key) {
        InterestsActionCreators.resetInterests(this.props.params.userId);
        InterestsActionCreators.requestComparedInterests(parseId(this.props.user), parseInt(this.props.params.userId), this.state.type, key);
        this.setState({
            commonContent: key,
            carousel     : false
        });
    }

    onFilterTypeClick(type) {
        this.setState({
            type: type
        });
    }

    render() {
        const {interests, noInterests, otherUser, user, params, pagination, totals, strings} = this.props;
        const ownUserId = parseId(user);
        const otherUserId = parseInt(params.userId);
        const otherUserPicture = otherUser && otherUser.photo ? otherUser.photo.thumbnail.small : 'img/no-img/small.jpg';
        const ownPicture = user && user.photo ? user.photo.thumbnail.small : 'img/no-img/small.jpg';
        return (

                {this.state.carousel ?
                    <TopNavBar leftText={strings.cancel} centerText={otherUser ? otherUser.username : ''} onLeftLinkClickHandler={this.onNavBarLeftLinkClick}/>
                    :
                    <TopNavBar leftIcon={'left-arrow'} centerText={otherUser ? otherUser.username : ''}/>
                }
                {otherUser ?
                    <ToolBar links={[
                        {'url': `/profile/${otherUserId}`, 'text': strings.about},
                        {'url': `/users/${otherUserId}/other-questions`, 'text': strings.questions},
                        {'url': `/users/${otherUserId}/other-interests`, 'text': strings.interests}
                    ]} activeLinkIndex={2} arrowUpLeft={'83%'}/>
                    :
                    ''}

                            <ProfilesAvatarConnection ownPicture={ownPicture} otherPicture={otherUserPicture}/>

{this.state.commonContent ? strings.similarInterestsCount.replace('%count%', pagination.total || 0) : strings.interestsCount.replace('%count%', pagination.total || 0)}

                            {otherUser ? <FilterContentButtons userId={otherUserId} contentsCount={pagination.total || 0} ownContent={false} ownUserId={ownUserId} onClickHandler={this.onFilterTypeClick} commonContent={this.state.commonContent}
                                                  linksCount={totals.Link}
                                                  audiosCount={totals.Audio}
                                                  videosCount={totals.Video}
                                                  imagesCount={totals.Image}
                                                  channelsCount={totals.Creator}
                            /> : ''}

                                <TextRadios labels={[{key: 0, text: strings.all}, {key: 1, text: strings.common}]} value={this.state.commonContent} onClickHandler={this.onFilterCommonClick}/>

                            {noInterests ? '' :
                                /*this.state.carousel ?
                                    <CardContentCarousel contents={interests} userId={ownUserId} otherUserId={otherUserId}/>
                                    :
                                    <CardContentList contents={interests} userId={ownUserId} otherUserId={otherUserId}
                                                     onClickHandler={this.onContentClick}/>
                                 */
                                <CardContentList contents={interests} userId={ownUserId} otherUserId={otherUserId}/>
                            }

                            {this.state.carousel ? '' :

}

        );
    }
};

OtherInterestsPage.defaultProps = {
    strings: {
        cancel               : 'Cancel',
        interestsCount       : '%count% Interests',
        similarInterestsCount: '%count% Similar interests',
        all                  : 'All',
        common               : 'In common',
        about                : 'About',
        photos               : 'Photos',
        questions            : 'Answers',
        interests            : 'Interests'
    }
};<commit_msg>Comment last change to clarify<commit_after>import React, { PropTypes, Component } from 'react';
import TopNavBar from '../components/ui/TopNavBar';
import ToolBar from '../components/ui/ToolBar';
import CardContentList from '../components/interests/CardContentList';
import CardContentCarousel from '../components/interests/CardContentCarousel';
import FilterContentButtons from '../components/ui/FilterContentButtons';
import TextRadios from '../components/ui/TextRadios';
import ProfilesAvatarConnection from '../components/ui/ProfilesAvatarConnection';
import AuthenticatedComponent from '../components/AuthenticatedComponent';
import translate from '../i18n/Translate';
import connectToStores from '../utils/connectToStores';
import * as UserActionCreators from '../actions/UserActionCreators';
import * as InterestsActionCreators from '../actions/InterestsActionCreators';
import UserStore from '../stores/UserStore';
import InterestStore from '../stores/InterestStore';

function parseId(user) {
    return user.id;
}

function requestData(props) {
    const userId = parseId(props.user);
    UserActionCreators.requestUser(parseInt(props.params.userId), ['username', 'photo', 'status']);
    InterestsActionCreators.resetInterests(parseInt(props.params.userId));
    InterestsActionCreators.requestComparedInterests(userId, parseInt(props.params.userId), 'Link', 1);
}

function getState(props) {
    const otherUserId = props.params.userId;
    const pagination = InterestStore.getPagination(otherUserId) || {};
    const totals = InterestStore.getTotals(otherUserId) || {};
    const interests = InterestStore.get(otherUserId) || [];
    const noInterests = InterestStore.noInterests(otherUserId) || false;
    const isLoadingComparedInterests = InterestStore.isLoadingComparedInterests();
    const otherUser = UserStore.get(otherUserId);
    return {
        pagination,
        totals,
        interests,
        noInterests,
        isLoadingComparedInterests,
        otherUser
    };
}

@AuthenticatedComponent
@translate('OtherInterestsPage')
@connectToStores([UserStore, InterestStore], getState)
export default class OtherInterestsPage extends Component {
    static propTypes = {
        // Injected by React Router:
        params                    : PropTypes.shape({
            userId: PropTypes.string.isRequired
        }).isRequired,
        // Injected by @AuthenticatedComponent
        user                      : PropTypes.object.isRequired,
        // Injected by @translate:
        strings                   : PropTypes.object,
        // Injected by @connectToStores:
        pagination                : PropTypes.object,
        totals                    : PropTypes.object,
        interests                 : PropTypes.array.isRequired,
        isLoadingComparedInterests: PropTypes.bool,
        otherUser                 : PropTypes.object
    };

    constructor(props) {

        super(props);

        this.onFilterCommonClick = this.onFilterCommonClick.bind(this);
        this.onFilterTypeClick = this.onFilterTypeClick.bind(this);
        this.handleScroll = this.handleScroll.bind(this);
        this.onContentClick = this.onContentClick.bind(this);
        this.onNavBarLeftLinkClick = this.onNavBarLeftLinkClick.bind(this);
        this.initSwiper = this.initSwiper.bind(this);

        this.state = {
            type         : '',
            commonContent: 1,
            carousel     : false,
            position     : 0,
            swiper       : null
        }
    }

    componentWillMount() {
        requestData(this.props);
    }

    componentWillUnmount() {
        document.getElementsByClassName('view')[0].removeEventListener('scroll', this.handleScroll);
    }

    componentDidUpdate() {
        if (!this.state.carousel || this.props.interests.length == 0) {
            return;
        }
        if (!this.state.swiper) {
            this.state.swiper = this.initSwiper();
            this.state.carousel = true;

        } else {
            this.state.swiper.updateSlidesSize();
        }
    }

    componentDidMount() {

        if (!this.state.carousel || this.props.interests.length == 0) {
            return;
        }
        this.state = {
            swiper  : this.initSwiper(),
            carousel: true
        };
    }

    onContentClick(contentKey) {
        this.setState({
            carousel: true,
            position: contentKey,
            swiper  : null
        });
    };

    handleScroll() {
        const {pagination, isLoadingComparedInterests} = this.props;
        let nextLink = pagination && pagination.hasOwnProperty('nextLink') ? pagination.nextLink : null;
        let offsetTop = parseInt(document.getElementsByClassName('view')[0].scrollTop + document.getElementsByClassName('view')[0].offsetHeight - 117);
        let offsetTopMax = parseInt(document.getElementById('page-content').offsetHeight);

        if (nextLink && offsetTop >= offsetTopMax && !isLoadingComparedInterests) {
            document.getElementsByClassName('view')[0].removeEventListener('scroll', this.handleScroll);
            InterestsActionCreators.requestNextComparedInterests(parseId(this.props.user), this.props.params.userId, nextLink);
        }
    }

    onNavBarLeftLinkClick() {
        this.setState({
            carousel: false
        });
    }

    initSwiper() {
        var _self = this;
        return nekunoApp.swiper('.swiper-container', {
            onReachEnd    : onReachEnd,
            effect        : 'coverflow',
            slidesPerView : 'auto',
            coverflow     : {
                rotate      : 30,
                stretch     : 0,
                depth       : 100,
                modifier    : 1,
                slideShadows: false
            },
            centeredSlides: true,
            grabCursor    : true,
            initialSlide  : this.state.position
        });

        function onReachEnd() {
            let pagination = _self.props.pagination;
            let nextLink = pagination && pagination.hasOwnProperty('nextLink') ? pagination.nextLink : null;
            InterestsActionCreators.requestNextComparedInterests(parseId(_self.props.user), _self.props.params.userId, nextLink);
        }
    }

    onFilterCommonClick(key) {
        InterestsActionCreators.resetInterests(this.props.params.userId);
        InterestsActionCreators.requestComparedInterests(parseId(this.props.user), parseInt(this.props.params.userId), this.state.type, key);
        this.setState({
            commonContent: key,
            carousel     : false
        });
    }

    onFilterTypeClick(type) {
        this.setState({
            type: type
        });
    }

    render() {
        const {interests, noInterests, otherUser, user, params, pagination, totals, strings} = this.props;
        const ownUserId = parseId(user);
        const otherUserId = parseInt(params.userId);
        const otherUserPicture = otherUser && otherUser.photo ? otherUser.photo.thumbnail.small : 'img/no-img/small.jpg';
        const ownPicture = user && user.photo ? user.photo.thumbnail.small : 'img/no-img/small.jpg';
        return (

                {this.state.carousel ?
                    <TopNavBar leftText={strings.cancel} centerText={otherUser ? otherUser.username : ''} onLeftLinkClickHandler={this.onNavBarLeftLinkClick}/>
                    :
                    <TopNavBar leftIcon={'left-arrow'} centerText={otherUser ? otherUser.username : ''}/>
                }
                {otherUser ?
                    <ToolBar links={[
                        {'url': `/profile/${otherUserId}`, 'text': strings.about},
                        {'url': `/users/${otherUserId}/other-questions`, 'text': strings.questions},
                        {'url': `/users/${otherUserId}/other-interests`, 'text': strings.interests}
                    ]} activeLinkIndex={2} arrowUpLeft={'83%'}/>
                    :
                    ''}

                            <ProfilesAvatarConnection ownPicture={ownPicture} otherPicture={otherUserPicture}/>

{this.state.commonContent ? strings.similarInterestsCount.replace('%count%', pagination.total || 0) : strings.interestsCount.replace('%count%', pagination.total || 0)}

                            {otherUser ? <FilterContentButtons userId={otherUserId} contentsCount={pagination.total || 0} ownContent={false} ownUserId={ownUserId} onClickHandler={this.onFilterTypeClick} commonContent={this.state.commonContent}
                                                  linksCount={totals.Link}
                                                  audiosCount={totals.Audio}
                                                  videosCount={totals.Video}
                                                  imagesCount={totals.Image}
                                                  channelsCount={totals.Creator}
                            /> : ''}

                                <TextRadios labels={[{key: 0, text: strings.all}, {key: 1, text: strings.common}]} value={this.state.commonContent} onClickHandler={this.onFilterCommonClick}/>

                            {noInterests ? '' :
                                /* Uncomment to enable carousel
                                this.state.carousel ?
                                    <CardContentCarousel contents={interests} userId={ownUserId} otherUserId={otherUserId}/>
                                    :
                                    <CardContentList contents={interests} userId={ownUserId} otherUserId={otherUserId}
                                                     onClickHandler={this.onContentClick}/>
                                 */
                                <CardContentList contents={interests} userId={ownUserId} otherUserId={otherUserId}/>
                            }

                            {this.state.carousel ? '' :

}

        );
    }
};

OtherInterestsPage.defaultProps = {
    strings: {
        cancel               : 'Cancel',
        interestsCount       : '%count% Interests',
        similarInterestsCount: '%count% Similar interests',
        all                  : 'All',
        common               : 'In common',
        about                : 'About',
        photos               : 'Photos',
        questions            : 'Answers',
        interests            : 'Interests'
    }
};<|endoftext|>


<commit_before>import React from "React";

/**
 * @module Maple
 * @author Adam Timberlake
 * @link https://github.com/maplejs/framework
 * @constructor
 */
class React {

}<commit_msg>Added initial example<commit_after>(function main($window) {

    "use strict";

    /**
     * @module Maple
     * @author Adam Timberlake
     * @link https://github.com/Wildhoney/Maple.js
     */
    class Maple {

    }

    /**
     * @module Maple
     * @submodule Component
     * @author Adam Timberlake
     * @link https://github.com/Wildhoney/Maple.js
     */
    class MapleComponent extends React.Component {

        /**
         * @constructor
         * @param {String} name
         * @param {Object} componentClass
         * @return {MapleComponent}
         */
        constructor(name, componentClass) {

            //React.render(component, document.querySelector('body'));
            this.registerElement(name, componentClass);

        }

        /**
         * @method registerElement
         * @param {String} name
         * @param {Object} componentClass
         * @return {void}
         */
        registerElement(name, componentClass) {

            var prototype = Object.create(HTMLElement.prototype, {

                createdCallback: {

                    value: function value() {

                        let element    = React.createElement(componentClass),
                            component  = React.render(element, this),
                            domNode    = React.findDOMNode(component),
                            shadowRoot = domNode.parentNode.createShadowRoot();

                        shadowRoot.appendChild(domNode);

                    }

                }

            });

            /**
             * @property MegaButton
             * @type {Object}
             */
            document.registerElement(name, {
                prototype: prototype
            });

        }

    }

    $window.Maple           = Maple;
    $window.Maple.Component = MapleComponent;

})(window);<|endoftext|>


<commit_before>'use strict';
/* eslint no-sync: 0 */
/**
 * @see [Atom's publish-build-task.coffee](https://git.io/vaYu3)
 * - https://github.com/atom/electron/blob/master/script/create-dist.py
 */

/**
 * TODO (imlucas) If darwin, dump symbols for breakpad
 * and include in assets.
 * @see [Atom's dump-symbols-task.coffee](https://git.io/va3fG)
 */
const config = require('../lib/config');
const cli = require('mongodb-js-cli')('hadron-build:release');
const util = require('util');
const format = util.format;
const glob = require('glob');
const path = require('path');
const del = require('del');
const fs = require('fs-extra');
const _ = require('lodash');
const async = require('async');
const asar = require('asar');
const packager = require('electron-packager');
const run = require('electron-installer-run');
const zip = require('electron-installer-zip');
const license = require('electron-license');
const ModuleCache = require('hadron-module-cache');
const CompileCache = require('hadron-compile-cache');

const pkg = require('../lib/package');
const ui = require('./ui');
const verify = require('./verify');

exports.command = 'release';

exports.describe = ':shipit:';

const COMPILE_CACHE = '.compiled-sources';
const COMPILE_CACHE_MAPPINGS = '_compileCacheMappings';
const CACHE_PATTERN = '**/*.{jade,jsx}';

/**
 * Clean out the existing development compile cache.
 *
 * @param {Object} CONFIG
 * @param {Function} done
 * @api public
 */
function cleanCompileCache(CONFIG, done) {
  cli.debug('cleaning out development compile cache');
  fs.remove(path.resolve(CONFIG.dir, COMPILE_CACHE), function() {
    done();
  });
}

/**
 * Create a precompiled cache of .jade and .jsx sources.
 *
 * @param {Object} CONFIG
 * @param {Function} done
 * @api public
 */
function createCompileCache(CONFIG, done) {
  cli.debug('creating compile cache');
  var appDir = path.join(CONFIG.resources, 'app');
  CompileCache.setHomeDirectory(appDir);
  glob(`src/${CACHE_PATTERN}`, function(error, files) {
    cli.abortIfError(error);
    _.each(files, function(file) {
      var compiler = CompileCache.COMPILERS[path.extname(file)];
      CompileCache.compileFileAtPath(compiler, file);
    });
    // Write the compile cache mappings to the package.json.
    var metadata = pkg.get(appDir);
    metadata[COMPILE_CACHE_MAPPINGS] = CompileCache.digestMappings;
    fs.writeFile(metadata._path, JSON.stringify(metadata, null, 2), done);
  });
}

/**
 * Run `electron-packager`
 *
 * @see https://npm.im/electron-packager
 * @param {Object} CONFIG
 * @param {Function} done
 * @api public
 */
function createBrandedApplication(CONFIG, done) {
  cli.debug('running electron-packager');
  packager(CONFIG.packagerOptions, function(err, res) {
    if (err) {
      return done(err);
    }
    cli.debug('Packager result is: ' + JSON.stringify(res, null, 2));
    done(null, true);
  });
}

/**
 * For some platforms, there will be extraneous (to us) folders generated
 * we can remove to make the generated branded app less cluttered and easier
 * to debug.
 *
 * @param {Object} CONFIG
 * @param {Function} done
 * @api public
 */
function cleanupBrandedApplicationScaffold(CONFIG, done) {
  if (CONFIG.platform === 'linux') {
    done(null, true);
    return;
  }

  var globsToDelete = [];
  if (CONFIG.platform === 'win32') {
    globsToDelete.push(path.join(CONFIG.resources, '*.pak'));
  } else {
    globsToDelete.push(path.join(CONFIG.resources, '*.lproj'));
  }

  cli.debug('cleaning up extraneous files from template');
  del(globsToDelete, {
    force: true
  }).then(function(paths) {
    cli.debug(format('%d extraneous files removed', paths.length));
    done(null, true);
  }, done);
}

/**
 * Replace the LICENSE file `electron-packager` creates w/ a LICENSE
 * file specific to the project.
 *
 * @see [Atom's generate-license-task.coffee](https://git.io/vaZI7)
 * @param {Object} CONFIG
 * @returns {Promise}
 * @api public
 */
let writeLicenseFile = (CONFIG, done) => {
  var LICENSE_DEST = path.join(CONFIG.appPath, '..', 'LICENSE');
  var opts = {
    dir: path.join(CONFIG.resources, 'app'),
    production: false,
    excludeOrg: 'mongodb-js,10gen,christkv'
  };
  return license.build(opts)
    .then((contents) => {
      fs.writeFileSync(LICENSE_DEST, contents);
      cli.debug(format('LICENSE written to `%s`', LICENSE_DEST));
      done(null, true);
    });
};

/**
 * Replace the version file `electron-packager` creates w/ a version
 * file specific to the project.
 *
 * @see [Atom's set-version-task.coffee](https://git.io/vaZkN)
 * @param {Object} CONFIG
 * @returns {Promise}
 * @api public
 */
let writeVersionFile = (CONFIG, done) => {
  const VERSION_DEST = path.join(CONFIG.appPath, '..', 'version');
  cli.debug(format('Writing version file to `%s`', VERSION_DEST));
  fs.writeFile(VERSION_DEST, CONFIG.version, function(err) {
    if (err) {
      return done(err);
    }
    cli.debug(format('version file written to `%s`', VERSION_DEST));
    done(null, CONFIG.version);
  });
};

/**
 * Update the project's `./package.json` (like npm does when it
 * installs a package) for inclusion in the application
 * `electron-packager` creates.
 *
 * @param {Object} CONFIG
 * @param {Function} done
 * @api public
 */
function transformPackageJson(CONFIG, done) {
  const PACKAGE_JSON_DEST = path.join(CONFIG.resources, 'app', 'package.json');
  const packageKeysToRemove = [
    'scripts',
    'devDependencies',
    'dependency-check',
    'repository',
    'check',
    'config.hadron.build'
  ];

  let contents = _.omit(pkg, packageKeysToRemove);

  _.assign(contents, {
    productName: CONFIG.productName,
    channel: CONFIG.channel
  });

  fs.writeFile(PACKAGE_JSON_DEST, JSON.stringify(contents, null, 2), done);
}

/**
 * TODO (imlucas) Cache this... http://npm.im/npm-cache ?
 * @see [Atom's fingerprint-task.js](https://git.io/vaZLq)
 * @see [Atom's fingerprint.js](https://git.io/vaZLZ)
 * @see [Atom's generate-module-cache-task.coffee](https://git.io/vaY0O)
 * @see [Atom's native-compile-cache](https://git.io/vaY0a)
 * @see [Atom's module-cache.coffee](https://git.io/vaY0K)
 *
 * @param {Object} CONFIG
 * @param {Function} done
 * @api public
 */
function installDependencies(CONFIG, done) {
  var args = [
    'install',
    '--production'
  ];
  cli.debug('Installing dependencies');
  var opts = {
    env: process.env,
    cwd: path.join(CONFIG.resources, 'app')
  };
  run('npm', args, opts, function(err) {
    if (err) {
      return done(err);
    }
    cli.debug('Dependencies installed');
    done();
  });
}

/**
 * Before creating installers for distribution to
 * customers, there are thousands of files
 * they don't need in order to run the application.
 *
 * TODO (imlucas) even more we can remove!  see
 * `EXCLUDE_FROM_RELEASE` in INT-1225.
 *
 * @see https://jira.mongodb.org/browse/INT-1225
 * @param {Object} CONFIG
 * @param {Function} done
 * @api public
 */
function removeDevelopmentFiles(CONFIG, done) {
  if (CONFIG.platform !== 'darwin') {
    done();
    return;
  }
  var DOT_FILES = [
    '.DS_Store',
    '.eslint*',
    '.evergreen*',
    '.travis*',
    '.npm*',
    '.jsfmt*',
    '.git*',
    'report*'
  ];

  var globsToDelete = [
    path.join(CONFIG.resources, 'app', 'test'),
    path.join(CONFIG.resources, 'app', 'scripts')
  ];

  if (CONFIG.platform === 'darwin') {
    globsToDelete.push(path.join(CONFIG.resources,
      'app', '{' + DOT_FILES.join(',') + '}'));
  }

  cli.debug('Checking for extraneous files to remove:\n' +
    JSON.stringify(globsToDelete, null, 2));

  del(globsToDelete).then(function(paths) {
    if (paths.length === 0) {
      cli.debug('No extraneous files to remove');
    } else {
      cli.debug(format('%s extraneous files removed', paths.length));
    }
    done(null, true);
  }, done);
}

/**
 * Package the application as a single `asar` file.
 *
 * @see [Atom's generate-asar-task.coffee](https://git.io/vaY4O)
 * @see https://gist.github.com/imlucas/7a8956cf153595168109
 * @see https://jira.mongodb.org/browse/INT-1225
 * @param {Object} CONFIG
 * @param {Function} done
 */
function createApplicationAsar(CONFIG, done) {
  var opts = {
    /**
     * TODO (imlucas) Find a good way to automate generating
     * the hints file using `ELECTRON_LOG_ASAR_READS=1`.
     *
     *  ordering: path.join(process.cwd(),
     *   'resources', 'asar-ordering-hint.txt'),
     */
    unpack: '{' + [
      '*.node',
      '**/vendor/**'
    ].join(',') + '}'
  };

  var src = path.join(CONFIG.resources, 'app');
  var dest = path.join(CONFIG.resources, 'app.asar');

  asar.createPackageWithOptions(src, dest, opts, function() {
    del(src, {force: true}).then(function() {
      done();
    }, done);
  });
}

/**
 * Packages the app as a plain zip using `electron-installer-zip`
 * for auto updates.
 *
 * NOTE (imlucas) This should be run after the installers have been
 * created.  The modules that generate the installers also
 * handle signinging the assets. If we zip unsigned assets
 * and publish them for the release, auto updates will be rejected.
 *
 * @param {Object} CONFIG
 * @param {Function} done
 */
function createApplicationZip(CONFIG, done) {
  if (CONFIG.platform === 'linux') {
    done();
    return;
  }

  var DIR = CONFIG.appPath;

  var OUT = CONFIG.assets.filter(function(asset) {
    return path.extname(asset.path) === '.zip';
  })[0].path;
  cli.debug('Zipping `%s` to `%s`', DIR, OUT);

  var options = {
    dir: DIR,
    out: OUT,
    platform: CONFIG.platform
  };

  zip(options, done);
}

/**
 * Create the application installer.
 *
 * @param {Object} CONFIG
 * @param {Function} done
 * @api public
 */
function createBrandedInstaller(CONFIG, done) {
  cli.debug('Creating installer');
  CONFIG.createInstaller(done);
}

let createModuleCache = (CONFIG, done) => {
  const appDir = path.join(CONFIG.resources, 'app');
  ModuleCache.create(appDir);

  let metadata = pkg.get(appDir);

  for (let folder in _.get(metadata, '_compassModuleCache.folders')) {
    if (_.includes(folder.paths, '')) {
      folder.paths = ['', 'test', 'src', 'src/app'];
    }
  }
  fs.writeFile(metadata._path, JSON.stringify(metadata, null, 2), done);
};

exports.builder = {};

_.assign(exports.builder, config.options, ui.builder, verify.builder);

exports.handler = (argv) => {
  cli.argv = argv;

  let CONFIG = config.get(cli);

  var tasks = _.flatten([
    function(cb) {
      verify.tasks(argv)
        .then(() => ui.tasks(argv))
        .then(() => cb())
        .catch(cb);
    },
    _.partial(cleanCompileCache, CONFIG),
    _.partial(createBrandedApplication, CONFIG),
    _.partial(cleanupBrandedApplicationScaffold, CONFIG),
    _.partial(writeVersionFile, CONFIG),
    _.partial(transformPackageJson, CONFIG),
    _.partial(installDependencies, CONFIG),
    _.partial(writeLicenseFile, CONFIG),
    _.partial(createCompileCache, CONFIG),
    _.partial(createModuleCache, CONFIG),
    _.partial(removeDevelopmentFiles, CONFIG),
    _.partial(createApplicationAsar, CONFIG),
    _.partial(createBrandedInstaller, CONFIG),
    _.partial(createApplicationZip, CONFIG)
  ]);

  async.series(tasks, (_err) => {
    cli.abortIfError(_err);
    cli.ok(format('%d assets successfully built',
      CONFIG.assets.length));
    CONFIG.assets.map(function(asset) {
      cli.info(asset.path);
    });
  });
};
<commit_msg>:bug: get zip full path<commit_after>'use strict';
/* eslint no-sync: 0 */
/**
 * @see [Atom's publish-build-task.coffee](https://git.io/vaYu3)
 * - https://github.com/atom/electron/blob/master/script/create-dist.py
 */

/**
 * TODO (imlucas) If darwin, dump symbols for breakpad
 * and include in assets.
 * @see [Atom's dump-symbols-task.coffee](https://git.io/va3fG)
 */
const config = require('../lib/config');
const cli = require('mongodb-js-cli')('hadron-build:release');
const util = require('util');
const format = util.format;
const glob = require('glob');
const path = require('path');
const del = require('del');
const fs = require('fs-extra');
const _ = require('lodash');
const async = require('async');
const asar = require('asar');
const packager = require('electron-packager');
const run = require('electron-installer-run');
const zip = require('electron-installer-zip');
const license = require('electron-license');
const ModuleCache = require('hadron-module-cache');
const CompileCache = require('hadron-compile-cache');

const pkg = require('../lib/package');
const ui = require('./ui');
const verify = require('./verify');

exports.command = 'release';

exports.describe = ':shipit:';

const COMPILE_CACHE = '.compiled-sources';
const COMPILE_CACHE_MAPPINGS = '_compileCacheMappings';
const CACHE_PATTERN = '**/*.{jade,jsx}';

/**
 * Clean out the existing development compile cache.
 *
 * @param {Object} CONFIG
 * @param {Function} done
 * @api public
 */
function cleanCompileCache(CONFIG, done) {
  cli.debug('cleaning out development compile cache');
  fs.remove(path.resolve(CONFIG.dir, COMPILE_CACHE), function() {
    done();
  });
}

/**
 * Create a precompiled cache of .jade and .jsx sources.
 *
 * @param {Object} CONFIG
 * @param {Function} done
 * @api public
 */
function createCompileCache(CONFIG, done) {
  cli.debug('creating compile cache');
  var appDir = path.join(CONFIG.resources, 'app');
  CompileCache.setHomeDirectory(appDir);
  glob(`src/${CACHE_PATTERN}`, function(error, files) {
    cli.abortIfError(error);
    _.each(files, function(file) {
      var compiler = CompileCache.COMPILERS[path.extname(file)];
      CompileCache.compileFileAtPath(compiler, file);
    });
    // Write the compile cache mappings to the package.json.
    var metadata = pkg.get(appDir);
    metadata[COMPILE_CACHE_MAPPINGS] = CompileCache.digestMappings;
    fs.writeFile(metadata._path, JSON.stringify(metadata, null, 2), done);
  });
}

/**
 * Run `electron-packager`
 *
 * @see https://npm.im/electron-packager
 * @param {Object} CONFIG
 * @param {Function} done
 * @api public
 */
function createBrandedApplication(CONFIG, done) {
  cli.debug('running electron-packager');
  packager(CONFIG.packagerOptions, function(err, res) {
    if (err) {
      return done(err);
    }
    cli.debug('Packager result is: ' + JSON.stringify(res, null, 2));
    done(null, true);
  });
}

/**
 * For some platforms, there will be extraneous (to us) folders generated
 * we can remove to make the generated branded app less cluttered and easier
 * to debug.
 *
 * @param {Object} CONFIG
 * @param {Function} done
 * @api public
 */
function cleanupBrandedApplicationScaffold(CONFIG, done) {
  if (CONFIG.platform === 'linux') {
    done(null, true);
    return;
  }

  var globsToDelete = [];
  if (CONFIG.platform === 'win32') {
    globsToDelete.push(path.join(CONFIG.resources, '*.pak'));
  } else {
    globsToDelete.push(path.join(CONFIG.resources, '*.lproj'));
  }

  cli.debug('cleaning up extraneous files from template');
  del(globsToDelete, {
    force: true
  }).then(function(paths) {
    cli.debug(format('%d extraneous files removed', paths.length));
    done(null, true);
  }, done);
}

/**
 * Replace the LICENSE file `electron-packager` creates w/ a LICENSE
 * file specific to the project.
 *
 * @see [Atom's generate-license-task.coffee](https://git.io/vaZI7)
 * @param {Object} CONFIG
 * @returns {Promise}
 * @api public
 */
let writeLicenseFile = (CONFIG, done) => {
  var LICENSE_DEST = path.join(CONFIG.appPath, '..', 'LICENSE');
  var opts = {
    dir: path.join(CONFIG.resources, 'app'),
    production: false,
    excludeOrg: 'mongodb-js,10gen,christkv'
  };
  return license.build(opts)
    .then((contents) => {
      fs.writeFileSync(LICENSE_DEST, contents);
      cli.debug(format('LICENSE written to `%s`', LICENSE_DEST));
      done(null, true);
    });
};

/**
 * Replace the version file `electron-packager` creates w/ a version
 * file specific to the project.
 *
 * @see [Atom's set-version-task.coffee](https://git.io/vaZkN)
 * @param {Object} CONFIG
 * @returns {Promise}
 * @api public
 */
let writeVersionFile = (CONFIG, done) => {
  const VERSION_DEST = path.join(CONFIG.appPath, '..', 'version');
  cli.debug(format('Writing version file to `%s`', VERSION_DEST));
  fs.writeFile(VERSION_DEST, CONFIG.version, function(err) {
    if (err) {
      return done(err);
    }
    cli.debug(format('version file written to `%s`', VERSION_DEST));
    done(null, CONFIG.version);
  });
};

/**
 * Update the project's `./package.json` (like npm does when it
 * installs a package) for inclusion in the application
 * `electron-packager` creates.
 *
 * @param {Object} CONFIG
 * @param {Function} done
 * @api public
 */
function transformPackageJson(CONFIG, done) {
  const PACKAGE_JSON_DEST = path.join(CONFIG.resources, 'app', 'package.json');
  const packageKeysToRemove = [
    'scripts',
    'devDependencies',
    'dependency-check',
    'repository',
    'check',
    'config.hadron.build'
  ];

  let contents = _.omit(pkg, packageKeysToRemove);

  _.assign(contents, {
    productName: CONFIG.productName,
    channel: CONFIG.channel
  });

  fs.writeFile(PACKAGE_JSON_DEST, JSON.stringify(contents, null, 2), done);
}

/**
 * TODO (imlucas) Cache this... http://npm.im/npm-cache ?
 * @see [Atom's fingerprint-task.js](https://git.io/vaZLq)
 * @see [Atom's fingerprint.js](https://git.io/vaZLZ)
 * @see [Atom's generate-module-cache-task.coffee](https://git.io/vaY0O)
 * @see [Atom's native-compile-cache](https://git.io/vaY0a)
 * @see [Atom's module-cache.coffee](https://git.io/vaY0K)
 *
 * @param {Object} CONFIG
 * @param {Function} done
 * @api public
 */
function installDependencies(CONFIG, done) {
  var args = [
    'install',
    '--production'
  ];
  cli.debug('Installing dependencies');
  var opts = {
    env: process.env,
    cwd: path.join(CONFIG.resources, 'app')
  };
  run('npm', args, opts, function(err) {
    if (err) {
      return done(err);
    }
    cli.debug('Dependencies installed');
    done();
  });
}

/**
 * Before creating installers for distribution to
 * customers, there are thousands of files
 * they don't need in order to run the application.
 *
 * TODO (imlucas) even more we can remove!  see
 * `EXCLUDE_FROM_RELEASE` in INT-1225.
 *
 * @see https://jira.mongodb.org/browse/INT-1225
 * @param {Object} CONFIG
 * @param {Function} done
 * @api public
 */
function removeDevelopmentFiles(CONFIG, done) {
  if (CONFIG.platform !== 'darwin') {
    done();
    return;
  }
  var DOT_FILES = [
    '.DS_Store',
    '.eslint*',
    '.evergreen*',
    '.travis*',
    '.npm*',
    '.jsfmt*',
    '.git*',
    'report*'
  ];

  var globsToDelete = [
    path.join(CONFIG.resources, 'app', 'test'),
    path.join(CONFIG.resources, 'app', 'scripts')
  ];

  if (CONFIG.platform === 'darwin') {
    globsToDelete.push(path.join(CONFIG.resources,
      'app', '{' + DOT_FILES.join(',') + '}'));
  }

  cli.debug('Checking for extraneous files to remove:\n' +
    JSON.stringify(globsToDelete, null, 2));

  del(globsToDelete).then(function(paths) {
    if (paths.length === 0) {
      cli.debug('No extraneous files to remove');
    } else {
      cli.debug(format('%s extraneous files removed', paths.length));
    }
    done(null, true);
  }, done);
}

/**
 * Package the application as a single `asar` file.
 *
 * @see [Atom's generate-asar-task.coffee](https://git.io/vaY4O)
 * @see https://gist.github.com/imlucas/7a8956cf153595168109
 * @see https://jira.mongodb.org/browse/INT-1225
 * @param {Object} CONFIG
 * @param {Function} done
 */
function createApplicationAsar(CONFIG, done) {
  var opts = {
    /**
     * TODO (imlucas) Find a good way to automate generating
     * the hints file using `ELECTRON_LOG_ASAR_READS=1`.
     *
     *  ordering: path.join(process.cwd(),
     *   'resources', 'asar-ordering-hint.txt'),
     */
    unpack: '{' + [
      '*.node',
      '**/vendor/**'
    ].join(',') + '}'
  };

  var src = path.join(CONFIG.resources, 'app');
  var dest = path.join(CONFIG.resources, 'app.asar');

  asar.createPackageWithOptions(src, dest, opts, function() {
    del(src, {force: true}).then(function() {
      done();
    }, done);
  });
}

/**
 * Packages the app as a plain zip using `electron-installer-zip`
 * for auto updates.
 *
 * NOTE (imlucas) This should be run after the installers have been
 * created.  The modules that generate the installers also
 * handle signinging the assets. If we zip unsigned assets
 * and publish them for the release, auto updates will be rejected.
 *
 * @param {Object} CONFIG
 * @param {Function} done
 */
function createApplicationZip(CONFIG, done) {
  if (CONFIG.platform === 'linux') {
    done();
    return;
  }

  var DIR = CONFIG.appPath;

  var OUT = CONFIG.assets.filter(function(asset) {
    return path.extname(asset.path) === '.zip';
  })[0].path;
  cli.debug('Zipping `%s` to `%s`', DIR, OUT);

  var options = {
    dir: DIR,
    out: CONFIG.dest(OUT),
    platform: CONFIG.platform
  };

  zip(options, done);
}

/**
 * Create the application installer.
 *
 * @param {Object} CONFIG
 * @param {Function} done
 * @api public
 */
function createBrandedInstaller(CONFIG, done) {
  cli.debug('Creating installer');
  CONFIG.createInstaller(done);
}

let createModuleCache = (CONFIG, done) => {
  const appDir = path.join(CONFIG.resources, 'app');
  ModuleCache.create(appDir);

  let metadata = pkg.get(appDir);

  for (let folder in _.get(metadata, '_compassModuleCache.folders')) {
    if (_.includes(folder.paths, '')) {
      folder.paths = ['', 'test', 'src', 'src/app'];
    }
  }
  fs.writeFile(metadata._path, JSON.stringify(metadata, null, 2), done);
};

exports.builder = {};

_.assign(exports.builder, config.options, ui.builder, verify.builder);

exports.handler = (argv) => {
  cli.argv = argv;

  let CONFIG = config.get(cli);

  var tasks = _.flatten([
    function(cb) {
      verify.tasks(argv)
        .then(() => ui.tasks(argv))
        .then(() => cb())
        .catch(cb);
    },
    _.partial(cleanCompileCache, CONFIG),
    _.partial(createBrandedApplication, CONFIG),
    _.partial(cleanupBrandedApplicationScaffold, CONFIG),
    _.partial(writeVersionFile, CONFIG),
    _.partial(transformPackageJson, CONFIG),
    _.partial(installDependencies, CONFIG),
    _.partial(writeLicenseFile, CONFIG),
    _.partial(createCompileCache, CONFIG),
    _.partial(createModuleCache, CONFIG),
    _.partial(removeDevelopmentFiles, CONFIG),
    _.partial(createApplicationAsar, CONFIG),
    _.partial(createBrandedInstaller, CONFIG),
    _.partial(createApplicationZip, CONFIG)
  ]);

  async.series(tasks, (_err) => {
    cli.abortIfError(_err);
    cli.ok(format('%d assets successfully built',
      CONFIG.assets.length));
    CONFIG.assets.map(function(asset) {
      cli.info(asset.path);
    });
  });
};
<|endoftext|>


<commit_before>import { assign, keys, omit, defaults, isArray, flatten, sortBy, isNaN } from "lodash";
import { Helpers, LabelHelpers, Scale, Domain, Data } from "victory-core";

export default {
  getBaseProps(props, fallbackProps) {
    props = Helpers.modifyProps(props, fallbackProps, "errorbar");
    const { data, style, scale, domain, origin } = this.getCalculatedValues(props, fallbackProps);
    const { groupComponent, height, width, borderWidth, standalone, theme, polar, padding } = props;
    const initialChildProps = { parent: {
      domain, scale, data, height, width, standalone, theme, polar, origin,
      padding, style: style.parent
    } };

    return data.reduce((childProps, datum, index) => {
      const eventKey = datum.eventKey || index;
      const x = scale.x(datum._x1 !== undefined ? datum._x1 : datum._x);
      const y = scale.y(datum._y1 !== undefined ? datum._y1 : datum._y);

      const dataProps = {
        x, y, scale, datum, data, index, groupComponent, borderWidth,
        style: this.getDataStyles(datum, style.data),
        errorX: this.getErrors(datum, scale, "x"),
        errorY: this.getErrors(datum, scale, "y")
      };

      childProps[eventKey] = {
        data: dataProps
      };
      const text = LabelHelpers.getText(props, datum, index);
      if (text !== undefined && text !== null || props.events || props.sharedEvents) {
        childProps[eventKey].labels = this.getLabelProps(dataProps, text, style);
      }

      return childProps;
    }, initialChildProps);
  },

  getLabelProps(dataProps, text, style) {
    const { x, index, scale, errorY } = dataProps;
    const error = errorY && Array.isArray(errorY) ? errorY[0] : errorY;
    const y = error || dataProps.y;
    const labelStyle = style.labels || {};
    return {
      style: labelStyle,
      y: y - (labelStyle.padding || 0),
      x,
      text,
      index,
      scale,
      datum: dataProps.datum,
      data: dataProps.data,
      textAnchor: labelStyle.textAnchor,
      verticalAnchor: labelStyle.verticalAnchor || "end",
      angle: labelStyle.angle
    };
  },

  getErrorData(props) {
    if (props.data) {
      if (props.data.length < 1) {
        return [];
      }

      return this.formatErrorData(props.data, props);
    } else {
      const generatedData = (props.errorX || props.errorY) && this.generateData(props);
      return this.formatErrorData(generatedData, props);
    }
  },

  getErrors(datum, scale, axis) {
    /**
     * check if it is asymmetric error or symmetric error, asymmetric error should be an array
     * and the first value is the positive error, the second is the negative error
     * @param  {Boolean} isArray(errorX)
     * @return {String or Array}
     */

    const errorNames = { x: "errorX", y: "errorY" };
    const errors = datum[errorNames[axis]];
    if (errors === 0) {
      return false;
    }

    return isArray(errors) ?
      [ errors[0] === 0 ? false : scale[axis](errors[0] + datum[`_${axis}`]),
        errors[1] === 0 ? false : scale[axis](datum[`_${axis}`] - errors[1]) ] :
      [ scale[axis](errors + datum[`_${axis}`]), scale[axis](datum[`_${axis}`] - errors) ];
  },

  formatErrorData(dataset, props) {
    if (!dataset) {
      return [];
    }
    const accessor = {
      x: Helpers.createAccessor(props.x !== undefined ? props.x : "x"),
      y: Helpers.createAccessor(props.y !== undefined ? props.y : "y"),
      errorX: Helpers.createAccessor(props.errorX !== undefined ? props.errorX : "errorX"),
      errorY: Helpers.createAccessor(props.errorY !== undefined ? props.errorY : "errorY")
    };

    const replaceNegatives = (errors) => {
      // check if the value is negative, if it is set to 0
      const replaceNeg = (val) => !val || val < 0 ? 0 : val;
      return isArray(errors) ? errors.map((err) => replaceNeg(err)) : replaceNeg(errors);
    };

    const stringMap = {
      x: Data.createStringMap(props, "x"),
      y: Data.createStringMap(props, "y")
    };

    return this.sortData(dataset.map((datum, index) => {
      const evaluatedX = accessor.x(datum);
      const evaluatedY = accessor.y(datum);
      const _x = evaluatedX !== undefined ? evaluatedX : index;
      const _y = evaluatedY !== undefined ? evaluatedY : datum;
      const errorX = replaceNegatives(accessor.errorX(datum));
      const errorY = replaceNegatives(accessor.errorY(datum));

      return assign(
        {},
        datum,
        { _x, _y, errorX, errorY },
        // map string data to numeric values, and add names
        typeof _x === "string" ? { _x: stringMap.x[_x], x: _x } : {},
        typeof _y === "string" ? { _y: stringMap.y[_y], y: _y } : {}
      );
    }), props.sortKey, props.sortOrder);
  },

  sortData(dataset, sortKey, sortOrder = "ascending") {
    if (!sortKey) {
      return dataset;
    }

    if (sortKey === "x" || sortKey === "y") {
      sortKey = `_${sortKey}`;
    }

    const sortedData = sortBy(dataset, sortKey);

    if (sortOrder === "descending") {
      return sortedData.reverse();
    }

    return sortedData;
  },

  getDomain(props, axis) {
    const propsDomain = Domain.getDomainFromProps(props, axis);
    if (propsDomain) {
      return Domain.padDomain(propsDomain, props, axis);
    }
    const categoryDomain = Domain.getDomainFromCategories(props, axis);
    if (categoryDomain) {
      return Domain.padDomain(categoryDomain, props, axis);
    }
    const dataset = this.getErrorData(props);

    if (dataset.length < 1) {
      return Scale.getBaseScale(props, axis).domain();
    }

    const domain = this.getDomainFromData(props, axis, dataset);
    return Domain.cleanDomain(Domain.padDomain(domain, props, axis), props);
  },

  getDomainFromData(props, axis, dataset) {
    const currentAxis = Helpers.getCurrentAxis(axis, props.horizontal);
    let error;
    if (currentAxis === "x") {
      error = "errorX";
    } else if (currentAxis === "y") {
      error = "errorY";
    }
    const axisData = flatten(dataset).map((datum) => datum[`_${currentAxis}`]);
    const errorData = flatten(flatten(dataset).map((datum) => {
      let errorMax;
      let errorMin;
      if (isArray(datum[error])) {
        errorMax = datum[error][0] + datum[`_${currentAxis}`];
        errorMin = datum[`_${currentAxis}`] - datum[error][1];
      } else {
        errorMax = datum[error] + datum[`_${currentAxis}`];
        errorMin = datum[`_${currentAxis}`] - datum[error];
      }
      return [errorMax, errorMin];
    }));

    const allData = axisData.concat(errorData);
    const min = Math.min(...allData);
    const max = Math.max(...allData);
    // TODO: is this the correct behavior, or should we just error. How do we
    // handle charts with just one data point?
    if (+min === +max) {
      return Domain.getSinglePointDomain(max);
    }
    return [min, max];
  },

  getCalculatedValues(props) {
    const defaultStyles = props.theme && props.theme.errorbar && props.theme.errorbar.style ?
      props.theme.errorbar.style : {};
    const style = Helpers.getStyles(props.style, defaultStyles) || {};
    const dataWithErrors = assign(Data.getData(props), this.getErrorData(props));
    const data = Data.addEventKeys(props, dataWithErrors);
    const range = {
      x: Helpers.getRange(props, "x"),
      y: Helpers.getRange(props, "y")
    };
    const domain = {
      x: this.getDomain(props, "x"),
      y: this.getDomain(props, "y")
    };
    const scale = {
      x: Scale.getBaseScale(props, "x").domain(domain.x).range(range.x),
      y: Scale.getBaseScale(props, "y").domain(domain.y).range(range.y)
    };
    const origin = props.polar ? props.origin || Helpers.getPolarOrigin(props) : undefined;
    return { domain, data, scale, style, origin };
  },

  getDataStyles(datum, style) {
    const numKeys = keys(datum).filter((k) => isNaN(k));
    const omitKeys = [
      "x", "y", "_x", "_y", "name", "errorX", "errorY", "eventKey", "label"
    ];
    const stylesFromData = omit(datum, [...omitKeys, ...numKeys]);
    return defaults({}, stylesFromData, style);
  }
};
<commit_msg>Modify error bar helpers to handle immutable data<commit_after>import { assign, keys, omit, defaults, isArray, flatten, sortBy, isNaN } from "lodash";
import { Helpers, LabelHelpers, Scale, Domain, Data } from "victory-core";

export default {
  getBaseProps(props, fallbackProps) {
    props = Helpers.modifyProps(props, fallbackProps, "errorbar");
    const { data, style, scale, domain, origin } = this.getCalculatedValues(props, fallbackProps);
    const { groupComponent, height, width, borderWidth, standalone, theme, polar, padding } = props;
    const initialChildProps = { parent: {
      domain, scale, data, height, width, standalone, theme, polar, origin,
      padding, style: style.parent
    } };

    return data.reduce((childProps, datum, index) => {
      const eventKey = datum.eventKey || index;
      const x = scale.x(datum._x1 !== undefined ? datum._x1 : datum._x);
      const y = scale.y(datum._y1 !== undefined ? datum._y1 : datum._y);

      const dataProps = {
        x, y, scale, datum, data, index, groupComponent, borderWidth,
        style: this.getDataStyles(datum, style.data),
        errorX: this.getErrors(datum, scale, "x"),
        errorY: this.getErrors(datum, scale, "y")
      };

      childProps[eventKey] = {
        data: dataProps
      };
      const text = LabelHelpers.getText(props, datum, index);
      if (text !== undefined && text !== null || props.events || props.sharedEvents) {
        childProps[eventKey].labels = this.getLabelProps(dataProps, text, style);
      }

      return childProps;
    }, initialChildProps);
  },

  getLabelProps(dataProps, text, style) {
    const { x, index, scale, errorY } = dataProps;
    const error = errorY && Array.isArray(errorY) ? errorY[0] : errorY;
    const y = error || dataProps.y;
    const labelStyle = style.labels || {};
    return {
      style: labelStyle,
      y: y - (labelStyle.padding || 0),
      x,
      text,
      index,
      scale,
      datum: dataProps.datum,
      data: dataProps.data,
      textAnchor: labelStyle.textAnchor,
      verticalAnchor: labelStyle.verticalAnchor || "end",
      angle: labelStyle.angle
    };
  },

  getErrorData(props) {
    if (props.data) {
      if (Data.getLength(props.data) < 1) {
        return [];
      }

      return this.formatErrorData(props.data, props);
    } else {
      const generatedData = (props.errorX || props.errorY) && this.generateData(props);
      return this.formatErrorData(generatedData, props);
    }
  },

  getErrors(datum, scale, axis) {
    /**
     * check if it is asymmetric error or symmetric error, asymmetric error should be an array
     * and the first value is the positive error, the second is the negative error
     * @param  {Boolean} isArray(errorX)
     * @return {String or Array}
     */

    const errorNames = { x: "errorX", y: "errorY" };
    const errors = datum[errorNames[axis]];
    if (errors === 0) {
      return false;
    }

    return isArray(errors) ?
      [ errors[0] === 0 ? false : scale[axis](errors[0] + datum[`_${axis}`]),
        errors[1] === 0 ? false : scale[axis](datum[`_${axis}`] - errors[1]) ] :
      [ scale[axis](errors + datum[`_${axis}`]), scale[axis](datum[`_${axis}`] - errors) ];
  },

  formatErrorData(dataset, props) {
    if (!dataset || Data.getLength(dataset) < 1) {
      return [];
    }
    const accessor = {
      x: Helpers.createAccessor(props.x !== undefined ? props.x : "x"),
      y: Helpers.createAccessor(props.y !== undefined ? props.y : "y"),
      errorX: Helpers.createAccessor(props.errorX !== undefined ? props.errorX : "errorX"),
      errorY: Helpers.createAccessor(props.errorY !== undefined ? props.errorY : "errorY")
    };

    const replaceNegatives = (errors) => {
      // check if the value is negative, if it is set to 0
      const replaceNeg = (val) => !val || val < 0 ? 0 : val;
      return isArray(errors) ? errors.map((err) => replaceNeg(err)) : replaceNeg(errors);
    };

    const stringMap = {
      x: Data.createStringMap(props, "x"),
      y: Data.createStringMap(props, "y")
    };

    const formattedData = dataset.reduce((dataArr, datum, index) => {
      datum = Data.parseDatum(datum);

      const evaluatedX = accessor.x(datum);
      const evaluatedY = accessor.y(datum);
      const _x = evaluatedX !== undefined ? evaluatedX : index;
      const _y = evaluatedY !== undefined ? evaluatedY : datum;
      const errorX = replaceNegatives(accessor.errorX(datum));
      const errorY = replaceNegatives(accessor.errorY(datum));

      dataArr.push(
        assign(
          {},
          datum,
          { _x, _y, errorX, errorY },
          // map string data to numeric values, and add names
          typeof _x === "string" ? { _x: stringMap.x[_x], x: _x } : {},
          typeof _y === "string" ? { _y: stringMap.y[_y], y: _y } : {}
        )
      );

      return dataArr;
    }, []);

    return this.sortData(formattedData, props.sortKey, props.sortOrder);
  },

  sortData(dataset, sortKey, sortOrder = "ascending") {
    if (!sortKey) {
      return dataset;
    }

    if (sortKey === "x" || sortKey === "y") {
      sortKey = `_${sortKey}`;
    }

    const sortedData = sortBy(dataset, sortKey);

    if (sortOrder === "descending") {
      return sortedData.reverse();
    }

    return sortedData;
  },

  getDomain(props, axis) {
    const propsDomain = Domain.getDomainFromProps(props, axis);
    if (propsDomain) {
      return Domain.padDomain(propsDomain, props, axis);
    }
    const categoryDomain = Domain.getDomainFromCategories(props, axis);
    if (categoryDomain) {
      return Domain.padDomain(categoryDomain, props, axis);
    }
    const dataset = this.getErrorData(props);

    if (dataset.length < 1) {
      return Scale.getBaseScale(props, axis).domain();
    }

    const domain = this.getDomainFromData(props, axis, dataset);
    return Domain.cleanDomain(Domain.padDomain(domain, props, axis), props);
  },

  getDomainFromData(props, axis, dataset) {
    const currentAxis = Helpers.getCurrentAxis(axis, props.horizontal);
    let error;
    if (currentAxis === "x") {
      error = "errorX";
    } else if (currentAxis === "y") {
      error = "errorY";
    }
    const axisData = flatten(dataset).map((datum) => datum[`_${currentAxis}`]);
    const errorData = flatten(flatten(dataset).map((datum) => {
      let errorMax;
      let errorMin;
      if (isArray(datum[error])) {
        errorMax = datum[error][0] + datum[`_${currentAxis}`];
        errorMin = datum[`_${currentAxis}`] - datum[error][1];
      } else {
        errorMax = datum[error] + datum[`_${currentAxis}`];
        errorMin = datum[`_${currentAxis}`] - datum[error];
      }
      return [errorMax, errorMin];
    }));

    const allData = axisData.concat(errorData);
    const min = Math.min(...allData);
    const max = Math.max(...allData);
    // TODO: is this the correct behavior, or should we just error. How do we
    // handle charts with just one data point?
    if (+min === +max) {
      return Domain.getSinglePointDomain(max);
    }
    return [min, max];
  },

  getCalculatedValues(props) {
    const defaultStyles = props.theme && props.theme.errorbar && props.theme.errorbar.style ?
      props.theme.errorbar.style : {};
    const style = Helpers.getStyles(props.style, defaultStyles) || {};
    const dataWithErrors = assign(Data.getData(props), this.getErrorData(props));
    const data = Data.addEventKeys(props, dataWithErrors);
    const range = {
      x: Helpers.getRange(props, "x"),
      y: Helpers.getRange(props, "y")
    };
    const domain = {
      x: this.getDomain(props, "x"),
      y: this.getDomain(props, "y")
    };
    const scale = {
      x: Scale.getBaseScale(props, "x").domain(domain.x).range(range.x),
      y: Scale.getBaseScale(props, "y").domain(domain.y).range(range.y)
    };
    const origin = props.polar ? props.origin || Helpers.getPolarOrigin(props) : undefined;
    return { domain, data, scale, style, origin };
  },

  getDataStyles(datum, style) {
    const numKeys = keys(datum).filter((k) => isNaN(k));
    const omitKeys = [
      "x", "y", "_x", "_y", "name", "errorX", "errorY", "eventKey", "label"
    ];
    const stylesFromData = omit(datum, [...omitKeys, ...numKeys]);
    return defaults({}, stylesFromData, style);
  }
};
<|endoftext|>


<commit_before>/*
Copyright 2017 Vector Creations Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import { _t } from '../../../languageHandler';
import MatrixClientPeg from '../../../MatrixClientPeg';

export default React.createClass({
    displayName: 'CreateGroupDialog',
    propTypes: {
        onFinished: PropTypes.func.isRequired,
    },

    getInitialState: function() {
        return {
            groupName: '',
            groupId: '',
            groupError: null,
            creating: false,
            createError: null,
        };
    },

    _onGroupNameChange: function(e) {
        this.setState({
            groupName: e.target.value,
        });
    },

    _onGroupIdChange: function(e) {
        this.setState({
            groupId: e.target.value,
        });
    },

    _onGroupIdBlur: function(e) {
        this._checkGroupId();
    },

    _checkGroupId: function(e) {
        let error = null;
        if (!/^[a-z0-9=_-\.\/]*$/.test(this.state.groupId)) {
            error = _t("Community IDs may only contain characters a-z, 0-9, or '=_-./'");
        }
        this.setState({
            groupIdError: error,
        });
        return error;
    },

    _onFormSubmit: function(e) {
        e.preventDefault();

        if (this._checkGroupId()) return;

        const profile = {};
        if (this.state.groupName !== '') {
            profile.name = this.state.groupName;
        }
        this.setState({creating: true});
        MatrixClientPeg.get().createGroup({
            localpart: this.state.groupId,
            profile: profile,
        }).then((result) => {
            dis.dispatch({
                action: 'view_group',
                group_id: result.group_id,
            });
            this.props.onFinished(true);
        }).catch((e) => {
            this.setState({createError: e});
        }).finally(() => {
            this.setState({creating: false});
        }).done();
    },

    _onCancel: function() {
        this.props.onFinished(false);
    },

    render: function() {
        const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
        const Spinner = sdk.getComponent('elements.Spinner');

        if (this.state.creating) {
            return <Spinner />;
        }

        let createErrorNode;
        if (this.state.createError) {
            // XXX: We should catch errcodes and give sensible i18ned messages for them,
            // rather than displaying what the server gives us, but synapse doesn't give
            // any yet.
            createErrorNode =

{ _t('Something went wrong whilst creating your community') }

{ this.state.createError.message }

;
        }

        return (
            <BaseDialog className="mx_CreateGroupDialog" onFinished={this.props.onFinished}
                onEnterPressed={this._onFormSubmit}
                title={_t('Create Community')}
            >
                <form onSubmit={this._onFormSubmit}>

                                { _t('Community Name') }

                                <input id="groupname" className="mx_CreateGroupDialog_input"
                                    autoFocus={true} size="64"
                                    placeholder={_t('Example')}
                                    onChange={this._onGroupNameChange}
                                    value={this.state.groupName}
                                />

                                { _t('Community ID') }

                                +
                                <input id="groupid" className="mx_CreateGroupDialog_input"
                                    size="32"
                                    placeholder={_t('example')}
                                    onChange={this._onGroupIdChange}
                                    onBlur={this._onGroupIdBlur}
                                    value={this.state.groupId}
                                />
                                :{ MatrixClientPeg.get().getDomain() }

                            { this.state.groupIdError }

                        { createErrorNode }

                        <button onClick={this._onCancel}>
                            { _t("Cancel") }
                        </button>
                        <input type="submit" value={_t('Create')} className="mx_Dialog_primary" />

                </form>
            </BaseDialog>
        );
    },
});
<commit_msg>Escape dash in regex<commit_after>/*
Copyright 2017 Vector Creations Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import { _t } from '../../../languageHandler';
import MatrixClientPeg from '../../../MatrixClientPeg';

export default React.createClass({
    displayName: 'CreateGroupDialog',
    propTypes: {
        onFinished: PropTypes.func.isRequired,
    },

    getInitialState: function() {
        return {
            groupName: '',
            groupId: '',
            groupError: null,
            creating: false,
            createError: null,
        };
    },

    _onGroupNameChange: function(e) {
        this.setState({
            groupName: e.target.value,
        });
    },

    _onGroupIdChange: function(e) {
        this.setState({
            groupId: e.target.value,
        });
    },

    _onGroupIdBlur: function(e) {
        this._checkGroupId();
    },

    _checkGroupId: function(e) {
        let error = null;
        if (!/^[a-z0-9=_\-\.\/]*$/.test(this.state.groupId)) {
            error = _t("Community IDs may only contain characters a-z, 0-9, or '=_-./'");
        }
        this.setState({
            groupIdError: error,
        });
        return error;
    },

    _onFormSubmit: function(e) {
        e.preventDefault();

        if (this._checkGroupId()) return;

        const profile = {};
        if (this.state.groupName !== '') {
            profile.name = this.state.groupName;
        }
        this.setState({creating: true});
        MatrixClientPeg.get().createGroup({
            localpart: this.state.groupId,
            profile: profile,
        }).then((result) => {
            dis.dispatch({
                action: 'view_group',
                group_id: result.group_id,
            });
            this.props.onFinished(true);
        }).catch((e) => {
            this.setState({createError: e});
        }).finally(() => {
            this.setState({creating: false});
        }).done();
    },

    _onCancel: function() {
        this.props.onFinished(false);
    },

    render: function() {
        const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
        const Spinner = sdk.getComponent('elements.Spinner');

        if (this.state.creating) {
            return <Spinner />;
        }

        let createErrorNode;
        if (this.state.createError) {
            // XXX: We should catch errcodes and give sensible i18ned messages for them,
            // rather than displaying what the server gives us, but synapse doesn't give
            // any yet.
            createErrorNode =

{ _t('Something went wrong whilst creating your community') }

{ this.state.createError.message }

;
        }

        return (
            <BaseDialog className="mx_CreateGroupDialog" onFinished={this.props.onFinished}
                onEnterPressed={this._onFormSubmit}
                title={_t('Create Community')}
            >
                <form onSubmit={this._onFormSubmit}>

                                { _t('Community Name') }

                                <input id="groupname" className="mx_CreateGroupDialog_input"
                                    autoFocus={true} size="64"
                                    placeholder={_t('Example')}
                                    onChange={this._onGroupNameChange}
                                    value={this.state.groupName}
                                />

                                { _t('Community ID') }

                                +
                                <input id="groupid" className="mx_CreateGroupDialog_input"
                                    size="32"
                                    placeholder={_t('example')}
                                    onChange={this._onGroupIdChange}
                                    onBlur={this._onGroupIdBlur}
                                    value={this.state.groupId}
                                />
                                :{ MatrixClientPeg.get().getDomain() }

                            { this.state.groupIdError }

                        { createErrorNode }

                        <button onClick={this._onCancel}>
                            { _t("Cancel") }
                        </button>
                        <input type="submit" value={_t('Create')} className="mx_Dialog_primary" />

                </form>
            </BaseDialog>
        );
    },
});
<|endoftext|>


<commit_before>/*jslint nomen: true */
/*globals define */

define(function (require, exports, module) {
  'use strict';

  var $ = require('jquery');
  var _ = require('lib/lodash');
  var Backbone = require('backbone');
  var L = require('lib/leaflet/leaflet.tilejson');
  var Promise = require('lib/bluebird');

  var settings = require('settings');

  var infoTemplate = require('text!templates/cartodb-info.html');
  var tooltipTemplate = require('text!templates/cartodb-tooltip.html');
  var template = require('text!templates/projects/layerControl.html');

  // Render an infowindow for a selected cartodb map item
  var ItemView = Backbone.View.extend({
    template: _.template(infoTemplate),

    events: {
      'click .close': 'remove'
    },

    initialize: function (options) {
      this.layerOptions = options.layerOptions;
      this.data = options.data;
    },

    render: function () {
      var context = {
        name: this.data[this.layerOptions.humanReadableField],
        centroid: JSON.parse(this.data.centroid),
        googleKey: settings.GoogleKey
      };

      var names = this.layerOptions.fieldNames;
      context.fields = _.map(_.keys(names), function (name) {
        return {
          name: names[name],
          value: this.data[name]
        };
      }, this);
      this.$el.html(this.template(context));
      return this;
    }
  });

  // The View
  // We don't make much use of the Backbone.View facilities, but we may need
  // later need to have this layer behave more like the survey layer, which
  // renders some summary data and reacts to models.
  module.exports = Backbone.View.extend({
    template: _.template(template),
    tooltipTemplate: _.template(tooltipTemplate),

    state: 'active',
    className: 'layer',

    events: {
      'click .toggle-layer': 'toggleLayer'
    },

    initialize: function(options) {
      _.bindAll(this, 'render', 'getCartoData', 'handleGridHover');

      this.mapView = options.mapView;
      this.dataQuery = options.layer.dataQuery;
      this.layerOptions = options.layer;

      this.state = this.layerOptions.state || 'active';

      var self = this;

      // Now we need to start loading the tiles
      Promise.resolve($.ajax({
        url: 'https://localdata.cartodb.com/api/v1/map',
        type: 'GET',
        dataType: 'jsonp',
        data: {
          stat_tag: options.layer.stat_tag, // doesn't seem to be required
          config: JSON.stringify(options.layer.config)
        }
      })).then(function (data) {

        // Add image tiles
        var url = 'https://' + data.cdn_url.https +
            '/localdata/api/v1/map/' + data.layergroupid +
            '/{z}/{x}/{y}.png';
        var attribution = this.layerOptions.attribution || '';
        this.tileLayer = L.tileLayer(url, { attribution: attribution });

        if (this.state === 'active') {
          self.mapView.addTileLayer(this.tileLayer);
        }

        // We can skip adding the UTF grids on purely informational layers.
        if(this.layerOptions.config.disableGrid) {
          return;
        }

        // Add grid
        var gridUrl = 'https://' + data.cdn_url.https +
            '/localdata/api/v1/map/' + data.layergroupid +
            '/0/{z}/{x}/{y}.grid.json?callback={cb}';
        this.gridLayer = new L.UtfGrid(gridUrl, {
          resolution: 4
        });

        if (this.state === 'active') {
          self.mapView.addGridLayer(this.gridLayer, true);
        }

        if (this.layerOptions.handleClick) {
          this.gridLayer.on('click', self.handleGridClick, self);
        }

        if (this.layerOptions.handleMouseover) {
          this.gridLayer.on('mouseover', self.handleGridHover, self);
          this.gridLayer.on('mouseout', self.handleGridMouseout, self);
        }

      }.bind(this)).catch(function (error) {
        console.log('Failed to fetch cartodb map config', error);
      });

    },

    render: function () {
      if (this.layerOptions.noLegend) {
        return;
      }

      var context = {
        name: this.layerOptions.layerName,
        meta: {
          color: this.layerOptions.color
        }
      };

      if (this.layerOptions.staticLegend) {
        context.meta.staticLegend = this.layerOptions.staticLegend;
      }

      if (this.state === 'inactive') {
        this.$el.addClass('legend-inactive');
      }

      this.trigger('rendered', this.$el);

      this.$el.html(this.template(context));
      return this.$el;
    },

    toggleLayer: function () {
      console.log("Toggling layer, start state", this.state);
      if (this.state === 'active') {
        this.state = 'inactive';
        this.mapView.removeTileLayer(this.tileLayer);

        if(this.gridLayer) {
          this.mapView.removeGridLayer(this.gridLayer);
        }

        this.hideTooltip();

        this.$el.addClass('legend-inactive');
      } else if (this.state === 'inactive') {
        this.state = 'active';
        this.mapView.addTileLayer(this.tileLayer);

        if(this.gridLayer) {
          this.mapView.addGridLayer(this.gridLayer, true);
        }

        this.$el.removeClass('legend-inactive');
      }
    },

    getCartoData: function(cartodb_id, done) {
      Promise.resolve($.ajax({
        url: 'https://localdata.cartodb.com/api/v2/sql',
        type: 'GET',
        dataType: 'jsonp',
        data: {
          q: _.template(this.dataQuery, { cartodb_id: cartodb_id })
        }
      })).then(done).catch(function (error) {
        console.log('Error getting data from cartodb', error);
      });
    },

    hideTooltip: function() {
      if (!this.$tooltip) { return; }
      this.$tooltip.remove();
      delete this.$tooltip;
    },

    showTooltip: function(name) {
      if (this.$tooltip) {
        this.$tooltip.html(name);
      } else {
        this.$tooltip = $(this.tooltipTemplate({ name: name}));
        this.$tooltip.appendTo($('#map-tools .tooltips'));
      }
    },

    handleGridMouseout: function() {
      this.hideTooltip();
    },

    handleGridHover: function(event) {
      this.getCartoData(event.data.cartodb_id, function(data) {
        if (data.rows && data.rows.length > 0) {
          var name = data.rows[0][this.layerOptions.humanReadableField];
          this.showTooltip(name);
        }
      }.bind(this));
    },

    handleGridClick: function (event) {
      if (!event.data) {
        console.log("No data --returning");
        return;
      }

      var self = this;

      this.getCartoData(event.data.cartodb_id, function (data) {
        console.log("Got data from carto", data);
        if (data.rows && data.rows.length > 0) {
          self.trigger('itemSelected', {
            view: new ItemView({
              data: data.rows[0],
              layerOptions: self.layerOptions
            }),
            latlng: event.latlng
          });
        }
      });
    }
  });
});
<commit_msg>Remove errant log<commit_after>/*jslint nomen: true */
/*globals define */

define(function (require, exports, module) {
  'use strict';

  var $ = require('jquery');
  var _ = require('lib/lodash');
  var Backbone = require('backbone');
  var L = require('lib/leaflet/leaflet.tilejson');
  var Promise = require('lib/bluebird');

  var settings = require('settings');

  var infoTemplate = require('text!templates/cartodb-info.html');
  var tooltipTemplate = require('text!templates/cartodb-tooltip.html');
  var template = require('text!templates/projects/layerControl.html');

  // Render an infowindow for a selected cartodb map item
  var ItemView = Backbone.View.extend({
    template: _.template(infoTemplate),

    events: {
      'click .close': 'remove'
    },

    initialize: function (options) {
      this.layerOptions = options.layerOptions;
      this.data = options.data;
    },

    render: function () {
      var context = {
        name: this.data[this.layerOptions.humanReadableField],
        centroid: JSON.parse(this.data.centroid),
        googleKey: settings.GoogleKey
      };

      var names = this.layerOptions.fieldNames;
      context.fields = _.map(_.keys(names), function (name) {
        return {
          name: names[name],
          value: this.data[name]
        };
      }, this);
      this.$el.html(this.template(context));
      return this;
    }
  });

  // The View
  // We don't make much use of the Backbone.View facilities, but we may need
  // later need to have this layer behave more like the survey layer, which
  // renders some summary data and reacts to models.
  module.exports = Backbone.View.extend({
    template: _.template(template),
    tooltipTemplate: _.template(tooltipTemplate),

    state: 'active',
    className: 'layer',

    events: {
      'click .toggle-layer': 'toggleLayer'
    },

    initialize: function(options) {
      _.bindAll(this, 'render', 'getCartoData', 'handleGridHover');

      this.mapView = options.mapView;
      this.dataQuery = options.layer.dataQuery;
      this.layerOptions = options.layer;

      this.state = this.layerOptions.state || 'active';

      var self = this;

      // Now we need to start loading the tiles
      Promise.resolve($.ajax({
        url: 'https://localdata.cartodb.com/api/v1/map',
        type: 'GET',
        dataType: 'jsonp',
        data: {
          stat_tag: options.layer.stat_tag, // doesn't seem to be required
          config: JSON.stringify(options.layer.config)
        }
      })).then(function (data) {

        // Add image tiles
        var url = 'https://' + data.cdn_url.https +
            '/localdata/api/v1/map/' + data.layergroupid +
            '/{z}/{x}/{y}.png';
        var attribution = this.layerOptions.attribution || '';
        this.tileLayer = L.tileLayer(url, { attribution: attribution });

        if (this.state === 'active') {
          self.mapView.addTileLayer(this.tileLayer);
        }

        // We can skip adding the UTF grids on purely informational layers.
        if(this.layerOptions.config.disableGrid) {
          return;
        }

        // Add grid
        var gridUrl = 'https://' + data.cdn_url.https +
            '/localdata/api/v1/map/' + data.layergroupid +
            '/0/{z}/{x}/{y}.grid.json?callback={cb}';
        this.gridLayer = new L.UtfGrid(gridUrl, {
          resolution: 4
        });

        if (this.state === 'active') {
          self.mapView.addGridLayer(this.gridLayer, true);
        }

        if (this.layerOptions.handleClick) {
          this.gridLayer.on('click', self.handleGridClick, self);
        }

        if (this.layerOptions.handleMouseover) {
          this.gridLayer.on('mouseover', self.handleGridHover, self);
          this.gridLayer.on('mouseout', self.handleGridMouseout, self);
        }

      }.bind(this)).catch(function (error) {
        console.log('Failed to fetch cartodb map config', error);
      });

    },

    render: function () {
      if (this.layerOptions.noLegend) {
        return;
      }

      var context = {
        name: this.layerOptions.layerName,
        meta: {
          color: this.layerOptions.color
        }
      };

      if (this.layerOptions.staticLegend) {
        context.meta.staticLegend = this.layerOptions.staticLegend;
      }

      if (this.state === 'inactive') {
        this.$el.addClass('legend-inactive');
      }

      this.trigger('rendered', this.$el);

      this.$el.html(this.template(context));
      return this.$el;
    },

    toggleLayer: function () {
      console.log("Toggling layer, start state", this.state);
      if (this.state === 'active') {
        this.state = 'inactive';
        this.mapView.removeTileLayer(this.tileLayer);

        if(this.gridLayer) {
          this.mapView.removeGridLayer(this.gridLayer);
        }

        this.hideTooltip();

        this.$el.addClass('legend-inactive');
      } else if (this.state === 'inactive') {
        this.state = 'active';
        this.mapView.addTileLayer(this.tileLayer);

        if(this.gridLayer) {
          this.mapView.addGridLayer(this.gridLayer, true);
        }

        this.$el.removeClass('legend-inactive');
      }
    },

    getCartoData: function(cartodb_id, done) {
      Promise.resolve($.ajax({
        url: 'https://localdata.cartodb.com/api/v2/sql',
        type: 'GET',
        dataType: 'jsonp',
        data: {
          q: _.template(this.dataQuery, { cartodb_id: cartodb_id })
        }
      })).then(done).catch(function (error) {
        console.log('Error getting data from cartodb', error);
      });
    },

    hideTooltip: function() {
      if (!this.$tooltip) { return; }
      this.$tooltip.remove();
      delete this.$tooltip;
    },

    showTooltip: function(name) {
      if (this.$tooltip) {
        this.$tooltip.html(name);
      } else {
        this.$tooltip = $(this.tooltipTemplate({ name: name}));
        this.$tooltip.appendTo($('#map-tools .tooltips'));
      }
    },

    handleGridMouseout: function() {
      this.hideTooltip();
    },

    handleGridHover: function(event) {
      this.getCartoData(event.data.cartodb_id, function(data) {
        if (data.rows && data.rows.length > 0) {
          var name = data.rows[0][this.layerOptions.humanReadableField];
          this.showTooltip(name);
        }
      }.bind(this));
    },

    handleGridClick: function (event) {
      if (!event.data) {
        console.log("No data --returning");
        return;
      }

      var self = this;

      this.getCartoData(event.data.cartodb_id, function (data) {
        if (data.rows && data.rows.length > 0) {
          self.trigger('itemSelected', {
            view: new ItemView({
              data: data.rows[0],
              layerOptions: self.layerOptions
            }),
            latlng: event.latlng
          });
        }
      });
    }
  });
});
<|endoftext|>


<commit_before>import {checkRendering} from '../../lib/utils.js';

const usage = `Usage:
var customRangeSlider = connectRangeSlider(function render(params, isFirstRendering) {
  // params = {
  //   refine,
  //   range,
  //   start,
  //   format,
  //   instantSearchInstance,
  // }
});
search.addWidget(
  customRangeSlider({
    attributeName,
    min,
    max,
    precision
  });
);
Full documentation available at https://community.algolia.com/instantsearch.js/connectors/connectRangeSlider.html
`;

/**
 * @typedef {Object} CustomRangeSliderWidgetOptions
 * @param {string} attributeName Name of the attribute for faceting.
 * @param {number} [min] Minimal slider value, default to automatically computed from the result set
 * @param {number} [max] Maximal slider value, defaults to automatically computed from the result set
 * @param {number} [precision = 2]
 */

/**
 * @typedef {Object} RangeSliderRenderingOptions
 * @property {function} refine
 * @property {{min: number, max: number}} range
 * @property {[number, number]} start
 * @property {function} format
 * @property {InstantSearch} instantSearchInstance
 */

 /**
  * Connects a rendering function with the range slider business logic.
  * @param {function(RangeSliderRenderingOptions)} renderFn function that renders the range slider widget
  * @return {function(CustomRangeSliderWidgetOptions)} a widget factory for range slider widget
  */
export default function connectRangeSlider(renderFn) {
  checkRendering(renderFn, usage);

  return ({
    attributeName,
    min: userMin,
    max: userMax,
    precision = 2,
  }) => {
    if (!attributeName) {
      throw new Error(usage);
    }

    const formatToNumber = v => Number(Number(v).toFixed(precision));

    const sliderFormatter = {
      from: v => v,
      to: v => formatToNumber(v).toLocaleString(),
    };

    return {
      getConfiguration: originalConf => {
        const conf = {
          disjunctiveFacets: [attributeName],
        };

        const hasUserBounds = userMin !== undefined || userMax !== undefined;
        const boundsNotAlreadyDefined = !originalConf ||
          originalConf.numericRefinements &&
          originalConf.numericRefinements[attributeName] === undefined;

        if (hasUserBounds && boundsNotAlreadyDefined) {
          conf.numericRefinements = {[attributeName]: {}};
          if (userMin !== undefined) conf.numericRefinements[attributeName]['>='] = [userMin];
          if (userMax !== undefined) conf.numericRefinements[attributeName]['<='] = [userMax];
        }

        return conf;
      },

      _getCurrentRefinement(helper) {
        let min = helper.state.getNumericRefinement(attributeName, '>=');
        let max = helper.state.getNumericRefinement(attributeName, '<=');

        if (min && min.length) {
          min = min[0];
        } else {
          min = -Infinity;
        }

        if (max && max.length) {
          max = max[0];
        } else {
          max = Infinity;
        }

        return {
          min,
          max,
        };
      },

      init({helper, instantSearchInstance}) {
        this._refine = bounds => newValues => {
          helper.clearRefinements(attributeName);
          if (!bounds.min || newValues[0] > bounds.min) {
            helper.addNumericRefinement(attributeName, '>=', formatToNumber(newValues[0]));
          }
          if (!bounds.max || newValues[1] < bounds.max) {
            helper.addNumericRefinement(attributeName, '<=', formatToNumber(newValues[1]));
          }
          helper.search();
        };

        const stats = {
          min: userMin || null,
          max: userMax || null,
        };
        const currentRefinement = this._getCurrentRefinement(helper);

        renderFn({
          refine: this._refine(stats),
          range: {min: Math.floor(stats.min), max: Math.ceil(stats.max)},
          start: [currentRefinement.min, currentRefinement.max],
          format: sliderFormatter,
          instantSearchInstance,
        }, true);
      },

      render({results, helper, instantSearchInstance}) {
        const facet = (results.disjunctiveFacets || []).find(({name}) => name === attributeName);
        const stats = facet !== undefined && facet.stats !== undefined ? facet.stats : {
          min: null,
          max: null,
        };

        if (userMin !== undefined) stats.min = userMin;
        if (userMax !== undefined) stats.max = userMax;

        const currentRefinement = this._getCurrentRefinement(helper);

        renderFn({
          refine: this._refine(stats),
          range: {min: Math.floor(stats.min), max: Math.ceil(stats.max)},
          start: [currentRefinement.min, currentRefinement.max],
          format: sliderFormatter,
          instantSearchInstance,
        }, false);
      },
    };
  };
}
<commit_msg>feature(connector): jsDoc connectRangeSlider<commit_after>import {checkRendering} from '../../lib/utils.js';

const usage = `Usage:
var customRangeSlider = connectRangeSlider(function render(params, isFirstRendering) {
  // params = {
  //   refine,
  //   range,
  //   start,
  //   format,
  //   instantSearchInstance,
  // }
});
search.addWidget(
  customRangeSlider({
    attributeName,
    min,
    max,
    precision
  });
);
Full documentation available at https://community.algolia.com/instantsearch.js/connectors/connectRangeSlider.html
`;

/**
 * @typedef {Object} CustomRangeSliderWidgetOptions
 * @param {string} attributeName Name of the attribute for faceting.
 * @param {number} [min] Minimal slider value, default to automatically computed from the result set
 * @param {number} [max] Maximal slider value, defaults to automatically computed from the result set
 * @param {number} [precision = 2]
 */

/**
 * @typedef {Object} RangeSliderRenderingOptions
 * @property {function} refine set the next search state setting the range of the widget
 * @property {{min: number, max: number}} numeric bounds without the current range filter
 * @property {[number, number]} start current numeric bounds of the search
 * @property {{from: function, to: function}} formatter functions
 * @property {InstantSearch} instantSearchInstance the instance of instantsearch on which the widget is attached
 */

 /**
  * Connects a rendering function with the range slider business logic.
  * @param {function(RangeSliderRenderingOptions)} renderFn function that renders the range slider widget
  * @return {function(CustomRangeSliderWidgetOptions)} a widget factory for range slider widget
  */
export default function connectRangeSlider(renderFn) {
  checkRendering(renderFn, usage);

  return ({
    attributeName,
    min: userMin,
    max: userMax,
    precision = 2,
  }) => {
    if (!attributeName) {
      throw new Error(usage);
    }

    const formatToNumber = v => Number(Number(v).toFixed(precision));

    const sliderFormatter = {
      from: v => v,
      to: v => formatToNumber(v).toLocaleString(),
    };

    return {
      getConfiguration: originalConf => {
        const conf = {
          disjunctiveFacets: [attributeName],
        };

        const hasUserBounds = userMin !== undefined || userMax !== undefined;
        const boundsNotAlreadyDefined = !originalConf ||
          originalConf.numericRefinements &&
          originalConf.numericRefinements[attributeName] === undefined;

        if (hasUserBounds && boundsNotAlreadyDefined) {
          conf.numericRefinements = {[attributeName]: {}};
          if (userMin !== undefined) conf.numericRefinements[attributeName]['>='] = [userMin];
          if (userMax !== undefined) conf.numericRefinements[attributeName]['<='] = [userMax];
        }

        return conf;
      },

      _getCurrentRefinement(helper) {
        let min = helper.state.getNumericRefinement(attributeName, '>=');
        let max = helper.state.getNumericRefinement(attributeName, '<=');

        if (min && min.length) {
          min = min[0];
        } else {
          min = -Infinity;
        }

        if (max && max.length) {
          max = max[0];
        } else {
          max = Infinity;
        }

        return {
          min,
          max,
        };
      },

      init({helper, instantSearchInstance}) {
        this._refine = bounds => newValues => {
          helper.clearRefinements(attributeName);
          if (!bounds.min || newValues[0] > bounds.min) {
            helper.addNumericRefinement(attributeName, '>=', formatToNumber(newValues[0]));
          }
          if (!bounds.max || newValues[1] < bounds.max) {
            helper.addNumericRefinement(attributeName, '<=', formatToNumber(newValues[1]));
          }
          helper.search();
        };

        const stats = {
          min: userMin || null,
          max: userMax || null,
        };
        const currentRefinement = this._getCurrentRefinement(helper);

        renderFn({
          refine: this._refine(stats),
          range: {min: Math.floor(stats.min), max: Math.ceil(stats.max)},
          start: [currentRefinement.min, currentRefinement.max],
          format: sliderFormatter,
          instantSearchInstance,
        }, true);
      },

      render({results, helper, instantSearchInstance}) {
        const facet = (results.disjunctiveFacets || []).find(({name}) => name === attributeName);
        const stats = facet !== undefined && facet.stats !== undefined ? facet.stats : {
          min: null,
          max: null,
        };

        if (userMin !== undefined) stats.min = userMin;
        if (userMax !== undefined) stats.max = userMax;

        const currentRefinement = this._getCurrentRefinement(helper);

        renderFn({
          refine: this._refine(stats),
          range: {min: Math.floor(stats.min), max: Math.ceil(stats.max)},
          start: [currentRefinement.min, currentRefinement.max],
          format: sliderFormatter,
          instantSearchInstance,
        }, false);
      },
    };
  };
}
<|endoftext|>