<commit_before>/*
 * Backpack - Skyscanner's Design System
 *
 * Copyright 2016-2021 Skyscanner 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.
 */
/* @flow strict */

import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Table, AutoSizer } from 'react-virtualized';
import { cssModules } from 'bpk-react-utils';
import _omit from 'lodash/omit';

import STYLES from './BpkDataTable.scss';
import BpkDataTableColumn from './BpkDataTableColumn';
import hasChildrenOfType from './hasChildrenOfType';
import makeSorter, { type Sorter } from './sorter';
import type { Props } from './common-types';

const getClassName = cssModules(STYLES);
const omittedTableProps = ['rowGetter', 'rowCount', 'onHeaderClick'];

type State<Row> = {
  sorter: Sorter<Row>,
  rowSelected: ?number,
};

class BpkDataTable<Row> extends Component<Props<Row>, State<Row>> {
  static defaultProps = {
    ...Table.defaultProps,
    width: null,
    headerHeight: 60,
    rowHeight: 60,
    gridStyle: { direction: undefined }, // This is required for rows to automatically respect rtl
    defaultColumnSortIndex: 0,
    sort: null,
    sortBy: null,
    sortDirection: null,
  };

  constructor(props: Props<Row>) {
    super();

    this.state = {
      sorter: makeSorter(props),
      rowSelected: undefined,
    };
  }

  UNSAFE_componentWillReceiveProps(nextProps: Props<Row>) {
    if (nextProps.rows !== this.props.rows) {
      this.state.sorter.propsChange(nextProps);
    }
  }

  onRowClicked = ({ index }: { index: number }) => {
    if (this.state.rowSelected === index) {
      this.setState({ rowSelected: undefined });
    } else {
      this.setState({ rowSelected: index });
    }
    if (this.props.onRowClick !== undefined) {
      this.props.onRowClick(this.state.sorter.getRow(index));
    }
  };

  onHeaderClick = ({
    dataKey: sortBy,
    event,
  }: {
    dataKey: string,
    event: SyntheticEvent<HTMLDivElement>,
  }) => {
    const column = React.Children.toArray(this.props.children).find(
      child => child.props.dataKey === sortBy,
    );

    if (!column) {
      return;
    }

    if (column.props.disableSort === true) {
      return;
    }

    // See: https://reactjs.org/docs/events.html#event-pooling
    const eventTarget = event.currentTarget;

    this.setState(prevState => ({
      sorter: prevState.sorter.onHeaderClick(sortBy, eventTarget, column),
    }));
  };

  rowClassName = (
    consumerClassName: ?string | ?(({ index: number }) => string),
    { index }: { index: number },
  ) => {
    const classNames = [getClassName('bpk-data-table__row')];
    if (this.state.rowSelected === index) {
      classNames.push(getClassName('bpk-data-table__row--selected'));
    }
    if (this.props.onRowClick !== undefined) {
      classNames.push(getClassName('bpk-data-table__row--clickable'));
    }
    if (index === -1) {
      classNames.push(getClassName('bpk-data-table__header-row'));
    }
    if (consumerClassName) {
      if (typeof consumerClassName === 'function') {
        classNames.push(consumerClassName({ index }));
      } else {
        classNames.push(consumerClassName);
      }
    }
    return classNames;
  };

  renderTable(width: ?number) {
    const {
      children,
      className,
      headerClassName,
      rowClassName,
      sort,
      ...restOfProps
    } = this.props;

    const classNames = [getClassName('bpk-data-table')];
    if (className) {
      classNames.push(className);
    }

    const headerClassNames = [getClassName('bpk-data-table__header-column')];
    if (headerClassName) {
      headerClassNames.push(headerClassName);
    }

    return (
      // $FlowFixMe[cannot-spread-inexact]
      <Table
        {...restOfProps}
        className={classNames.join(' ')}
        width={width}
        rowCount={this.state.sorter.rowCount}
        rowGetter={({ index }) => this.state.sorter.getRow(index)}
        headerClassName={headerClassNames.join(' ')}
        rowClassName={row => this.rowClassName(rowClassName, row)}
        onRowClick={this.onRowClicked}
        onHeaderClick={this.onHeaderClick}
        {...this.state.sorter.sortProps}
      >
        {React.Children.map(children, BpkDataTableColumn.toColumn)}
      </Table>
    );
  }

  render() {
    if (this.props.width !== null) {
      return this.renderTable(this.props.width);
    }

    return (
      <AutoSizer disableHeight>
        {({ width }) => this.renderTable(width)}
      </AutoSizer>
    );
  }
}

BpkDataTable.propTypes = {
  ..._omit(Table.propTypes, omittedTableProps),
  rows: PropTypes.arrayOf(Object).isRequired,
  children: hasChildrenOfType(BpkDataTableColumn),
  width: PropTypes.number,
  headerHeight: PropTypes.number,
  className: PropTypes.string,
  defaultColumnSortIndex: PropTypes.number,
  sort: PropTypes.func,
  sortBy: PropTypes.string,
  sortDirection: PropTypes.oneOf('ASC', 'DESC'),
};

export default BpkDataTable;
<commit_msg>Use event.target as we need event propagation<commit_after>/*
 * Backpack - Skyscanner's Design System
 *
 * Copyright 2016-2021 Skyscanner 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.
 */
/* @flow strict */

import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Table, AutoSizer } from 'react-virtualized';
import { cssModules } from 'bpk-react-utils';
import _omit from 'lodash/omit';

import STYLES from './BpkDataTable.scss';
import BpkDataTableColumn from './BpkDataTableColumn';
import hasChildrenOfType from './hasChildrenOfType';
import makeSorter, { type Sorter } from './sorter';
import type { Props } from './common-types';

const getClassName = cssModules(STYLES);
const omittedTableProps = ['rowGetter', 'rowCount', 'onHeaderClick'];

type State<Row> = {
  sorter: Sorter<Row>,
  rowSelected: ?number,
};

class BpkDataTable<Row> extends Component<Props<Row>, State<Row>> {
  static defaultProps = {
    ...Table.defaultProps,
    width: null,
    headerHeight: 60,
    rowHeight: 60,
    gridStyle: { direction: undefined }, // This is required for rows to automatically respect rtl
    defaultColumnSortIndex: 0,
    sort: null,
    sortBy: null,
    sortDirection: null,
  };

  constructor(props: Props<Row>) {
    super();

    this.state = {
      sorter: makeSorter(props),
      rowSelected: undefined,
    };
  }

  UNSAFE_componentWillReceiveProps(nextProps: Props<Row>) {
    if (nextProps.rows !== this.props.rows) {
      this.state.sorter.propsChange(nextProps);
    }
  }

  onRowClicked = ({ index }: { index: number }) => {
    if (this.state.rowSelected === index) {
      this.setState({ rowSelected: undefined });
    } else {
      this.setState({ rowSelected: index });
    }
    if (this.props.onRowClick !== undefined) {
      this.props.onRowClick(this.state.sorter.getRow(index));
    }
  };

  onHeaderClick = ({
    dataKey: sortBy,
    event,
  }: {
    dataKey: string,
    event: SyntheticEvent<HTMLDivElement>,
  }) => {
    const column = React.Children.toArray(this.props.children).find(
      child => child.props.dataKey === sortBy,
    );

    if (!column) {
      return;
    }

    if (column.props.disableSort === true) {
      return;
    }

    // See: https://reactjs.org/docs/legacy-event-pooling.html
    const eventTarget = event.target;

    this.setState(prevState => ({
      sorter: prevState.sorter.onHeaderClick(sortBy, eventTarget, column),
    }));
  };

  rowClassName = (
    consumerClassName: ?string | ?(({ index: number }) => string),
    { index }: { index: number },
  ) => {
    const classNames = [getClassName('bpk-data-table__row')];
    if (this.state.rowSelected === index) {
      classNames.push(getClassName('bpk-data-table__row--selected'));
    }
    if (this.props.onRowClick !== undefined) {
      classNames.push(getClassName('bpk-data-table__row--clickable'));
    }
    if (index === -1) {
      classNames.push(getClassName('bpk-data-table__header-row'));
    }
    if (consumerClassName) {
      if (typeof consumerClassName === 'function') {
        classNames.push(consumerClassName({ index }));
      } else {
        classNames.push(consumerClassName);
      }
    }
    return classNames;
  };

  renderTable(width: ?number) {
    const {
      children,
      className,
      headerClassName,
      rowClassName,
      sort,
      ...restOfProps
    } = this.props;

    const classNames = [getClassName('bpk-data-table')];
    if (className) {
      classNames.push(className);
    }

    const headerClassNames = [getClassName('bpk-data-table__header-column')];
    if (headerClassName) {
      headerClassNames.push(headerClassName);
    }

    return (
      // $FlowFixMe[cannot-spread-inexact]
      <Table
        {...restOfProps}
        className={classNames.join(' ')}
        width={width}
        rowCount={this.state.sorter.rowCount}
        rowGetter={({ index }) => this.state.sorter.getRow(index)}
        headerClassName={headerClassNames.join(' ')}
        rowClassName={row => this.rowClassName(rowClassName, row)}
        onRowClick={this.onRowClicked}
        onHeaderClick={this.onHeaderClick}
        {...this.state.sorter.sortProps}
      >
        {React.Children.map(children, BpkDataTableColumn.toColumn)}
      </Table>
    );
  }

  render() {
    if (this.props.width !== null) {
      return this.renderTable(this.props.width);
    }

    return (
      <AutoSizer disableHeight>
        {({ width }) => this.renderTable(width)}
      </AutoSizer>
    );
  }
}

BpkDataTable.propTypes = {
  ..._omit(Table.propTypes, omittedTableProps),
  rows: PropTypes.arrayOf(Object).isRequired,
  children: hasChildrenOfType(BpkDataTableColumn),
  width: PropTypes.number,
  headerHeight: PropTypes.number,
  className: PropTypes.string,
  defaultColumnSortIndex: PropTypes.number,
  sort: PropTypes.func,
  sortBy: PropTypes.string,
  sortDirection: PropTypes.oneOf('ASC', 'DESC'),
};

export default BpkDataTable;
<|endoftext|>


<commit_before>/*global d3, $:false */
'use strict';

var app = angular.module('mean.overview');

app.directive('d3Test', ['TasklistService', 'User', function (TasklistService, User) {

    // The amount to move down y for each level of depth
    var levelDepth = 180;

    ///**
    // * Allow click events to be triggered programmatically
    // */
    //$.fn.d3Click = function () {
    //    this.each(function (i, e) {
    //        var evt = document.createEvent('MouseEvents');
    //        evt.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
    //
    //        e.dispatchEvent(evt);
    //    });
    //};
    /**
     * Reworking of original tree graph creation
     *
     * Zooming and panning: http://stackoverflow.com/questions/17405638/d3-js-zooming-and-panning-a-collapsible-tree-diagram
     * Zoomable, panable, scalable tree: http://bl.ocks.org/robschmuecker/7880033
     *
     * Critical Path Method: https://github.com/MilanPecov/critical-path-method
     * .NET CPM: http://www.leniel.net/2007/12/critical-path-method.html#sthash.MFd5MpNB.dpbs
     * Python implementation: https://github.com/dhenderson/criticalpy
     * JS implementation, not sure if it's any good: https://github.com/maxinfang/Cpath
     */
    var updatedTree = function () {
        var heightWidthModifierObj = {
            h1: 20,
            h2: 120,
            w1: 20,
            w2: 120
        };
        // Determine width based on modifier
        var height = 1280,
            // Determine height based on modifier
            width = $('#task_graph').width(),
            i = 0,
            root;

        // Create SVG element, append g, translate from top left (120, 20)
        var graph = d3.select('#task_graph')
        .append('svg:svg')
        .attr('width', width)
        .attr('height', height)
        .append('svg:g')
        .attr('transform', 'translate(' + 0 + ',' + heightWidthModifierObj.h1 + ')');

        // Tree
        var tree = d3.layout.tree().size([width, height]);
        // Projections based on data points
        var diagonal = d3.svg.diagonal().projection(function (d) {
            return [d.x, d.y];
        });

        d3.json('/tasks/team/graph/' + User.getIdentity().teams[0], function (json) {
            root = json;
            //root.x0 = width / 2;
            root.x0 = 0;
            root.y0 = 0;
            createNodes(root);
            // Reposition nodes based on estimate
            repositionNodes();
        });

        /**
         * Get the original coordinates a node to be repositioned
         * @param originalTransform
         * @returns {Array}
         */
        var getOriginalCoordinates = function (originalTransform) {
            var subT = originalTransform.substr(originalTransform.indexOf('(') + 1);
            var coordinatesOnly = subT.substr(0, subT.indexOf(')'));
            return coordinatesOnly.split(',');
        };

        /**
         * Reposition nodes based on time estimates
         */
        var repositionNodes = function () {
            // Variables for moving the nodes themselves
            var gParent, transitionY, originalTransform, coordinatesSplit, xCoordinate, yCoordinate,
                newYCoordinate, thisChildCircle;
            // Variables for moving the paths
            var extendLength, pathD, pathDSplit, pathLastDrawInstruction, pathXCoordinate, pathYCoordinate;

            /**
             * Reposition individual nodes based on estimate or based on parent's estimate
             * @param d
             * @param nodeDomElement
             * @param amount
             */
            var repositionIndividualNode = function (d, nodeDomElement, amount) {
                // Get the grouping parent
                gParent = $(nodeDomElement).parent().get(0);
                // Move the node down based on the estimate, or else based on the parent node's estimate, if that moved
                transitionY = amount || (d.estimate - 1) * levelDepth;
                // Get the original transform property of the node
                originalTransform = gParent.getAttribute('transform');
                // Get the coordinates of the original transform property
                coordinatesSplit = getOriginalCoordinates(originalTransform);
                xCoordinate = coordinatesSplit[0];
                yCoordinate = coordinatesSplit[1];
                newYCoordinate = (parseInt(yCoordinate) + transitionY);
                // Move the node to its new position
                gParent.setAttribute('transform', 'translate(' + xCoordinate + ',' + newYCoordinate + ')');
                // Move all children an equal amount
                _.each(d.children, function (nodeChild) {
                    // Find the nodes which actually need to be moved
                    thisChildCircle = $('.' + nodeChild.title + '.reposition').find('circle');
                    // Move each that needs it
                    _.each(thisChildCircle, function (child) {
                        repositionIndividualNode(nodeChild, child, transitionY);
                    });
                });
            };

            /**
             * Redraw paths when nodes are repositioned
             * @param d
             * @param thisPath
             * @param amount
             */
            var redrawPathsBasedOnReposition = function (d, thisPath, amount) {
                // Don't process closed nodes
                if (!d.source.children || !$('.' + d.target.title).length) {
                    return;
                }
                // Get the length of redraw based on estimate, or amount parent moves
                extendLength = amount || (d.target.estimate - 1) * levelDepth;
                // If this is a child node, then perform a translate, rather extending the path
                if (amount) {
                    $(thisPath).attr('transform', 'translate(0, ' + amount + ')');
                } else {
                    // Get original svg path d attribute
                    pathD = thisPath.getAttribute('d');
                    // Split coordinates
                    pathDSplit = pathD.split(' ');
                    // Split the last coordinate instructions, so we can get coordinates to extend from
                    pathLastDrawInstruction = pathDSplit[pathDSplit.length -1].split(',');
                    // Get original x and y
                    pathXCoordinate = pathLastDrawInstruction[0];
                    pathYCoordinate = pathLastDrawInstruction[1];
                    // Keep x the same, extend y as far down as necessary
                    thisPath.setAttribute('d', pathD + ' L' + pathXCoordinate + ',' + (parseInt(pathYCoordinate) + extendLength));
                }
                // If there's a target, iterate children to find those that need to move as well
                if (d.target) {
                    _.each(d.target.children, function (nodeChild) {
                        // Translate the path of each child node
                        d3.select('#' + d.target.title + '_' + nodeChild.title).each(function (childData) {
                            redrawPathsBasedOnReposition(childData, $('#' + d.target.title + '_' + nodeChild.title).get(0), extendLength);
                        });
                    });
                }
            };

            // @todo Not sure why, but I need to set a timeout or the lines never finish drawing.
            // It seems as though this if this runs before everything is drawn initially, it interrupts the drawing
            // This might be a clue: http://stackoverflow.com/questions/10692100/invoke-a-callback-at-the-end-of-a-transition
            setTimeout(function () {
                d3.selectAll('circle').transition().each('end', function (d) {
                    if (typeof d.estimate !== 'undefined' && d.estimate > 1) {
                        repositionIndividualNode(d, this);
                    }
                });
                // Lengthen the path when necessary
                d3.selectAll('path').transition().each('end', function (d) {
                    if (d.target.estimate > 1) {
                        redrawPathsBasedOnReposition(d, this);
                    }
                });
            }, 500);
        };

        /**
         * Toggle and update on click
         */
        var clickInProgress = false,
            opening = null;
        var toggleClick = function (nodeEnter) {
            nodeEnter.on('click', function (d) {
                // If no children, don't do anything
                if (!d.children && !d._children) {
                    return;
                }
                toggle(d);
                createNodes(d);
                clickInProgress = false;
                opening = null;
            });
        };

        /**
         * Append text to each node
         */
        var appendText = function (nodeEnter) {
            nodeEnter.append('svg:text').attr('x', function (d) {
                // On first iteration
                return d.children || d._children ? -10 : 10;
            })
            .attr('dy', '.35em')
                // If there are children, anchor text at end. Otherwise, at start
            .attr('text-anchor', function (d) {
                return (d.children || d._children) ? 'end' : 'start';
                // Place the name as the text on each node
            }).text(function (d) {
                return d.title;
                // 1e-6 is a workaround for text flicker when transitioning text
            }).style('fill-opacity', 1e-6);
        };

        //var cloneAndConsole = function (node) {
        //    console.log(_.clone(node));
        //};

        var depthModifierByEstimate = 1;

        /**
         * Update data, draw nodes, etc
         */
        var createNodes = function (source) {
            var duration = 500;

            /**
             * This will automatically make the nodes stretch as far x as possible, and close to as far y as possible
             */
            var nodes = tree.nodes(root);

            /**
             * Determine x position based on how far down the hierarchy the node resides (0-ordered)
             */
            nodes.forEach(function (d) {
                //console.log(depthModifier);
                //depthModifier = depthModifier + 10;
                depthModifierByEstimate = 1;
                d.y = d.depth * levelDepth;
                if (d.estimate && d.estimate > 1) {
                    depthModifierByEstimate = d.estimate;
                }
                //if (d.title === 'Task13') {
                //    console.log('**************DEPTH BEFORE MODIFICATION**********');
                //    console.log((d.depth * 180));
                //    console.log('**************DEPTH AFTER MODIFICATION**********');
                //    console.log((d.depth * 180) * depthModifierByEstimate);
                //}
                //d.y = d.y + (180 * depthModifierByEstimate);
            });

            // M117.55555555555556,720C117.55555555555556,945 176.33333333333334,945 176.33333333333334,1170

            /**
             * Assign each node an id, or else return the id already assigned
             */
            var node = graph.selectAll('g.node').data(nodes, function (d) {
                if (d.id) {
                    return d.id;
                }
                d.id = i;
                i = i + 1;
                return d.id;
            });

            /**
            * Append each node on top of the parent node for their stating position
            */
            var nodeEnter = node.enter().append('svg:g').attr('class', 'node').attr('transform', function (d) {
                var originalClass = $(this).attr('class');
                // Add the title of the node to the class
                var newClass = originalClass + ' ' + d.title;
                // Note nodes which will be repositioned
                if (d.parent && d.parent.estimate && d.parent.estimate > 1) {
                    newClass = newClass + ' reposition';
                }
                // Set the new class
                $(this).attr('class', newClass);
                return 'translate(0,0)';
            });

            /**
            * Append children for each that has them, then style them
            */
            nodeEnter.append('svg:circle').attr('r', 1e-6).style('fill', function (d) {
                return d._children ? 'lightsteelblue' : '#fff';
            });

            /**
            * Move the nodes to their proper locations
            */
            var nodeUpdate = node.transition().duration(duration).attr('transform', function (d) {
                return 'translate(' + d.x + ',' + d.y + ')';
            });

            /**
            * Define how big the circles are, and then color them based on whether children exist
            */
            nodeUpdate.select('circle').attr('r', 4.5).style('fill', function (d) {
                return d._children ? 'lightsteelblue' : '#fff';
            });

            /**
            * When removing nodes, move them back to the parents position, fade out circle and text
            */
            var nodeExit = node.exit().transition().duration(duration).attr('transform', function (d) {
                return 'translate(' + source.x + ',' + source.y + ')';
            }).remove();
            nodeExit.select('circle').attr('r', 1e-6);
            nodeExit.select('text').style('fill-opacity', 1e-6);

            /**
            * Stash the old positions for transition.
            */
            nodes.forEach(function (d) {
                d.x0 = d.x;
                d.y0 = d.y;
            });

            /**
            * Draw the lines
            */
            var link = graph.selectAll('path.link').data(tree.links(nodes), function (d) {
                return d.target.id;
            });

            // Enter any new links at the parent's previous position.
            link.enter().insert('svg:path', 'g').attr('class', 'link').attr('d', function (d) {
                // Add ID for the path for finding during repositioning
                $(this).attr('id', d.source.title + '_' + d.target.title);
                var o = {x: source.x0, y: source.y0};
                return diagonal({source: o, target: o});
            }).transition().duration(duration).attr('d', diagonal);

            // Transition links to their new position.
            link.transition().duration(duration).attr('d', diagonal);

            // Transition exiting nodes to the parent's new position.
            link.exit().transition().duration(duration).attr('d', function (d) {
                var o = {x: source.x, y: source.y};
                return diagonal({source: o, target: o});
            }).remove();

            /**
            * Append and fill text
            */
            appendText(nodeEnter);
            nodeUpdate.select('text').style('fill-opacity', 1);

            // Toggle each node on click
            toggleClick(nodeEnter);
        };

        // Toggle children.
        function toggle(d) {
            // Remove children nodes that exist
            if (d.children) {
                d._children = d.children;
                d.children = null;
                // Add children nodes back in
            } else {
                d.children = d._children;
                d._children = null;
            }
            repositionNodes();
        }
    };

    return {
        restrict: 'A',
        templateUrl: 'overview/views/directiveTemplates/d3_test.html',
        scope: {
            data: '='
        },
        link: function (scope, element, attrs) {
            updatedTree();
        }
    };
}]);<commit_msg>Only move child nodes which have the same x coordinate as the parent nodes, since multiple nodes of the same title may need to be moved different amounts<commit_after>/*global d3, $:false */
'use strict';

var app = angular.module('mean.overview');

app.directive('d3Test', ['TasklistService', 'User', function (TasklistService, User) {

    // The amount to move down y for each level of depth
    var levelDepth = 100;

    ///**
    // * Allow click events to be triggered programmatically
    // */
    //$.fn.d3Click = function () {
    //    this.each(function (i, e) {
    //        var evt = document.createEvent('MouseEvents');
    //        evt.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
    //
    //        e.dispatchEvent(evt);
    //    });
    //};
    /**
     * Reworking of original tree graph creation
     *
     * Zooming and panning: http://stackoverflow.com/questions/17405638/d3-js-zooming-and-panning-a-collapsible-tree-diagram
     * Zoomable, panable, scalable tree: http://bl.ocks.org/robschmuecker/7880033
     *
     * Critical Path Method: https://github.com/MilanPecov/critical-path-method
     * .NET CPM: http://www.leniel.net/2007/12/critical-path-method.html#sthash.MFd5MpNB.dpbs
     * Python implementation: https://github.com/dhenderson/criticalpy
     * JS implementation, not sure if it's any good: https://github.com/maxinfang/Cpath
     */
    var updatedTree = function () {
        var heightWidthModifierObj = {
            h1: 20,
            h2: 120,
            w1: 20,
            w2: 120
        };
        // Determine width based on modifier
        var height = 1280,
            // Determine height based on modifier
            width = $('#task_graph').width(),
            i = 0,
            root;

        // Create SVG element, append g, translate from top left (120, 20)
        var graph = d3.select('#task_graph')
        .append('svg:svg')
        .attr('width', width)
        .attr('height', height)
        .append('svg:g')
        .attr('transform', 'translate(' + 0 + ',' + heightWidthModifierObj.h1 + ')');

        // Tree
        var tree = d3.layout.tree().size([width, height]);
        // Projections based on data points
        var diagonal = d3.svg.diagonal().projection(function (d) {
            return [d.x, d.y];
        });
        d3.json('/tasks/team/graph/' + User.getIdentity().teams[0], function (json) {
            root = json;
            //root.x0 = width / 2;
            root.x0 = 0;
            root.y0 = 0;
            createNodes(root);
            // Reposition nodes based on estimate
            repositionNodes();
        });

        /**
         * Get the original coordinates a node to be repositioned
         * @param originalTransform
         * @returns {Array}
         */
        var getOriginalCoordinates = function (originalTransform) {
            var subT = originalTransform.substr(originalTransform.indexOf('(') + 1);
            var coordinatesOnly = subT.substr(0, subT.indexOf(')'));
            return coordinatesOnly.split(',');
        };

        /**
         * Reposition nodes based on time estimates
         */
        var repositionNodes = function () {
            // Variables for moving the nodes themselves
            var gParent, transitionY, originalTransform, coordinatesSplit, xCoordinate, yCoordinate,
                newYCoordinate, thisChildCircle;
            // Variables for moving the paths
            var extendLength, pathD, pathDSplit, pathLastDrawInstruction, pathXCoordinate, pathYCoordinate;

            /**
             * Reposition individual nodes based on estimate or based on parent's estimate
             * @param d
             * @param nodeDomElement
             * @param amount
             */
            var repositionIndividualNode = function (d, nodeDomElement, amount) {
                // Get the grouping parent
                gParent = $(nodeDomElement).parent().get(0);
                // Move the node down based on the estimate, or else based on the parent node's estimate, if that moved
                transitionY = amount || (d.estimate - 1) * levelDepth;
                // Get the original transform property of the node
                originalTransform = gParent.getAttribute('transform');
                // Get the coordinates of the original transform property
                coordinatesSplit = getOriginalCoordinates(originalTransform);
                xCoordinate = coordinatesSplit[0];
                yCoordinate = coordinatesSplit[1];
                newYCoordinate = (parseInt(yCoordinate) + transitionY);
                // Move the node to its new position
                gParent.setAttribute('transform', 'translate(' + xCoordinate + ',' + newYCoordinate + ')');
                // Move all children an equal amount
                _.each(d.children, function (nodeChild) {
                    // Find the nodes which actually need to be moved. Since multiple nodes of the same
                    // task may need to be moved different amounts, match on x coordinate of parent
                    thisChildCircle = $('.' + nodeChild.title + '.reposition').find('circle').filter(function () {
                        var regex = new RegExp('translate.' + xCoordinate + ',');
                        return regex.test($(this).parent().attr('transform'));
                    });
                    //Move each that needs it
                    _.each(thisChildCircle, function (child) {
                        repositionIndividualNode(nodeChild, child, transitionY);
                    });
                });
            };

            /**
             * Redraw paths when nodes are repositioned
             * @param d
             * @param thisPath
             * @param amount
             */
            var redrawPathsBasedOnReposition = function (d, thisPath, amount) {
                // Don't process closed nodes
                if (!d.source.children || !$('.' + d.target.title).length) {
                    return;
                }
                // Get the length of redraw based on estimate, or amount parent moves
                extendLength = amount || (d.target.estimate - 1) * levelDepth;
                // If this is a child node, then perform a translate, rather extending the path
                if (amount) {
                    $(thisPath).attr('transform', 'translate(0, ' + amount + ')');
                } else {
                    // Get original svg path d attribute
                    pathD = thisPath.getAttribute('d');
                    // Split coordinates
                    pathDSplit = pathD.split(' ');
                    // Split the last coordinate instructions, so we can get coordinates to extend from
                    pathLastDrawInstruction = pathDSplit[pathDSplit.length -1].split(',');
                    // Get original x and y
                    pathXCoordinate = pathLastDrawInstruction[0];
                    pathYCoordinate = pathLastDrawInstruction[1];
                    // Keep x the same, extend y as far down as necessary
                    thisPath.setAttribute('d', pathD + ' L' + pathXCoordinate + ',' + (parseInt(pathYCoordinate) + extendLength));
                }
                // If there's a target, iterate children to find those that need to move as well
                if (d.target) {
                    _.each(d.target.children, function (nodeChild) {
                        // Translate the path of each child node
                        d3.select('#' + d.target.title + '_' + nodeChild.title).each(function (childData) {
                            redrawPathsBasedOnReposition(childData, $('#' + d.target.title + '_' + nodeChild.title).get(0), extendLength);
                        });
                    });
                }
            };

            // @todo Not sure why, but I need to set a timeout or the lines never finish drawing.
            // It seems as though this if this runs before everything is drawn initially, it interrupts the drawing
            // This might be a clue: http://stackoverflow.com/questions/10692100/invoke-a-callback-at-the-end-of-a-transition
            setTimeout(function () {
                d3.selectAll('circle').transition().each('end', function (d) {
                    if (typeof d.estimate !== 'undefined' && d.estimate > 1) {
                        repositionIndividualNode(d, this);
                    }
                });
                // Lengthen the path when necessary
                d3.selectAll('path').transition().each('end', function (d) {
                    if (d.target.estimate > 1) {
                        redrawPathsBasedOnReposition(d, this);
                    }
                });
            }, 500);
        };

        /**
         * Toggle and update on click
         */
        var clickInProgress = false,
            opening = null;
        var toggleClick = function (nodeEnter) {
            nodeEnter.on('click', function (d) {
                // If no children, don't do anything
                if (!d.children && !d._children) {
                    return;
                }
                toggle(d);
                createNodes(d);
                clickInProgress = false;
                opening = null;
            });
        };

        /**
         * Append text to each node
         */
        var appendText = function (nodeEnter) {
            nodeEnter.append('svg:text').attr('x', function (d) {
                // On first iteration
                return d.children || d._children ? -10 : 10;
            })
            .attr('dy', '.35em')
                // If there are children, anchor text at end. Otherwise, at start
            .attr('text-anchor', function (d) {
                return (d.children || d._children) ? 'end' : 'start';
                // Place the name as the text on each node
            }).text(function (d) {
                return d.title;
                // 1e-6 is a workaround for text flicker when transitioning text
            }).style('fill-opacity', 1e-6);
        };

        //var cloneAndConsole = function (node) {
        //    console.log(_.clone(node));
        //};

        var depthModifierByEstimate = 1;

        /**
         * Update data, draw nodes, etc
         */
        var createNodes = function (source) {
            var duration = 500;

            /**
             * This will automatically make the nodes stretch as far x as possible, and close to as far y as possible
             */
            var nodes = tree.nodes(root);

            /**
             * Determine x position based on how far down the hierarchy the node resides (0-ordered)
             */
            nodes.forEach(function (d) {
                //console.log(depthModifier);
                //depthModifier = depthModifier + 10;
                depthModifierByEstimate = 1;
                d.y = d.depth * levelDepth;
                if (d.estimate && d.estimate > 1) {
                    depthModifierByEstimate = d.estimate;
                }
                //if (d.title === 'Task13') {
                //    console.log('**************DEPTH BEFORE MODIFICATION**********');
                //    console.log((d.depth * 180));
                //    console.log('**************DEPTH AFTER MODIFICATION**********');
                //    console.log((d.depth * 180) * depthModifierByEstimate);
                //}
                //d.y = d.y + (180 * depthModifierByEstimate);
            });

            // M117.55555555555556,720C117.55555555555556,945 176.33333333333334,945 176.33333333333334,1170

            /**
             * Assign each node an id, or else return the id already assigned
             */
            var node = graph.selectAll('g.node').data(nodes, function (d) {
                if (d.id) {
                    return d.id;
                }
                d.id = i;
                i = i + 1;
                return d.id;
            });

            /**
            * Append each node on top of the parent node for their stating position
            */
            var nodeEnter = node.enter().append('svg:g').attr('class', 'node').attr('transform', function (d) {
                var originalClass = $(this).attr('class');
                // Add the title of the node to the class
                var newClass = originalClass + ' ' + d.title;
                // Note nodes which will be repositioned
                if (d.parent && d.parent.estimate && d.parent.estimate > 1) {
                    newClass = newClass + ' reposition';
                }
                // Set the new class
                $(this).attr('class', newClass);
                return 'translate(0,0)';
            });

            /**
            * Append children for each that has them, then style them
            */
            nodeEnter.append('svg:circle').attr('r', 1e-6).style('fill', function (d) {
                return d._children ? 'lightsteelblue' : '#fff';
            });

            /**
            * Move the nodes to their proper locations
            */
            var nodeUpdate = node.transition().duration(duration).attr('transform', function (d) {
                return 'translate(' + d.x + ',' + d.y + ')';
            });

            /**
            * Define how big the circles are, and then color them based on whether children exist
            */
            nodeUpdate.select('circle').attr('r', 4.5).style('fill', function (d) {
                return d._children ? 'lightsteelblue' : '#fff';
            });

            /**
            * When removing nodes, move them back to the parents position, fade out circle and text
            */
            var nodeExit = node.exit().transition().duration(duration).attr('transform', function (d) {
                return 'translate(' + source.x + ',' + source.y + ')';
            }).remove();
            nodeExit.select('circle').attr('r', 1e-6);
            nodeExit.select('text').style('fill-opacity', 1e-6);

            /**
            * Stash the old positions for transition.
            */
            nodes.forEach(function (d) {
                d.x0 = d.x;
                d.y0 = d.y;
            });

            /**
            * Draw the lines
            */
            var link = graph.selectAll('path.link').data(tree.links(nodes), function (d) {
                return d.target.id;
            });

            // Enter any new links at the parent's previous position.
            link.enter().insert('svg:path', 'g').attr('class', 'link').attr('d', function (d) {
                // Add ID for the path for finding during repositioning
                $(this).attr('id', d.source.title + '_' + d.target.title);
                var o = {x: source.x0, y: source.y0};
                return diagonal({source: o, target: o});
            }).transition().duration(duration).attr('d', diagonal);

            // Transition links to their new position.
            link.transition().duration(duration).attr('d', diagonal);

            // Transition exiting nodes to the parent's new position.
            link.exit().transition().duration(duration).attr('d', function (d) {
                var o = {x: source.x, y: source.y};
                return diagonal({source: o, target: o});
            }).remove();

            /**
            * Append and fill text
            */
            appendText(nodeEnter);
            nodeUpdate.select('text').style('fill-opacity', 1);

            // Toggle each node on click
            toggleClick(nodeEnter);
        };

        // Toggle children.
        function toggle(d) {
            // Remove children nodes that exist
            if (d.children) {
                d._children = d.children;
                d.children = null;
                // Add children nodes back in
            } else {
                d.children = d._children;
                d._children = null;
            }
            repositionNodes();
        }
    };

    return {
        restrict: 'A',
        templateUrl: 'overview/views/directiveTemplates/d3_test.html',
        scope: {
            data: '='
        },
        link: function (scope, element, attrs) {
            updatedTree();
        }
    };
}]);<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { injectIntl, FormattedMessage } from 'react-intl';
import classnames from 'classnames';
import hostedFields from 'braintree-web/hosted-fields';
import './Braintree.scss';
import ee from '../../shared/pub_sub';

class BraintreeCardFields extends Component {
  constructor(props) {
    super(props);
    this.state = {
      hostedFields: null,
      errors: {
        number: false,
        expirationDate: false,
        cvv: false,
      },
      styles: {
        input: {
          color: '#333',
          'font-size': '16px',
        },
        ':focus': { color: '#333' },
        '.valid': { color: '#333' },
      },
      fields: {
        number: Object.freeze({
          selector: '#braintree-card-number',
          placeholder: this.props.intl.formatMessage({
            id: 'fundraiser.fields.number',
            defaultMessage: 'Card number',
          }),
        }),
        cvv: Object.freeze({
          selector: '#braintree-cvv',
          placeholder: this.props.intl.formatMessage({
            id: 'fundraiser.fields.cvv',
            defaultMessage: 'CVV',
          }),
        }),
        expirationDate: Object.freeze({
          selector: '#braintree-expiration-date',
          placeholder: this.props.intl.formatMessage({
            id: 'fundraiser.fields.expiration_format',
            defaultMessage: 'MM/YY',
          }),
        }),
      },
    };
  }

  bindGlobalEvents() {
    ee.on('fundraiser:configure:hosted_fields', this.configureHostedFields);
  }

  configureHostedFields = (config = {}) => {
    this.setState(
      {
        styles: config.styles || this.state.styles,
        fields: config.fields || this.state.fields,
      },
      () => {
        console.log(this.state.fields);
        if (this.state.hostedFields) {
          this.teardown();
        } else {
          this.createHostedFields(this.props.client);
        }
      }
    );
  };

  componentDidMount() {
    if (this.props.client) {
      this.createHostedFields(this.props.client);
    }
    this.bindGlobalEvents();
  }

  componentWillReceiveProps(newProps) {
    if (newProps.client !== this.props.client && newProps.client) {
      this.createHostedFields(newProps.client);
    }
  }

  componentWillUnmount() {
    if (this.state.hostedFields) {
      this.state.hostedFields.teardown();
    }
  }

  createHostedFields(client) {
    const formatMessage = this.props.intl.formatMessage;
    hostedFields.create(
      {
        client,
        styles: this.state.styles,
        fields: this.state.fields,
      },
      (err, hostedFieldsInstance) => {
        if (err) {
          if (window.Sentry) {
            window.Sentry.captureException(err);
          }
          ee.emit('fundraiser:configure:hosted_fields:error', err);
          return;
        }
        ee.emit(
          'fundraiser:configure:hosted_fields:success',
          hostedFieldsInstance
        );

        this.setState({ hostedFields: hostedFieldsInstance }, () => {
          if (this.props.onInit) {
            this.props.onInit();
          }
        });

        hostedFieldsInstance.on('validityChange', event => {
          const field = event.fields[event.emittedBy];
          const newErrors = {};
          newErrors[event.emittedBy] = !field.isPotentiallyValid;
          this.setState({
            errors: Object.assign({}, this.state.errors, newErrors),
          });
        });

        hostedFieldsInstance.on('cardTypeChange', event => {
          if (event.cards.length === 1) {
            this.setState({ cardType: event.cards[0].type });
          } else {
            this.setState({ cardType: '' });
          }
        });
      }
    );
  }

  teardown() {
    if (!this.state.hostedFields) return null;
    this.state.hostedFields.teardown(() =>
      this.createHostedFields(this.props.client)
    );
  }

  submit(event) {
    if (event) event.preventDefault();
    this.resetErrors();

    return new Promise((resolve, reject) => {
      if (!this.state.hostedFields) return reject();

      this.state.hostedFields.tokenize(
        {
          vault: this.props.recurring,
        },
        (error, data) => {
          if (error) {
            this.processTokenizeErrors(error);
            return reject(error);
          }
          this.teardown();
          resolve(data);
        }
      );
    });
  }

  resetErrors() {
    const newErrors = {};
    Object.keys(this.state.errors).forEach(key => {
      newErrors[key] = false;
    });
    this.setState({ errors: newErrors });
  }

  processTokenizeErrors(error) {
    if (error.code === 'HOSTED_FIELDS_FIELDS_INVALID') {
      const errors = Object.assign({}, this.state.errors);
      for (const key of error.details.invalidFieldKeys) {
        errors[key] = true;
      }
      this.setState({ errors: errors });
    }
  }

  currentCardClass(cardType = 'hidden-irrelevant') {
    const icons = {
      'diners-club': 'fa-cc-diners-club',
      jcb: 'fa-cc-jcb',
      'american-express': 'fa-cc-amex',
      discover: 'fa-cc-discover',
      'master-card': 'fa-cc-mastercard',
      visa: 'fa-cc-visa',
      'hidden-irrelevant': 'hidden-irrelevant',
    };

    return icons[cardType];
  }

  render() {
    const prefix = 'BraintreeCardFields';
    const classNames = classnames({
      [prefix]: true,
      [`${prefix}--active`]: this.props.isActive,
    });
    return (

        <form
          id="braintree-hosted-fields-form"
          className="BraintreeCardFields__form"
          method="post"
          onSubmit={this.submit}
        >

            {' '}

          {this.state.errors.number && this.renderError('number')}

          {this.state.errors.cvv &&
            this.renderError('cvv', 'BraintreeCardFields__error-msg--col-left')}
          {this.state.errors.expirationDate &&
            this.renderError(
              'expiration',
              'BraintreeCardFields__error-msg--col-right'
            )}
        </form>

    );
  }

  renderError(field, className = '') {
    return (
      <div className={`BraintreeCardFields__error-msg error-msg ${className}`}>
        <FormattedMessage
          id={`fundraiser.field_names.${field}`}
          defaultMessage={`${field} (missing transl)`}
        />

        <FormattedMessage
          id="errors.probably_invalid"
          defaultMessage="doesn't look right (missing transl)"
        />

    );
  }
}

export default injectIntl(BraintreeCardFields, { withRef: true });
<commit_msg>:bug: Add a guard clause to configureHostedFields [bugfix]<commit_after>import React, { Component } from 'react';
import { injectIntl, FormattedMessage } from 'react-intl';
import classnames from 'classnames';
import hostedFields from 'braintree-web/hosted-fields';
import './Braintree.scss';
import ee from '../../shared/pub_sub';

class BraintreeCardFields extends Component {
  constructor(props) {
    super(props);
    this.state = {
      hostedFields: null,
      errors: {
        number: false,
        expirationDate: false,
        cvv: false,
      },
      styles: {
        input: {
          color: '#333',
          'font-size': '16px',
        },
        ':focus': { color: '#333' },
        '.valid': { color: '#333' },
      },
      fields: {
        number: Object.freeze({
          selector: '#braintree-card-number',
          placeholder: this.props.intl.formatMessage({
            id: 'fundraiser.fields.number',
            defaultMessage: 'Card number',
          }),
        }),
        cvv: Object.freeze({
          selector: '#braintree-cvv',
          placeholder: this.props.intl.formatMessage({
            id: 'fundraiser.fields.cvv',
            defaultMessage: 'CVV',
          }),
        }),
        expirationDate: Object.freeze({
          selector: '#braintree-expiration-date',
          placeholder: this.props.intl.formatMessage({
            id: 'fundraiser.fields.expiration_format',
            defaultMessage: 'MM/YY',
          }),
        }),
      },
    };
  }

  bindGlobalEvents() {
    ee.on('fundraiser:configure:hosted_fields', this.configureHostedFields);
  }

  configureHostedFields = (config = {}) => {
    this.setState(
      {
        styles: config.styles || this.state.styles,
        fields: config.fields || this.state.fields,
      },
      () => {
        console.log(this.state.fields);
        if (this.state.hostedFields) {
          return this.teardown();
        }

        if (this.props.client) {
          return this.createHostedFields(this.props.client);
        }
      }
    );
  };

  componentDidMount() {
    if (this.props.client) {
      this.createHostedFields(this.props.client);
    }
    this.bindGlobalEvents();
  }

  componentWillReceiveProps(newProps) {
    if (newProps.client !== this.props.client && newProps.client) {
      this.createHostedFields(newProps.client);
    }
  }

  componentWillUnmount() {
    if (this.state.hostedFields) {
      this.state.hostedFields.teardown();
    }
  }

  createHostedFields(client) {
    const formatMessage = this.props.intl.formatMessage;
    hostedFields.create(
      {
        client,
        styles: this.state.styles,
        fields: this.state.fields,
      },
      (err, hostedFieldsInstance) => {
        if (err) {
          if (window.Sentry) {
            window.Sentry.captureException(err);
          }
          ee.emit('fundraiser:configure:hosted_fields:error', err);
          return;
        }
        ee.emit(
          'fundraiser:configure:hosted_fields:success',
          hostedFieldsInstance
        );

        this.setState({ hostedFields: hostedFieldsInstance }, () => {
          if (this.props.onInit) {
            this.props.onInit();
          }
        });

        hostedFieldsInstance.on('validityChange', event => {
          const field = event.fields[event.emittedBy];
          const newErrors = {};
          newErrors[event.emittedBy] = !field.isPotentiallyValid;
          this.setState({
            errors: Object.assign({}, this.state.errors, newErrors),
          });
        });

        hostedFieldsInstance.on('cardTypeChange', event => {
          if (event.cards.length === 1) {
            this.setState({ cardType: event.cards[0].type });
          } else {
            this.setState({ cardType: '' });
          }
        });
      }
    );
  }

  teardown() {
    if (!this.state.hostedFields) return null;
    this.state.hostedFields.teardown(() =>
      this.createHostedFields(this.props.client)
    );
  }

  submit(event) {
    if (event) event.preventDefault();
    this.resetErrors();

    return new Promise((resolve, reject) => {
      if (!this.state.hostedFields) return reject();

      this.state.hostedFields.tokenize(
        {
          vault: this.props.recurring,
        },
        (error, data) => {
          if (error) {
            this.processTokenizeErrors(error);
            return reject(error);
          }
          this.teardown();
          resolve(data);
        }
      );
    });
  }

  resetErrors() {
    const newErrors = {};
    Object.keys(this.state.errors).forEach(key => {
      newErrors[key] = false;
    });
    this.setState({ errors: newErrors });
  }

  processTokenizeErrors(error) {
    if (error.code === 'HOSTED_FIELDS_FIELDS_INVALID') {
      const errors = Object.assign({}, this.state.errors);
      for (const key of error.details.invalidFieldKeys) {
        errors[key] = true;
      }
      this.setState({ errors: errors });
    }
  }

  currentCardClass(cardType = 'hidden-irrelevant') {
    const icons = {
      'diners-club': 'fa-cc-diners-club',
      jcb: 'fa-cc-jcb',
      'american-express': 'fa-cc-amex',
      discover: 'fa-cc-discover',
      'master-card': 'fa-cc-mastercard',
      visa: 'fa-cc-visa',
      'hidden-irrelevant': 'hidden-irrelevant',
    };

    return icons[cardType];
  }

  render() {
    const prefix = 'BraintreeCardFields';
    const classNames = classnames({
      [prefix]: true,
      [`${prefix}--active`]: this.props.isActive,
    });
    return (

        <form
          id="braintree-hosted-fields-form"
          className="BraintreeCardFields__form"
          method="post"
          onSubmit={this.submit}
        >

            {' '}

          {this.state.errors.number && this.renderError('number')}

          {this.state.errors.cvv &&
            this.renderError('cvv', 'BraintreeCardFields__error-msg--col-left')}
          {this.state.errors.expirationDate &&
            this.renderError(
              'expiration',
              'BraintreeCardFields__error-msg--col-right'
            )}
        </form>

    );
  }

  renderError(field, className = '') {
    return (
      <div className={`BraintreeCardFields__error-msg error-msg ${className}`}>
        <FormattedMessage
          id={`fundraiser.field_names.${field}`}
          defaultMessage={`${field} (missing transl)`}
        />

        <FormattedMessage
          id="errors.probably_invalid"
          defaultMessage="doesn't look right (missing transl)"
        />

    );
  }
}

export default injectIntl(BraintreeCardFields, { withRef: true });
<|endoftext|>


<commit_before>import { createSelector } from 'reselect';
import { biomassToCO2 } from 'utils/calculations';
import isEmpty from 'lodash/isEmpty';
import { format } from 'd3-format';

// get list data
const getLocation = state => state.payload || null;
const getLocationNames = state => state.locationNames || null;
const getData = state => state.data || null;
const getCountries = state => state.countries || null;
const getRegions = state => state.regions || null;
const getError = state => state.error || null;
const getSubRegions = state => state.subRegions || null;
const getSentences = state => state.config.sentences || null;

// get lists selected
export const getAdminsSelected = createSelector(
  [getCountries, getRegions, getSubRegions, getLocation],
  (countries, regions, subRegions, location) => {
    const country =
      (countries && countries.find(i => i.value === location.country)) || null;
    const region =
      (regions && regions.find(i => i.value === location.region)) || null;
    const subRegion =
      (subRegions && subRegions.find(i => i.value === location.subRegion)) ||
      null;
    let current = country;
    if (location.subRegion) {
      current = subRegion;
    } else if (location.region) {
      current = region;
    }

    return {
      ...current,
      country,
      region,
      subRegion
    };
  }
);

export const getSentence = createSelector(
  [getLocationNames, getData, getSentences, getError],
  (locationNames, data, sentences, error) => {
    if (error) {
      return 'An error occured while fetching data. Please try again later.';
    }
    if (isEmpty(data) || isEmpty(locationNames)) return {};
    const { initial, withLoss, withPlantationLoss } = sentences;
    const extent =
      data.extent < 1 ? format('.3r')(data.extent) : format('.3s')(data.extent);
    const percentageCover = format('.1f')(data.extent / data.totalArea * 100);
    const lossWithOutPlantations = format('.2s')(
      data.totalLoss.area - (data.plantationsLoss.area || 0)
    );
    const emissionsWithoutPlantations = format('.2s')(
      biomassToCO2(
        data.totalLoss.emissions - (data.plantationsLoss.emissions || 0)
      )
    );
    const location = locationNames && locationNames.label;

    const params = {
      extent: `${extent}ha`,
      location: location || 'the world',
      percentage: `${percentageCover}%`,
      loss: `${lossWithOutPlantations}ha`,
      emission: `${emissionsWithoutPlantations}t`,
      year: data.totalLoss.year
    };

    let sentence = initial;
    if (data.extent > 0 && data.totalLoss.area && data.plantationsLoss.area) {
      sentence =
        data.plantationsLoss.area && location ? withPlantationLoss : withLoss;
    }

    return {
      sentence,
      params
    };
  }
);
<commit_msg>Makes units consistant<commit_after>import { createSelector } from 'reselect';
import { biomassToCO2 } from 'utils/calculations';
import isEmpty from 'lodash/isEmpty';
import { format } from 'd3-format';

// get list data
const getLocation = state => state.payload || null;
const getLocationNames = state => state.locationNames || null;
const getData = state => state.data || null;
const getCountries = state => state.countries || null;
const getRegions = state => state.regions || null;
const getError = state => state.error || null;
const getSubRegions = state => state.subRegions || null;
const getSentences = state => state.config.sentences || null;

// get lists selected
export const getAdminsSelected = createSelector(
  [getCountries, getRegions, getSubRegions, getLocation],
  (countries, regions, subRegions, location) => {
    const country =
      (countries && countries.find(i => i.value === location.country)) || null;
    const region =
      (regions && regions.find(i => i.value === location.region)) || null;
    const subRegion =
      (subRegions && subRegions.find(i => i.value === location.subRegion)) ||
      null;
    let current = country;
    if (location.subRegion) {
      current = subRegion;
    } else if (location.region) {
      current = region;
    }

    return {
      ...current,
      country,
      region,
      subRegion
    };
  }
);

export const getSentence = createSelector(
  [getLocationNames, getData, getSentences, getError],
  (locationNames, data, sentences, error) => {
    if (error) {
      return 'An error occured while fetching data. Please try again later.';
    }
    if (isEmpty(data) || isEmpty(locationNames)) return {};
    const { initial, withLoss, withPlantationLoss } = sentences;
    const extent =
      data.extent < 1 ? format('.3r')(data.extent) : format('.3s')(data.extent);
    const percentageCover = format('.1f')(data.extent / data.totalArea * 100);
    const lossWithOutPlantations = format('.2s')(
      data.totalLoss.area - (data.plantationsLoss.area || 0)
    );
    const emissionsWithoutPlantations = format('.3s')(
      biomassToCO2(
        data.totalLoss.emissions - (data.plantationsLoss.emissions || 0)
      )
    );
    const location = locationNames && locationNames.label;

    const params = {
      extent: `${extent}ha`,
      location: location || 'the world',
      percentage: `${percentageCover}%`,
      loss: `${lossWithOutPlantations}ha`,
      emission: `${emissionsWithoutPlantations}t`,
      year: data.totalLoss.year
    };

    let sentence = initial;
    if (data.extent > 0 && data.totalLoss.area && data.plantationsLoss.area) {
      sentence =
        data.plantationsLoss.area && location ? withPlantationLoss : withLoss;
    }

    return {
      sentence,
      params
    };
  }
);
<|endoftext|>


<commit_before>/**
 * Author: Milica Kadic
 * Date: 12/18/14
 * Time: 12:30 PM
 */

'use strict';

angular.module('registryApp.cliche')
    .controller('ManagePropertyOutputCtrl', ['$scope', '$modalInstance', 'Cliche', 'options', 'lodash', 'HelpMessages', function ($scope, $modalInstance, Cliche, options, _, HelpMessages) {

        var key = options.key || 'name';
        var idObj = {n: '', o: ''};

        $scope.help = HelpMessages;

        $scope.view = {};
		$scope.view.uniqueId = _.uniqueId();
        $scope.view.key = key;
        $scope.view.mode = options.mode;
        $scope.view.property = options.property || {};
        $scope.view.property.type = Cliche.getSchema('output', options.property, options.toolType, false);

        // only add adapter if one has been defined
        if (options.property && options.property.outputBinding) {
            $scope.view.property.outputBinding = Cliche.getAdapter(options.property, false, 'output');
        }

        $scope.view.name = Cliche.parseName(options.property);
        $scope.view.required = Cliche.isRequired($scope.view.property.type);
        $scope.view.type = Cliche.parseType($scope.view.property.type);
        $scope.view.items = Cliche.getItemsRef($scope.view.type, $scope.view.property.type);
        $scope.view.itemsType = Cliche.getItemsType($scope.view.items);

        $scope.view.types = Cliche.getTypes('output');
        $scope.view.itemTypes = Cliche.getTypes('outputItem');

        idObj.o = $scope.view.name;

        // shows expression style input rather than regular input
        // for secondary files
        $scope.view.isSecondaryFilesExpr = false;

        // create list of input ids to inherit metadata from
        $scope.view.inputs = _.pluck(_.filter(Cliche.getTool().inputs, function(input) {
            var type = Cliche.parseType(input.type),
                typeObj = Cliche.parseTypeObj(input.type);
            return type === 'File' || (typeObj.items && typeObj.items === 'File');
        }), 'id');

        /**
         * Toggle secondary files into array (not currently using)
         */
        //$scope.toggleToList = function() {
        //    $scope.view.property.outputBinding.secondaryFiles = [];
        //    $scope.view.property.outputBinding.secondaryFiles.push('');
        //    $scope.view.isSecondaryFilesExpr = false;
        //};

        $scope.view.newMeta = {key: '', value: ''};

        /**
         * Add meta data to the output
         */
        $scope.addMeta = function () {

            $scope.view.newMeta.error = false;
            $scope.view.property.outputBinding = $scope.view.property.outputBinding || {};

            if (!$scope.view.property.outputBinding.metadata) {
                $scope.view.property.outputBinding.metadata = {};
            }

            if (!_.isUndefined($scope.view.property.outputBinding.metadata[$scope.view.newMeta.key]) || $scope.view.newMeta.key === '') {
                $scope.view.newMeta.error = true;
                return false;
            }

            $scope.view.property.outputBinding.metadata[$scope.view.newMeta.key] = $scope.view.newMeta.value;
            $scope.view.newMeta = {key: '', value: ''};

        };

        /**
         * Remove meta data from the output
         *
         * @param {integer} index
         * @returns {boolean}
         */
        $scope.removeMeta = function (index) {
            delete $scope.view.property.outputBinding.metadata[index];
        };

        /**
         * Update new meta value with expression or literal
         *
         * @param value
         */

        $scope.updateNewMeta = function (value) {
            $scope.view.newMeta.value = value;
        };

        /**
         * Update existing meta value with expression or literal
         *
         * @param index
         * @param value
         */
        $scope.updateMetaValue = function (index, value) {
            $scope.view.property.outputBinding.metadata[index] = value;
        };

        /**
         * Save property changes
         *
         * @returns {boolean}
         */
        $scope.save = function() {

            $scope.view.error = '';
            $scope.view.form.$setDirty();

            if ($scope.view.form.$invalid) { return false; }

            var inner = {
                key: key,
                name: $scope.view.name,
                required: $scope.view.required,
                type: $scope.view.type,
                items: $scope.view.items
            };

            $scope.view.property.type = $scope.view.property.schema;
            delete $scope.view.property.schema;

            var formatted = Cliche.formatProperty(inner, $scope.view.property, 'output');

            idObj.n = $scope.view.name;

            Cliche.manageProperty(options.mode, formatted, options.properties, idObj)
                .then(function() {
                    $modalInstance.close({prop: formatted});
                }, function(error) {
                    $scope.view.error = error;
                });

        };

        /* watch for the type change in order to adjust the property structure */
        $scope.$watch('view.type', function(n, o) {
            if (n !== o) {
                if (n === 'array') {
                    $scope.view.itemsType = 'File';
                    $scope.view.items = $scope.view.itemsType;
                } else {
                    delete $scope.view.items;
                }
            }
        });

        /**
         * Update existing glob value with expression or literal
         *
         * @param value
         */
        $scope.updateGlobValue = function (value) {

            if (_.isUndefined($scope.view.property.outputBinding)) {
                $scope.view.property.outputBinding = {};
            }
            $scope.view.property.outputBinding.glob = value;
        };

        /**
         * Dismiss modal
         */
        $scope.cancel = function () {
            $modalInstance.dismiss('cancel');
        };

    }]);
<commit_msg>Added check to remove output.outputBinding.sbg:inheritMetadataFrom if null and output.outputBinding if empty.<commit_after>/**
 * Author: Milica Kadic
 * Date: 12/18/14
 * Time: 12:30 PM
 */

'use strict';

angular.module('registryApp.cliche')
    .controller('ManagePropertyOutputCtrl', ['$scope', '$modalInstance', 'Cliche', 'options', 'lodash', 'HelpMessages', function ($scope, $modalInstance, Cliche, options, _, HelpMessages) {

        var key = options.key || 'name';
        var idObj = {n: '', o: ''};

        $scope.help = HelpMessages;

        $scope.view = {};
		$scope.view.uniqueId = _.uniqueId();
        $scope.view.key = key;
        $scope.view.mode = options.mode;
        $scope.view.property = options.property || {};
        $scope.view.property.type = Cliche.getSchema('output', options.property, options.toolType, false);

        // only add adapter if one has been defined
        if (options.property && options.property.outputBinding) {
            $scope.view.property.outputBinding = Cliche.getAdapter(options.property, false, 'output');
        }

        $scope.view.name = Cliche.parseName(options.property);
        $scope.view.required = Cliche.isRequired($scope.view.property.type);
        $scope.view.type = Cliche.parseType($scope.view.property.type);
        $scope.view.items = Cliche.getItemsRef($scope.view.type, $scope.view.property.type);
        $scope.view.itemsType = Cliche.getItemsType($scope.view.items);

        $scope.view.types = Cliche.getTypes('output');
        $scope.view.itemTypes = Cliche.getTypes('outputItem');

        idObj.o = $scope.view.name;

        // shows expression style input rather than regular input
        // for secondary files
        $scope.view.isSecondaryFilesExpr = false;

        // create list of input ids to inherit metadata from
        $scope.view.inputs = _.pluck(_.filter(Cliche.getTool().inputs, function(input) {
            var type = Cliche.parseType(input.type),
                typeObj = Cliche.parseTypeObj(input.type);
            return type === 'File' || (typeObj.items && typeObj.items === 'File');
        }), 'id');

		if (!_.isEmpty($scope.view.inputs)) {
			$scope.$watch('view.property.outputBinding["sbg:inheritMetadataFrom"]', function(n) {
				if (_.isNull(n)) {
					delete  $scope.view.property.outputBinding['sbg:inheritMetadataFrom'];
					if (_.isEmpty($scope.view.property.outputBinding)) {
						delete $scope.view.property.outputBinding;
					}
				}
			})
		}

        /**
         * Toggle secondary files into array (not currently using)
         */
        //$scope.toggleToList = function() {
        //    $scope.view.property.outputBinding.secondaryFiles = [];
        //    $scope.view.property.outputBinding.secondaryFiles.push('');
        //    $scope.view.isSecondaryFilesExpr = false;
        //};

        $scope.view.newMeta = {key: '', value: ''};

        /**
         * Add meta data to the output
         */
        $scope.addMeta = function () {

            $scope.view.newMeta.error = false;
            $scope.view.property.outputBinding = $scope.view.property.outputBinding || {};

            if (!$scope.view.property.outputBinding.metadata) {
                $scope.view.property.outputBinding.metadata = {};
            }

            if (!_.isUndefined($scope.view.property.outputBinding.metadata[$scope.view.newMeta.key]) || $scope.view.newMeta.key === '') {
                $scope.view.newMeta.error = true;
                return false;
            }

            $scope.view.property.outputBinding.metadata[$scope.view.newMeta.key] = $scope.view.newMeta.value;
            $scope.view.newMeta = {key: '', value: ''};

        };

        /**
         * Remove meta data from the output
         *
         * @param {integer} index
         * @returns {boolean}
         */
        $scope.removeMeta = function (index) {
            delete $scope.view.property.outputBinding.metadata[index];
        };

        /**
         * Update new meta value with expression or literal
         *
         * @param value
         */

        $scope.updateNewMeta = function (value) {
            $scope.view.newMeta.value = value;
        };

        /**
         * Update existing meta value with expression or literal
         *
         * @param index
         * @param value
         */
        $scope.updateMetaValue = function (index, value) {
            $scope.view.property.outputBinding.metadata[index] = value;
        };

        /**
         * Save property changes
         *
         * @returns {boolean}
         */
        $scope.save = function() {

            $scope.view.error = '';
            $scope.view.form.$setDirty();

            if ($scope.view.form.$invalid) { return false; }

            var inner = {
                key: key,
                name: $scope.view.name,
                required: $scope.view.required,
                type: $scope.view.type,
                items: $scope.view.items
            };

            $scope.view.property.type = $scope.view.property.schema;
            delete $scope.view.property.schema;

            var formatted = Cliche.formatProperty(inner, $scope.view.property, 'output');

            idObj.n = $scope.view.name;

            Cliche.manageProperty(options.mode, formatted, options.properties, idObj)
                .then(function() {
                    $modalInstance.close({prop: formatted});
                }, function(error) {
                    $scope.view.error = error;
                });

        };

        /* watch for the type change in order to adjust the property structure */
        $scope.$watch('view.type', function(n, o) {
            if (n !== o) {
                if (n === 'array') {
                    $scope.view.itemsType = 'File';
                    $scope.view.items = $scope.view.itemsType;
                } else {
                    delete $scope.view.items;
                }
            }
        });

        /**
         * Update existing glob value with expression or literal
         *
         * @param value
         */
        $scope.updateGlobValue = function (value) {

            if (_.isUndefined($scope.view.property.outputBinding)) {
                $scope.view.property.outputBinding = {};
            }
            $scope.view.property.outputBinding.glob = value;
        };

        /**
         * Dismiss modal
         */
        $scope.cancel = function () {
            $modalInstance.dismiss('cancel');
        };

    }]);
<|endoftext|>


<commit_before>import React from 'react'

import Heading from 'lib/modules/opportunity-datasets/components/heading'
import Upload from 'lib/modules/opportunity-datasets/components/upload'

export default function OpportunitiesUpload(p) {
  return (
    <Heading>
      <Upload {...p} regionId={p.query.regionId} />
    </Heading>
  )
}
<commit_msg>Ensure auth is provided to the page (#1045)<commit_after>import React from 'react'

import getInitialAuth from 'lib/get-initial-auth'
import Heading from 'lib/modules/opportunity-datasets/components/heading'
import Upload from 'lib/modules/opportunity-datasets/components/upload'

export default function OpportunitiesUpload(p) {
  return (
    <Heading>
      <Upload {...p} regionId={p.query.regionId} />
    </Heading>
  )
}

OpportunitiesUpload.getInitialProps = getInitialAuth
<|endoftext|>


<commit_before>// Copyright 2013-2017, University of Colorado Boulder

//TODO see sun#29, reimplement as a subtype of something from sun.buttons
/**
 * Button for expanding/collapsing something.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ButtonListener = require( 'SCENERY/input/ButtonListener' );
  var inherit = require( 'PHET_CORE/inherit' );
  var InstanceRegistry = require( 'PHET_CORE/documentation/InstanceRegistry' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Shape = require( 'KITE/Shape' );
  var sun = require( 'SUN/sun' );
  var Tandem = require( 'TANDEM/Tandem' );

  /**
   * @param {Property.<boolean>} expandedProperty
   * @param {Object} [options]
   * @constructor
   */
  function ExpandCollapseButton( expandedProperty, options ) {

    options = _.extend( {
      sideLength: 25,  // length of one side of the square button
      tandem: Tandem.required
    }, options );

    Node.call( this );

    // configure the button shape
    var cornerRadius = 0.1 * options.sideLength;
    var buttonShape = Shape.roundRectangle( 0, 0, options.sideLength, options.sideLength, cornerRadius, cornerRadius );

    // configure the +/- symbol on the button
    var symbolLength = 0.6 * options.sideLength;
    var symbolLineWidth = 0.15 * options.sideLength;
    var symbolOptions = {
      lineWidth: symbolLineWidth,
      stroke: 'white',
      centerX: options.sideLength / 2,
      centerY: options.sideLength / 2,
      pickable: false
    };

    // Expand '+' button
    var expandButton = new Path( buttonShape, {
      fill: 'rgb(0, 179, 0 )',
      stroke: 'black',
      lineWidth: 0.5,
      tandem: options.tandem.createTandem( 'expandButton' )
    } );
    var plusSymbolShape = new Shape()
      .moveTo( symbolLength / 2, 0 )
      .lineTo( symbolLength / 2, symbolLength )
      .moveTo( 0, symbolLength / 2 )
      .lineTo( symbolLength, symbolLength / 2 );
    expandButton.addChild( new Path(
      plusSymbolShape,
      symbolOptions
    ) );

    // Collapse '-' button
    var collapseButton = new Path( buttonShape, {
      fill: 'rgb( 255, 85, 0 )',
      stroke: 'black',
      lineWidth: 0.5,
      tandem: options.tandem.createTandem( 'collapseButton' )
    } );
    var minusSymbolShape = new Shape()
      .moveTo( -symbolLength / 2, 0 )
      .lineTo( symbolLength / 2, 0 );
    collapseButton.addChild( new Path(
      minusSymbolShape,
      symbolOptions
    ) );

    // rendering order
    this.addChild( expandButton );
    this.addChild( collapseButton );

    // click to toggle
    this.cursor = 'pointer';
    this.addInputListener( new ButtonListener( {
      fire: function() {
        expandedProperty.set( !expandedProperty.get() );
      },
      tandem: options.tandem.createTandem( 'buttonListener' )
    } ) );

    // @private
    this.expandedPropertyObserver = function( expanded ) {
      expandButton.visible = !expanded;
      collapseButton.visible = expanded;
    };
    this.expandedProperty = expandedProperty; // @private
    this.expandedProperty.link( this.expandedPropertyObserver ); // must be unlinked in dispose

    this.mutate( options );

    // a11y - support for binder documentation, stripped out in builds and only runs when ?binder is specified
    assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'sun', 'ExpandCollapseButton', this );

    // @private
    this.disposeExpandCollapseButton = function() {
      this.expandedProperty.unlink( this.expandedPropertyObserver );
    };
  }

  sun.register( 'ExpandCollapseButton', ExpandCollapseButton );

  return inherit( Node, ExpandCollapseButton, {

    // @public - Ensures that this node is eligible for GC.
    dispose: function() {
      this.disposeExpandCollapseButton();
      Node.prototype.dispose.call( this );
    }
  } );
} );<commit_msg>Uninstrument ExpandCollapseButton child nodes, see https://github.com/phetsims/graphing-quadratics/issues/14<commit_after>// Copyright 2013-2017, University of Colorado Boulder

//TODO see sun#29, reimplement as a subtype of something from sun.buttons
/**
 * Button for expanding/collapsing something.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ButtonListener = require( 'SCENERY/input/ButtonListener' );
  var inherit = require( 'PHET_CORE/inherit' );
  var InstanceRegistry = require( 'PHET_CORE/documentation/InstanceRegistry' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Shape = require( 'KITE/Shape' );
  var sun = require( 'SUN/sun' );
  var Tandem = require( 'TANDEM/Tandem' );

  /**
   * @param {Property.<boolean>} expandedProperty
   * @param {Object} [options]
   * @constructor
   */
  function ExpandCollapseButton( expandedProperty, options ) {

    options = _.extend( {
      sideLength: 25,  // length of one side of the square button
      tandem: Tandem.required
    }, options );

    Node.call( this );

    // configure the button shape
    var cornerRadius = 0.1 * options.sideLength;
    var buttonShape = Shape.roundRectangle( 0, 0, options.sideLength, options.sideLength, cornerRadius, cornerRadius );

    // configure the +/- symbol on the button
    var symbolLength = 0.6 * options.sideLength;
    var symbolLineWidth = 0.15 * options.sideLength;
    var symbolOptions = {
      lineWidth: symbolLineWidth,
      stroke: 'white',
      centerX: options.sideLength / 2,
      centerY: options.sideLength / 2,
      pickable: false
    };

    // Expand '+' button
    var expandButton = new Path( buttonShape, {
      fill: 'rgb(0, 179, 0 )',
      stroke: 'black',
      lineWidth: 0.5
    } );
    var plusSymbolShape = new Shape()
      .moveTo( symbolLength / 2, 0 )
      .lineTo( symbolLength / 2, symbolLength )
      .moveTo( 0, symbolLength / 2 )
      .lineTo( symbolLength, symbolLength / 2 );
    expandButton.addChild( new Path(
      plusSymbolShape,
      symbolOptions
    ) );

    // Collapse '-' button
    var collapseButton = new Path( buttonShape, {
      fill: 'rgb( 255, 85, 0 )',
      stroke: 'black',
      lineWidth: 0.5
    } );
    var minusSymbolShape = new Shape()
      .moveTo( -symbolLength / 2, 0 )
      .lineTo( symbolLength / 2, 0 );
    collapseButton.addChild( new Path(
      minusSymbolShape,
      symbolOptions
    ) );

    // rendering order
    this.addChild( expandButton );
    this.addChild( collapseButton );

    // click to toggle
    this.cursor = 'pointer';
    this.addInputListener( new ButtonListener( {
      fire: function() {
        expandedProperty.set( !expandedProperty.get() );
      },
      tandem: options.tandem.createTandem( 'buttonListener' )
    } ) );

    // @private
    this.expandedPropertyObserver = function( expanded ) {
      expandButton.visible = !expanded;
      collapseButton.visible = expanded;
    };
    this.expandedProperty = expandedProperty; // @private
    this.expandedProperty.link( this.expandedPropertyObserver ); // must be unlinked in dispose

    this.mutate( options );

    // a11y - support for binder documentation, stripped out in builds and only runs when ?binder is specified
    assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'sun', 'ExpandCollapseButton', this );

    // @private
    this.disposeExpandCollapseButton = function() {
      this.expandedProperty.unlink( this.expandedPropertyObserver );
    };
  }

  sun.register( 'ExpandCollapseButton', ExpandCollapseButton );

  return inherit( Node, ExpandCollapseButton, {

    // @public - Ensures that this node is eligible for GC.
    dispose: function() {
      this.disposeExpandCollapseButton();
      Node.prototype.dispose.call( this );
    }
  } );
} );<|endoftext|>


<commit_before>import { remote } from 'electron';
import React, { Component, PropTypes } from 'react';

import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';

import PlatformSpecific from './PlatformSpecific';
import generateTheme from '../../utils/theme';

export default class WindowContainer extends Component {
  static propTypes = {
    children: PropTypes.oneOfType([
      PropTypes.array,
      PropTypes.object,
    ]).isRequired,
    confirmClose: PropTypes.func,
    isMainWindow: PropTypes.bool,
    title: PropTypes.string.isRequired,
  };

  constructor(...args) {
    super(...args);

    this.state = {
      nativeFrame: Settings.get('nativeFrame'),
      theme: Settings.get('theme'),
      themeColor: Settings.get('themeColor'),
      themeType: Settings.get('themeType', 'FULL'),
    };
  }

  componentDidMount() {
    Emitter.on('settings:change:theme', this._themeUpdate);
    Emitter.on('settings:change:themeColor', this._themeColorUpdate);
    Emitter.on('settings:change:themeType', this._themeTypeUpdate);
  }

  componentWillUnmount() {
    Emitter.off('settings:change:theme', this._themeUpdate);
    Emitter.off('settings:change:themeColor', this._themeColorUpdate);
    Emitter.off('settings:change:themeType', this._themeTypeUpdate);
  }

  _darwinExpand = () => {
    const doubleClickAction = remote.systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string');
    if (doubleClickAction === 'Minimize') {
      this.minWindow();
    } else if (doubleClickAction === 'Maximize') {
      this.maxWindow();
    }
  }

  _themeUpdate = (event, theme) => {
    this.setState({ theme });
  }

  _themeColorUpdate = (event, themeColor) => {
    this.setState({ themeColor });
  }

  _themeTypeUpdate = (event, themeType) => {
    this.setState({ themeType });
  }

  minWindow = () => {
    Emitter.fire('window:minimize', remote.getCurrentWindow().id);
  }

  maxWindow = () => {
    Emitter.fire('window:maximize', remote.getCurrentWindow().id);
  }

  closeWindow = () => {
    if (this.props.isMainWindow && this.props.confirmClose && Settings.get('warnMinToTray', true) && Settings.get('minToTray', true)) {
      this.props.confirmClose();
    } else {
      Emitter.fire('window:close', remote.getCurrentWindow().id);
    }
  }

  render() {
    const muiTheme = generateTheme(this.state.theme, this.state.themeColor, this.state.themeType);

    const fadedBackground = {};
    if (this.state.theme && this.state.themeType === 'FULL') {
      fadedBackground.backgroundColor = '#121212';
      fadedBackground.color = '#FAFAFA';
    }

    return (
      <MuiThemeProvider muiTheme={muiTheme}>

          <PlatformSpecific platform="darwin">

{this.props.title}

          </PlatformSpecific>

              {
                ['min', 'max', 'close'].map((action) => (
                  <div key={action} className="control" onClick={this[`${action}Window`]}>
                    <img src={`../assets/img/control_bar/${action}.png`} alt={action} />

                ))
              }

          {
            this.props.isMainWindow ?
            (

                {
                  this.props.children
                }

            ) :
            (

                {
                  process.platform === 'darwin' && !this.state.nativeFrame ?
                  (

                      {this.props.title}

                  ) : null
                }

                  {
                    this.props.children
                  }

            )
          }

      </MuiThemeProvider>
    );
  }
}
<commit_msg>Show title on windows<commit_after>import { remote } from 'electron';
import React, { Component, PropTypes } from 'react';

import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';

import PlatformSpecific from './PlatformSpecific';
import generateTheme from '../../utils/theme';

export default class WindowContainer extends Component {
  static propTypes = {
    children: PropTypes.oneOfType([
      PropTypes.array,
      PropTypes.object,
    ]).isRequired,
    confirmClose: PropTypes.func,
    isMainWindow: PropTypes.bool,
    title: PropTypes.string.isRequired,
  };

  constructor(...args) {
    super(...args);

    this.state = {
      nativeFrame: Settings.get('nativeFrame'),
      theme: Settings.get('theme'),
      themeColor: Settings.get('themeColor'),
      themeType: Settings.get('themeType', 'FULL'),
    };
  }

  componentDidMount() {
    Emitter.on('settings:change:theme', this._themeUpdate);
    Emitter.on('settings:change:themeColor', this._themeColorUpdate);
    Emitter.on('settings:change:themeType', this._themeTypeUpdate);
  }

  componentWillUnmount() {
    Emitter.off('settings:change:theme', this._themeUpdate);
    Emitter.off('settings:change:themeColor', this._themeColorUpdate);
    Emitter.off('settings:change:themeType', this._themeTypeUpdate);
  }

  _darwinExpand = () => {
    const doubleClickAction = remote.systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string');
    if (doubleClickAction === 'Minimize') {
      this.minWindow();
    } else if (doubleClickAction === 'Maximize') {
      this.maxWindow();
    }
  }

  _themeUpdate = (event, theme) => {
    this.setState({ theme });
  }

  _themeColorUpdate = (event, themeColor) => {
    this.setState({ themeColor });
  }

  _themeTypeUpdate = (event, themeType) => {
    this.setState({ themeType });
  }

  minWindow = () => {
    Emitter.fire('window:minimize', remote.getCurrentWindow().id);
  }

  maxWindow = () => {
    Emitter.fire('window:maximize', remote.getCurrentWindow().id);
  }

  closeWindow = () => {
    if (this.props.isMainWindow && this.props.confirmClose && Settings.get('warnMinToTray', true) && Settings.get('minToTray', true)) {
      this.props.confirmClose();
    } else {
      Emitter.fire('window:close', remote.getCurrentWindow().id);
    }
  }

  render() {
    const muiTheme = generateTheme(this.state.theme, this.state.themeColor, this.state.themeType);

    const fadedBackground = {};
    if (this.state.theme && this.state.themeType === 'FULL') {
      fadedBackground.backgroundColor = '#121212';
      fadedBackground.color = '#FAFAFA';
    }

    return (
      <MuiThemeProvider muiTheme={muiTheme}>

          <PlatformSpecific platform="darwin">

{this.props.title}

          </PlatformSpecific>

              {
                ['min', 'max', 'close'].map((action) => (
                  <div key={action} className="control" onClick={this[`${action}Window`]}>
                    <img src={`../assets/img/control_bar/${action}.png`} alt={action} />

                ))
              }

          {
            this.props.isMainWindow ?
            (

                {
                  this.props.children
                }

            ) :
            (

                {
                  !(process.platform === 'darwin' && this.state.nativeFrame) ?
                  (

                      {this.props.title}

                  ) : null
                }

                  {
                    this.props.children
                  }

            )
          }

      </MuiThemeProvider>
    );
  }
}
<|endoftext|>