<commit_before>'use strict';

/* Controllers */

(function(){

    var errorMessages = {
        integerRequired: 'A whole number is required.',
        numberTooSmall: 'The number must be bigger than zero.',
        sampleCountTooLarge: 'The sample count must be smaller than the total count.',
        seedRequired: 'A random seed is required.',
        positiveIntegerRequired: 'A whole number bigger than zero is required.'
    };

    var samplerControllers = angular.module('samplerApp.controllers.form', [
        'samplerApp.services'
    ]);

    // Return an error object.
    function makeError(message, related) {
        return {
            message: message,
            related: related
        };
    }

    // TODO: move this to services.js?
    function chooseSamples(getSamplesUnique, parsed) {
        var hashes = [],
            sampleCount = parsed.sampleCount,
            seed = parsed.seed,
            smallestItem = parsed.smallestItem,
            totalCount = parsed.totalCount;

        var result = getSamplesUnique(seed, totalCount, sampleCount, smallestItem, hashes);

        var uniqueItems = result[0];
        var sortedItems = uniqueItems.concat();  // make a copy.
        sortedItems.sort(function(a, b) {
            return a - b;
        });

        return {
            all: result[1],
            hashes: hashes,
            sorted: sortedItems,
            unique: uniqueItems
        };
    }

    /**
     * @ngdoc function
     * @name samplerApp.controller:MainCtrl
     * @description
     * # MainCtrl
     * Controller of the samplerApp
     *
     * We do not perform form validation on ng-keyup, ng-change, or even
     * ng-blur.  This is because we take the philosophy that the user
     * should be able to continue working on the form undisturbed until
     * pressing the submit button, which sends the message that the user
     * is finished.
     *     However, we do clear error messages on ng-change because that
     * signifies that the user has started to correct the error.  We also
     * clear errors on related input elements whose error message could
     * potentially change as a result (e.g. the error message saying that
     * the sample count is larger than the total number of items).
     */
    samplerControllers.controller('MainCtrl', ['$log', '$scope', '$window',
      'getSamplesUnique', 'leftPadder', 'spellsInt',
      function ($log, $scope, $window, getSamplesUnique, leftPadder, spellsInt) {

        var form,
            input,
            output,
            parsed,
            parseFunctions;

        // The parse functions each return an object containing either
        // an error object or the parsed integer value.

        function parseSeed(inputValue) {
            var result = {};
            if (!inputValue) {
                result.error = makeError(errorMessages.seedRequired);
            } else {
                result.value = inputValue;
            }
            return result;
        }

        function parseInteger(inputValue) {
            var result = {};
            var n = spellsInt(inputValue);
            if (isNaN(n)) {
                result.error = makeError(errorMessages.integerRequired);
            } else {
                result.value = n;
            }
            return result;
        }

        function parsePositiveInteger(inputValue) {
            var result = parseInteger(inputValue);
            if (result.error !== undefined) {
                result.error = makeError(errorMessages.positiveIntegerRequired);
            } else if (result.value < 1) {
                result.error = makeError(errorMessages.numberTooSmall);
            }
            return result;
        }

        function parseSampleCount(inputValue) {
            var result = parsePositiveInteger(inputValue);
            if ((result.error === undefined) &&
                (parsed.totalCount !== undefined) &&
                (result.value > parsed.totalCount)) {
                result.error = makeError(errorMessages.sampleCountTooLarge, ['totalCount']);
            }
            return result;
        }

        function setOutput(all, unique, sorted, debugInfo) {
            output.allItems = all;
            output.uniqueItems = unique;
            output.sortedItems = sorted;
            output.debug = debugInfo;
        }

        // Actions to take when an input element changes:
        //
        // 1) clear the result display,
        // 2) clear any associated errors,
        // 3) set the parsed value if no error, and
        // 4) return any error (or else null).
        //
        // Returns undefined or an error object.
        function onInput(inputLabel) {
            var errors = form.errors,
                parsed = form.parsed,
                parseInput = parseFunctions[inputLabel],
                relatedErrors = form.relatedErrors;

            form.showing = false;
            // Clear the result display since the input has changed.
            setOutput();
            // Clear the main error and any related errors.
            // (It's okay to delete if the property does not exist.)
            delete errors[inputLabel];
            var related = relatedErrors[inputLabel];
            if (related !== undefined) {
                for (var i = 0, len = related.length; i < len; i++) {
                    delete errors[related[i]];
                }
                // We only need to clear related errors once.
                delete relatedErrors[inputLabel];
            }

            var result = parseInput(form.input[inputLabel]);
            $log.debug('onInput: ' + result.value);
            // The value will be undefined if parsing yielded an error.
            parsed[inputLabel] = result.value;

            return result.error;
        }

        // Return whether the form is error-free up to this point.
        // Also sets errors if there is a validation error.
        function checkInput(isOkay, inputLabel) {
            var relatedErrors = form.relatedErrors;
            var error = onInput(inputLabel);

            if (error !== undefined) {
                var errorText = error.message;
                var related = error.related;

                form.errors[inputLabel] = errorText;

                if (related !== undefined) {
                    for (var i = 0, len = related.length; i < len; i++) {
                        var relatedLabel = related[i];
                        if (relatedErrors[relatedLabel] === undefined) {
                            relatedErrors[relatedLabel] = [];
                        }
                        relatedErrors[relatedLabel].push(inputLabel);
                    }
                }
                isOkay = false;
            }

            return isOkay;
        }

        // Notes re: form validation & input values
        //
        // Note that with input type "number", the input value may already be
        // converted to a number and not be a string.
        //
        // For input type "number", an empty input element can result
        // in an input value of null.
        //
        // Moreover, if the user supplied "-1" and the input element has
        // min="1", then input[key] will be undefined (and spellsInt()
        // will return NaN) for at least some browsers.  Thus, we include
        // the full helpful error message if the parsed integer is NaN.

        // Populate the given object with parsed values, and return
        // whether the form validated without error.
        function validateForm() {
            var isOkay = true;

            isOkay = checkInput(isOkay, 'seed');
            isOkay = checkInput(isOkay, 'smallestItem');
            isOkay = checkInput(isOkay, 'totalCount');

            // We validate sampleCount after totalCount because validating
            // sampleCount depends on totalCount.
            isOkay = checkInput(isOkay, 'sampleCount');

            return isOkay;
        }

        function makeDebugInfo(parsed, result) {
            var allItems = result.all,
                hashes = result.hashes,
                item,
                line,
                lines,
                padIndex,
                padSample,
                parsedSeed = parsed.seed,
                parsedSmallestItem = parsed.smallestItem,
                parsedTotalCount = parsed.totalCount,
                sampleCount;

            sampleCount = allItems.length;

            padIndex = leftPadder(sampleCount.toString().length, ' ');
            padSample = leftPadder((parsedTotalCount - 1).toString().length, ' ');

            lines = [
                'Seed: "' + parsedSeed + '"',
                '      "' + encodeURI(parsedSeed) + '" (url-encoded)',
                ''
            ];

            for (var i = 0; i < sampleCount; i++) {
                item = allItems[i] - parsedSmallestItem;
                line = padIndex(i + 1) + '. ' + hashes[i] + ' % ' +
                       parsedTotalCount + ' = ' + padSample(item);
                lines.push(line);
            }

            return lines.join('\n');
        }

        // Initialize the models.
        form = {};
        output = {};
        parseFunctions = {
            sampleCount: parseSampleCount,
            seed: parseSeed,
            smallestItem: parseInteger,
            totalCount: parsePositiveInteger
        };

        input = {
            smallestItem: 1
        };

        form.showing = false;
        form.errors = {};
        form.input = input;
        form.parsed = {};
        form.relatedErrors = {};

        parsed = form.parsed;
        // We need to set the parsed value as well to make sure the
        // highest-item element gets updated if the total count is updated.
        onInput('smallestItem');

        form.onInputChange = onInput;

        form.submit = function() {
            var debugInfo,
                result;

            if (!validateForm()) {
                return;
            }
            result = chooseSamples(getSamplesUnique, parsed);
            debugInfo = makeDebugInfo(parsed, result);
            setOutput(result.all, result.unique, result.sorted, debugInfo);
            form.showing = true;
        };

        // Initialize the scope.
        $scope.form = form;
        $scope.output = output;

        $scope.highestItem = function() {
            var highest = parsed.smallestItem + parsed.totalCount - 1;
            if ((typeof highest !== 'number') || isNaN(highest)) {
                highest = '';
            }
            return highest;
        };

    }]);

})();
<commit_msg>Remove unneeded logging.<commit_after>'use strict';

/* Controllers */

(function(){

    var errorMessages = {
        integerRequired: 'A whole number is required.',
        numberTooSmall: 'The number must be bigger than zero.',
        sampleCountTooLarge: 'The sample count must be smaller than the total count.',
        seedRequired: 'A random seed is required.',
        positiveIntegerRequired: 'A whole number bigger than zero is required.'
    };

    var samplerControllers = angular.module('samplerApp.controllers.form', [
        'samplerApp.services'
    ]);

    // Return an error object.
    function makeError(message, related) {
        return {
            message: message,
            related: related
        };
    }

    // TODO: move this to services.js?
    function chooseSamples(getSamplesUnique, parsed) {
        var hashes = [],
            sampleCount = parsed.sampleCount,
            seed = parsed.seed,
            smallestItem = parsed.smallestItem,
            totalCount = parsed.totalCount;

        var result = getSamplesUnique(seed, totalCount, sampleCount, smallestItem, hashes);

        var uniqueItems = result[0];
        var sortedItems = uniqueItems.concat();  // make a copy.
        sortedItems.sort(function(a, b) {
            return a - b;
        });

        return {
            all: result[1],
            hashes: hashes,
            sorted: sortedItems,
            unique: uniqueItems
        };
    }

    /**
     * @ngdoc function
     * @name samplerApp.controller:MainCtrl
     * @description
     * # MainCtrl
     * Controller of the samplerApp
     *
     * We do not perform form validation on ng-keyup, ng-change, or even
     * ng-blur.  This is because we take the philosophy that the user
     * should be able to continue working on the form undisturbed until
     * pressing the submit button, which sends the message that the user
     * is finished.
     *     However, we do clear error messages on ng-change because that
     * signifies that the user has started to correct the error.  We also
     * clear errors on related input elements whose error message could
     * potentially change as a result (e.g. the error message saying that
     * the sample count is larger than the total number of items).
     */
    samplerControllers.controller('MainCtrl', ['$log', '$scope', '$window',
      'getSamplesUnique', 'leftPadder', 'spellsInt',
      function ($log, $scope, $window, getSamplesUnique, leftPadder, spellsInt) {

        var form,
            input,
            output,
            parsed,
            parseFunctions;

        // The parse functions each return an object containing either
        // an error object or the parsed integer value.

        function parseSeed(inputValue) {
            var result = {};
            if (!inputValue) {
                result.error = makeError(errorMessages.seedRequired);
            } else {
                result.value = inputValue;
            }
            return result;
        }

        function parseInteger(inputValue) {
            var result = {};
            var n = spellsInt(inputValue);
            if (isNaN(n)) {
                result.error = makeError(errorMessages.integerRequired);
            } else {
                result.value = n;
            }
            return result;
        }

        function parsePositiveInteger(inputValue) {
            var result = parseInteger(inputValue);
            if (result.error !== undefined) {
                result.error = makeError(errorMessages.positiveIntegerRequired);
            } else if (result.value < 1) {
                result.error = makeError(errorMessages.numberTooSmall);
            }
            return result;
        }

        function parseSampleCount(inputValue) {
            var result = parsePositiveInteger(inputValue);
            if ((result.error === undefined) &&
                (parsed.totalCount !== undefined) &&
                (result.value > parsed.totalCount)) {
                result.error = makeError(errorMessages.sampleCountTooLarge, ['totalCount']);
            }
            return result;
        }

        function setOutput(all, unique, sorted, debugInfo) {
            output.allItems = all;
            output.uniqueItems = unique;
            output.sortedItems = sorted;
            output.debug = debugInfo;
        }

        // Actions to take when an input element changes:
        //
        // 1) clear the result display,
        // 2) clear any associated errors,
        // 3) set the parsed value if no error, and
        // 4) return any error (or else null).
        //
        // Returns undefined or an error object.
        function onInput(inputLabel) {
            var errors = form.errors,
                parsed = form.parsed,
                parseInput = parseFunctions[inputLabel],
                relatedErrors = form.relatedErrors;

            form.showing = false;
            // Clear the result display since the input has changed.
            setOutput();
            // Clear the main error and any related errors.
            // (It's okay to delete if the property does not exist.)
            delete errors[inputLabel];
            var related = relatedErrors[inputLabel];
            if (related !== undefined) {
                for (var i = 0, len = related.length; i < len; i++) {
                    delete errors[related[i]];
                }
                // We only need to clear related errors once.
                delete relatedErrors[inputLabel];
            }

            var result = parseInput(form.input[inputLabel]);
            // The value will be undefined if parsing yielded an error.
            parsed[inputLabel] = result.value;

            return result.error;
        }

        // Return whether the form is error-free up to this point.
        // Also sets errors if there is a validation error.
        function checkInput(isOkay, inputLabel) {
            var relatedErrors = form.relatedErrors;
            var error = onInput(inputLabel);

            if (error !== undefined) {
                var errorText = error.message;
                var related = error.related;

                form.errors[inputLabel] = errorText;

                if (related !== undefined) {
                    for (var i = 0, len = related.length; i < len; i++) {
                        var relatedLabel = related[i];
                        if (relatedErrors[relatedLabel] === undefined) {
                            relatedErrors[relatedLabel] = [];
                        }
                        relatedErrors[relatedLabel].push(inputLabel);
                    }
                }
                isOkay = false;
            }

            return isOkay;
        }

        // Notes re: form validation & input values
        //
        // Note that with input type "number", the input value may already be
        // converted to a number and not be a string.
        //
        // For input type "number", an empty input element can result
        // in an input value of null.
        //
        // Moreover, if the user supplied "-1" and the input element has
        // min="1", then input[key] will be undefined (and spellsInt()
        // will return NaN) for at least some browsers.  Thus, we include
        // the full helpful error message if the parsed integer is NaN.

        // Populate the given object with parsed values, and return
        // whether the form validated without error.
        function validateForm() {
            var isOkay = true;

            isOkay = checkInput(isOkay, 'seed');
            isOkay = checkInput(isOkay, 'smallestItem');
            isOkay = checkInput(isOkay, 'totalCount');

            // We validate sampleCount after totalCount because validating
            // sampleCount depends on totalCount.
            isOkay = checkInput(isOkay, 'sampleCount');

            return isOkay;
        }

        function makeDebugInfo(parsed, result) {
            var allItems = result.all,
                hashes = result.hashes,
                item,
                line,
                lines,
                padIndex,
                padSample,
                parsedSeed = parsed.seed,
                parsedSmallestItem = parsed.smallestItem,
                parsedTotalCount = parsed.totalCount,
                sampleCount;

            sampleCount = allItems.length;

            padIndex = leftPadder(sampleCount.toString().length, ' ');
            padSample = leftPadder((parsedTotalCount - 1).toString().length, ' ');

            lines = [
                'Seed: "' + parsedSeed + '"',
                '      "' + encodeURI(parsedSeed) + '" (url-encoded)',
                ''
            ];

            for (var i = 0; i < sampleCount; i++) {
                item = allItems[i] - parsedSmallestItem;
                line = padIndex(i + 1) + '. ' + hashes[i] + ' % ' +
                       parsedTotalCount + ' = ' + padSample(item);
                lines.push(line);
            }

            return lines.join('\n');
        }

        // Initialize the models.
        form = {};
        output = {};
        parseFunctions = {
            sampleCount: parseSampleCount,
            seed: parseSeed,
            smallestItem: parseInteger,
            totalCount: parsePositiveInteger
        };

        input = {
            smallestItem: 1
        };

        form.showing = false;
        form.errors = {};
        form.input = input;
        form.parsed = {};
        form.relatedErrors = {};

        parsed = form.parsed;
        // We need to set the parsed value as well to make sure the
        // highest-item element gets updated if the total count is updated.
        onInput('smallestItem');

        form.onInputChange = onInput;

        form.submit = function() {
            var debugInfo,
                result;

            if (!validateForm()) {
                return;
            }
            result = chooseSamples(getSamplesUnique, parsed);
            debugInfo = makeDebugInfo(parsed, result);
            setOutput(result.all, result.unique, result.sorted, debugInfo);
            form.showing = true;
        };

        // Initialize the scope.
        $scope.form = form;
        $scope.output = output;

        $scope.highestItem = function() {
            var highest = parsed.smallestItem + parsed.totalCount - 1;
            if ((typeof highest !== 'number') || isNaN(highest)) {
                highest = '';
            }
            return highest;
        };

    }]);

})();
<|endoftext|>


<commit_before>'use strict';

angular.module('fivefifteenApp')
  .controller('MainCtrl', function ($scope, $routeParams, Data, Steps) {

    // Simple Data service to persist form values.
    $scope.data = Data;

    if (angular.isDefined($routeParams.stepName)) {
      $scope.path = $routeParams.stepName;
      $scope.step = Steps.rawData[$scope.path];
    }

    $scope.steps = Steps.data;
  })

  // scope data is not persistent across views so we use a simple service.
  .factory('Data', function () {
    // This variable holds all of the text used on the site.
    return {};
  })

  .factory('Steps', function($firebase) {
    var url = new Firebase("https://fivefifteen.firebaseio.com/steps"),
        promise = $firebase(url),
        factory = { "data": [] };

    promise.$on('loaded', function(values) {
      // If we get values, store it both in the scope and Steps.
      if (!angular.isDefined(values)) {
        return;
      }
      factory.rawData = values;
      for (var key in values) {
        var stepNumber = values[key].stepNumber;
        factory.data[stepNumber] = values[key];
      }
    });

    return factory;
  });
<commit_msg>Remove broken logic from controller.<commit_after>'use strict';

angular.module('fivefifteenApp')
  .controller('MainCtrl', function ($scope, $routeParams, Data, Steps) {

    // Simple Data service to persist form values.
    $scope.data = Data;

    $scope.steps = Steps.data;

    if (angular.isDefined($routeParams.stepName)) {
      $scope.path = $routeParams.stepName;
    }
  })

  // scope data is not persistent across views so we use a simple service.
  .factory('Data', function () {
    // This variable holds all of the text used on the site.
    return {};
  })

  .factory('Steps', function($firebase) {
    var url = new Firebase("https://fivefifteen.firebaseio.com/steps"),
        promise = $firebase(url),
        factory = { "data": [] };

    promise.$on('loaded', function(values) {
      // If we get values, store it both in the scope and Steps.
      if (!angular.isDefined(values)) {
        return;
      }
      factory.rawData = values;
      for (var key in values) {
        var stepNumber = values[key].stepNumber;
        factory.data[stepNumber] = values[key];
      }
    });

    return factory;
  });
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc function
 * @name stravaWorkoutsApp.controller:MainCtrl
 * @description
 * # MainCtrl
 * Controller of the stravaWorkoutsApp
 */
angular.module('stravaWorkoutsApp')
  .controller('MainCtrl', function ($scope) {

  	// TODO: Authenticate with Strava here.
  	//
    $scope.swims = [1, 2];
    $scope.bikes = [1, 2, 3];
    $scope.runs = [1];
  });
<commit_msg>Get Strava auth code<commit_after>'use strict';

/**
 * @ngdoc function
 * @name stravaWorkoutsApp.controller:MainCtrl
 * @description
 * # MainCtrl
 * Controller of the stravaWorkoutsApp
 */
angular.module('stravaWorkoutsApp')
  .config(function($locationProvider) {
  	$locationProvider.html5Mode(true);
  })
  .controller('MainCtrl', function ($scope, $location) {

  	// Authenticate with Strava
  	// 2. Strava redirects here with access code
  	if ($location.search().code) {
  		// 3. This page calls lambda function with access code
  		// 4. Lambda function knows secret, exchanges code for token with secrent
  		// 5. Lambda function returns the token as a parameter
  		alert($location.search().code);

  	}
  	else {
  		// 1. Page redirects to strava auth endpoint
  		// window.location = 'https://www.strava.com/oauth/authorize?client_id=6518&response_type=code&redirect_uri=http://emenendez.github.io/strava-workouts/';
  		window.location = 'https://www.strava.com/oauth/authorize?client_id=6518&response_type=code&redirect_uri=http://localhost:9000/';
  		return;
  	}

  	//
  	//
    $scope.swims = [1, 2];
    $scope.bikes = [1, 2, 3];
    $scope.runs = [1];
  });
<|endoftext|>


<commit_before>/* globals API_BASE, REPO, API_HOST, $, Handlebars, d3, moment */

'use strict';

Handlebars.registerHelper('eachLabel', function(context, options) {
  if (!context) return;
  var ret = "";
  for(var i=0, j=context.length; i<j; i++) {
    context[i]['label-color'] = extractLight(context[i]['color']) ? 'label-color-white' : 'label-color-dark';
    ret = ret + options.fn(context[i]);
  }

  return ret;
});

var tooltipUserTemplate = Handlebars.compile($('#tooltip-user-template').html());
var tooltipIssueTemplate = Handlebars.compile($('#tooltip-issue-template').html());

function drawIssuesActivity() {
    $.getJSON(API_BASE + '/issues_activity')
        .done(function (json) {
            var data = json.data;
            var opened = data.opened;
            var closed = data.closed;
            $('#issues-activity').highcharts({
                chart: {
                    type: 'spline'
                },
                title: {
                    text: 'Issues Burndown'
                },
                subtitle: {
                    text: '# of issues opened vs closed, monthly'
                },
                xAxis: {
                    categories: opened.map(function(e) { return e.month; })
                },
                yAxis: {
                    min: 0,
                    title: {
                        text: '# Issues'
                    }
                },
                legend: {
                    enabled: false
                },
                series: [
                    {
                        name: 'Opened',
                        data: opened.map(function(e) { return e.value; }),
                        lineColor: '#FF4E50',
                        color: '#FF4E50'
                    },
                    {
                        name: 'Closed',
                        data: closed.map(function(e) { return e.value; }),
                        lineColor: '#88C425',
                        color: '#88C425'
                    }
                ]
            });
        })
        .fail(displayFailMessage);
}

var issuesListTemplate = Handlebars.compile($('#issues-list-template').html());

function drawIssuesWidget(labels, options, label) {
    labels.data.sort(function (a, b) {
        return a.name.toLowerCase() >= b.name.toLowerCase();
    });

    var url = API_BASE + options.endpoint;
    if (label) {
        url +=  '?label=' + label;
    }

    $.getJSON(url)
        .done(function(json) {
            var data = json.data;
            var context = {
                title: options.title,
                subtitle: "(max. 20 results)",
                label: labels.data
            }
            if (!data.length) {
                context.issues = [{
                    title: 'No issues matching your request'
                }];
            } else {
                context.issues = data;
            }
            var $list = $(issuesListTemplate(context));
            $(options.selector).empty().append($list);
        })
        .fail(displayFailMessage)
        .complete(function() {
            $(options.selector + ' .widget-label--label-item').on('click', function() {
                drawIssuesWidget(labels, options, $(this).data('label'));
            });
        });
}

function drawAvgIssueTime() {
    makeXYGraph('#avg-issue-time', {
        endpoint: '/avg_issue_time',
        type: 'spline',
        title: "Average Issue Time",
        subtitle: "From the time it's opened until it's closed",
        keyName: 'month',
        valueName: function(e) {
            var m = moment.duration(e.value, 'seconds');
            return {
                name: m.humanize(),
                y: Math.ceil(e.value / (3600 * 24))
            };
        },
        yTitle: 'Days',
        label: 'days'
    });
}

function makeD3Graph(issues_data) {
    var nodes = [];
    var links = [];
    for (var number in issues_data) {
        var user_nodes = [];
        var issue = issues_data[number];
        issue.users.forEach(function(user) {
            var node = {
                type: 'user',
                data: user
            }
            nodes.push(node);
            user_nodes.push(node);
        });
        var issue_node = {
            type: 'issue',
            data: issue.issue
        };
        nodes.push(issue_node);

        // make links
        user_nodes.forEach(function(node) {
            var link = {
                source: node,
                target: issue_node
            };
            links.push(link);
        });
    }
    return {
        nodes: nodes,
        links: links
    }
}

function drawIssuesInvolvement() {
    $.getJSON(API_BASE + '/issues_involvement')
        .done(function(json) {
            var $container = $('#issues-involvement-graph-container');
            var width = $container.width();
            var height = $container.height();
            var radius = 25;
            var ratio = 1.5;

            var graph_data = makeD3Graph(json.data);
            var nodes = graph_data.nodes;
            var links = graph_data.links;

            var force = d3.layout.force()
                .charge(-350)
                .linkDistance(80)
                .gravity(0.05)
                .size([width, height]);

            var tip = d3.tip().attr('class', 'd3-tip').html(function(d) {
                var context;
                var template;
                if (d.type == 'issue') {
                    context = {
                        number: d.data.number,
                        title: d.data.title,
                        url: d.data.html_url,
                        comments: d.data.comments,
                        ago: moment().from(d.data.created_at, true)
                    };
                    template = tooltipIssueTemplate;
                } else {
                    context = {
                        login: d.data.login,
                        imgURL: d.data.avatar_url
                    };
                    template = tooltipUserTemplate;
                }
                return template(context);
            });

            var svg = d3.select('#issues-involvement-graph-container').append('svg')
                .attr('width', width)
                .attr('height', height)
                .on('mousedown', tip.hide);

            svg.call(tip);

            force.nodes(nodes)
                .links(links)
                .start();

            var link = svg.selectAll(".link")
                .data(links)
                .enter().append("line")
                .attr("class", "link")
                .style("stroke-width", 2);

            var gnodes = svg.selectAll('g.gnode')
                .data(nodes)
                .enter()
                .append('g')
                .classed('gnode', true)
                .call(force.drag);

            var nodes = gnodes.append('circle')
                .attr('class', 'node')
                .attr("r", function(d) {
                    if (d.type == 'issue') {
                        return radius;
                    }
                    return radius / ratio;
                })
                .style("fill", function(d) {
                    if (d.type == 'issue') {
                        return '#FF4E50';
                    }
                    return '#88C425';
                });

            var labels = gnodes.append('text')
                .attr('class', 'textnode')
                .attr('dx', function (d) {
                    if (d.type == 'issue') {
                        return radius + 3;
                    }
                    return radius / ratio + 3;
                })
                .attr('dy', 6)
                .style('fill', 'white')
                .text(function(d) {
                    if (d.type == 'issue') {
                        return '#' + d.data.number;
                    }
                    return d.data.login;
                })
                .on('mousedown', function(e) {
                    d3.event.stopPropagation();
                    tip.show(e);
                });

            force.on("tick", function() {
                link.attr("x1", function(d) { return d.source.x; })
                    .attr("y1", function(d) { return d.source.y; })
                    .attr("x2", function(d) { return d.target.x; })
                    .attr("y2", function(d) { return d.target.y; });

                gnodes.attr("transform", function(d) {
                    return 'translate(' + [d.x, d.y] + ')';
                });

                // http://mbostock.github.io/d3/talk/20110921/bounding.html
                var rl = radius + 5;
                gnodes.attr("cx", function(d) { return d.x = Math.max(rl, Math.min(width - rl, d.x)); })
                    .attr("cy", function(d) { return d.y = Math.max(rl, Math.min(height - rl, d.y)); });
            });
        })
        .fail(displayFailMessage);
}

function addMilestoneStatus() {

    var endpoint = API_HOST + REPO + '/milestones';
    var $milestones = $('#milestones');

    $.get(endpoint)
        .success(displayData)
        .fail(displayFailMessage);

    function displayData(data) {
        if (data.data.length) {

            var template = Handlebars.compile($('#insights-milestone').html());

            $.each(data.data, function(idx, milestone) {

                var due_date = 0;
                var delay = false;

                if (milestone.due_on) {
                    var start = moment(new Date());
                    var end = moment((new Date(milestone.due_on)).getTime());
                    due_date = start.from(end, true);
                    if ((new Date(milestone.due_on)).getTime() > (new Date()).getTime()) {
                        due_date = 'in ' + due_date;
                    } else {
                        due_date += ' ago';
                        delay = true;
                    }
                }

                var context = {
                    closed: milestone.closed_issues,
                    opened: milestone.open_issues,
                    title: milestone.title,
                    url: 'https://github.com/' + REPO + '/issues?state=open&milestone=' + milestone.number,
                    due: due_date,
                    progress: parseInt(milestone.closed_issues / (milestone.closed_issues + milestone.open_issues) * 100, 10),
                    delay: delay
                };

                $milestones.append(template(context));
            });
        } else {
            $($milestones).append($('
No milestones available.
'));
        }
    }
}

var pullRequestListTemplate = Handlebars.compile($('#pull-requests-list-template').html());

function drawOutstandingPullRequests() {
    $.get(API_BASE + '/outstanding_pull_requests')
        .done(function (data) {
            var prs = data.data;
            prs.forEach(function (e) {
                e.last_activity = moment().from(e.last_activity, true);
            });
            var $widget = $(pullRequestListTemplate({
                prs: prs,
                title: "Outstanding Pull Requests",
                subtitle: "(max. 20 results)"
            }));
            $('#outstanding-pull-requests').append($widget);
        })
        .fail(displayFailMessage);
}

//http://stackoverflow.com/questions/12043187/how-to-check-if-hex-color-is-too-black
function extractLight(c) {
    var rgb = parseInt(c, 16);
    var r = (rgb >> 16) & 0xff;
    var g = (rgb >>  8) & 0xff;
    var b = (rgb >>  0) & 0xff;

    var luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
    return luma < 70;
}

function drawInsights () {
    drawIssuesActivity();
    $.get(API_BASE + '/labels')
        .success(function (labels) {
            drawIssuesWidget(labels, {
                selector: '#untouched-issues',
                endpoint: '/untouched_issues',
                title: "Inactive Issues (2 weeks)"
            });
            drawIssuesWidget(labels, {
                selector: '#inactive-issues',
                endpoint: '/inactive_issues',
                title: "Untouched Issues"
            });
            drawIssuesWidget(labels, {
                selector: '#unassigned-issues',
                endpoint: '/unassigned_issues',
                title: "Unassigned Issues"
            });
        });
    drawOutstandingPullRequests();
    drawAvgIssueTime();
    drawIssuesInvolvement();
    addMilestoneStatus();
}
<commit_msg>Make burndown graph easier to read. Fixes #108<commit_after>/* globals API_BASE, REPO, API_HOST, $, Handlebars, d3, moment */

'use strict';

Handlebars.registerHelper('eachLabel', function(context, options) {
  if (!context) return;
  var ret = "";
  for(var i=0, j=context.length; i<j; i++) {
    context[i]['label-color'] = extractLight(context[i]['color']) ? 'label-color-white' : 'label-color-dark';
    ret = ret + options.fn(context[i]);
  }

  return ret;
});

var tooltipUserTemplate = Handlebars.compile($('#tooltip-user-template').html());
var tooltipIssueTemplate = Handlebars.compile($('#tooltip-issue-template').html());

function drawIssuesActivity() {
    $.getJSON(API_BASE + '/issues_activity')
        .done(function (json) {
            var data = json.data;
            var opened = data.opened;
            var closed = data.closed;
            $('#issues-activity').highcharts({
                chart: {
                    type: 'spline'
                },
                title: {
                    text: 'Issues Burndown'
                },
                subtitle: {
                    text: '# of issues opened vs closed, monthly'
                },
                xAxis: {
                    categories: opened.map(function(e) { return e.month; })
                },
                yAxis: {
                    min: 0,
                    title: {
                        text: '# Issues'
                    }
                },
                legend: {
                    enabled: true
                },
                tooltip: {
                    shared: true
                },
                series: [
                    {
                        name: 'Opened',
                        data: opened.map(function(e) { return e.value; }),
                        lineColor: '#FF4E50',
                        color: '#FF4E50'
                    },
                    {
                        name: 'Closed',
                        data: closed.map(function(e) { return e.value; }),
                        lineColor: '#88C425',
                        color: '#88C425'
                    }
                ]
            });
        })
        .fail(displayFailMessage);
}

var issuesListTemplate = Handlebars.compile($('#issues-list-template').html());

function drawIssuesWidget(labels, options, label) {
    labels.data.sort(function (a, b) {
        return a.name.toLowerCase() >= b.name.toLowerCase();
    });

    var url = API_BASE + options.endpoint;
    if (label) {
        url +=  '?label=' + label;
    }

    $.getJSON(url)
        .done(function(json) {
            var data = json.data;
            var context = {
                title: options.title,
                subtitle: "(max. 20 results)",
                label: labels.data
            }
            if (!data.length) {
                context.issues = [{
                    title: 'No issues matching your request'
                }];
            } else {
                context.issues = data;
            }
            var $list = $(issuesListTemplate(context));
            $(options.selector).empty().append($list);
        })
        .fail(displayFailMessage)
        .complete(function() {
            $(options.selector + ' .widget-label--label-item').on('click', function() {
                drawIssuesWidget(labels, options, $(this).data('label'));
            });
        });
}

function drawAvgIssueTime() {
    makeXYGraph('#avg-issue-time', {
        endpoint: '/avg_issue_time',
        type: 'spline',
        title: "Average Issue Time",
        subtitle: "From the time it's opened until it's closed",
        keyName: 'month',
        valueName: function(e) {
            var m = moment.duration(e.value, 'seconds');
            return {
                name: m.humanize(),
                y: Math.ceil(e.value / (3600 * 24))
            };
        },
        yTitle: 'Days',
        label: 'days'
    });
}

function makeD3Graph(issues_data) {
    var nodes = [];
    var links = [];
    for (var number in issues_data) {
        var user_nodes = [];
        var issue = issues_data[number];
        issue.users.forEach(function(user) {
            var node = {
                type: 'user',
                data: user
            }
            nodes.push(node);
            user_nodes.push(node);
        });
        var issue_node = {
            type: 'issue',
            data: issue.issue
        };
        nodes.push(issue_node);

        // make links
        user_nodes.forEach(function(node) {
            var link = {
                source: node,
                target: issue_node
            };
            links.push(link);
        });
    }
    return {
        nodes: nodes,
        links: links
    }
}

function drawIssuesInvolvement() {
    $.getJSON(API_BASE + '/issues_involvement')
        .done(function(json) {
            var $container = $('#issues-involvement-graph-container');
            var width = $container.width();
            var height = $container.height();
            var radius = 25;
            var ratio = 1.5;

            var graph_data = makeD3Graph(json.data);
            var nodes = graph_data.nodes;
            var links = graph_data.links;

            var force = d3.layout.force()
                .charge(-350)
                .linkDistance(80)
                .gravity(0.05)
                .size([width, height]);

            var tip = d3.tip().attr('class', 'd3-tip').html(function(d) {
                var context;
                var template;
                if (d.type == 'issue') {
                    context = {
                        number: d.data.number,
                        title: d.data.title,
                        url: d.data.html_url,
                        comments: d.data.comments,
                        ago: moment().from(d.data.created_at, true)
                    };
                    template = tooltipIssueTemplate;
                } else {
                    context = {
                        login: d.data.login,
                        imgURL: d.data.avatar_url
                    };
                    template = tooltipUserTemplate;
                }
                return template(context);
            });

            var svg = d3.select('#issues-involvement-graph-container').append('svg')
                .attr('width', width)
                .attr('height', height)
                .on('mousedown', tip.hide);

            svg.call(tip);

            force.nodes(nodes)
                .links(links)
                .start();

            var link = svg.selectAll(".link")
                .data(links)
                .enter().append("line")
                .attr("class", "link")
                .style("stroke-width", 2);

            var gnodes = svg.selectAll('g.gnode')
                .data(nodes)
                .enter()
                .append('g')
                .classed('gnode', true)
                .call(force.drag);

            var nodes = gnodes.append('circle')
                .attr('class', 'node')
                .attr("r", function(d) {
                    if (d.type == 'issue') {
                        return radius;
                    }
                    return radius / ratio;
                })
                .style("fill", function(d) {
                    if (d.type == 'issue') {
                        return '#FF4E50';
                    }
                    return '#88C425';
                });

            var labels = gnodes.append('text')
                .attr('class', 'textnode')
                .attr('dx', function (d) {
                    if (d.type == 'issue') {
                        return radius + 3;
                    }
                    return radius / ratio + 3;
                })
                .attr('dy', 6)
                .style('fill', 'white')
                .text(function(d) {
                    if (d.type == 'issue') {
                        return '#' + d.data.number;
                    }
                    return d.data.login;
                })
                .on('mousedown', function(e) {
                    d3.event.stopPropagation();
                    tip.show(e);
                });

            force.on("tick", function() {
                link.attr("x1", function(d) { return d.source.x; })
                    .attr("y1", function(d) { return d.source.y; })
                    .attr("x2", function(d) { return d.target.x; })
                    .attr("y2", function(d) { return d.target.y; });

                gnodes.attr("transform", function(d) {
                    return 'translate(' + [d.x, d.y] + ')';
                });

                // http://mbostock.github.io/d3/talk/20110921/bounding.html
                var rl = radius + 5;
                gnodes.attr("cx", function(d) { return d.x = Math.max(rl, Math.min(width - rl, d.x)); })
                    .attr("cy", function(d) { return d.y = Math.max(rl, Math.min(height - rl, d.y)); });
            });
        })
        .fail(displayFailMessage);
}

function addMilestoneStatus() {

    var endpoint = API_HOST + REPO + '/milestones';
    var $milestones = $('#milestones');

    $.get(endpoint)
        .success(displayData)
        .fail(displayFailMessage);

    function displayData(data) {
        if (data.data.length) {

            var template = Handlebars.compile($('#insights-milestone').html());

            $.each(data.data, function(idx, milestone) {

                var due_date = 0;
                var delay = false;

                if (milestone.due_on) {
                    var start = moment(new Date());
                    var end = moment((new Date(milestone.due_on)).getTime());
                    due_date = start.from(end, true);
                    if ((new Date(milestone.due_on)).getTime() > (new Date()).getTime()) {
                        due_date = 'in ' + due_date;
                    } else {
                        due_date += ' ago';
                        delay = true;
                    }
                }

                var context = {
                    closed: milestone.closed_issues,
                    opened: milestone.open_issues,
                    title: milestone.title,
                    url: 'https://github.com/' + REPO + '/issues?state=open&milestone=' + milestone.number,
                    due: due_date,
                    progress: parseInt(milestone.closed_issues / (milestone.closed_issues + milestone.open_issues) * 100, 10),
                    delay: delay
                };

                $milestones.append(template(context));
            });
        } else {
            $($milestones).append($('
No milestones available.
'));
        }
    }
}

var pullRequestListTemplate = Handlebars.compile($('#pull-requests-list-template').html());

function drawOutstandingPullRequests() {
    $.get(API_BASE + '/outstanding_pull_requests')
        .done(function (data) {
            var prs = data.data;
            prs.forEach(function (e) {
                e.last_activity = moment().from(e.last_activity, true);
            });
            var $widget = $(pullRequestListTemplate({
                prs: prs,
                title: "Outstanding Pull Requests",
                subtitle: "(max. 20 results)"
            }));
            $('#outstanding-pull-requests').append($widget);
        })
        .fail(displayFailMessage);
}

//http://stackoverflow.com/questions/12043187/how-to-check-if-hex-color-is-too-black
function extractLight(c) {
    var rgb = parseInt(c, 16);
    var r = (rgb >> 16) & 0xff;
    var g = (rgb >>  8) & 0xff;
    var b = (rgb >>  0) & 0xff;

    var luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
    return luma < 70;
}

function drawInsights () {
    drawIssuesActivity();
    $.get(API_BASE + '/labels')
        .success(function (labels) {
            drawIssuesWidget(labels, {
                selector: '#untouched-issues',
                endpoint: '/untouched_issues',
                title: "Inactive Issues (2 weeks)"
            });
            drawIssuesWidget(labels, {
                selector: '#inactive-issues',
                endpoint: '/inactive_issues',
                title: "Untouched Issues"
            });
            drawIssuesWidget(labels, {
                selector: '#unassigned-issues',
                endpoint: '/unassigned_issues',
                title: "Unassigned Issues"
            });
        });
    drawOutstandingPullRequests();
    drawAvgIssueTime();
    drawIssuesInvolvement();
    addMilestoneStatus();
}
<|endoftext|>


<commit_before>import firstBy from 'thenby';
import { ipcRenderer } from 'electron';
import { Container } from 'unstated';
import debounce from 'lodash.debounce';
import Fuse from 'fuse.js';
import formatPath from '~/common/format-path';
import createStore from '~/common/preferences-store';
import Channels from '~/common/channels';

const FAILED_DEBOUNCE_WAIT = 250;

// Sort function for projects
const sortFn = firstBy('pinned', { direction: -1 })
  .thenBy('name', { ignoreCase: true })
  .thenBy('path');

/**
 * This state container keeps key info about projects
 */
export default class AppState extends Container {
  state = {
    ready: false,
    projects: [],
    selected: null,
    search: null,
    failed: [],
  };

  searchInputRef = null;

  preferences = createStore();

  constructor(...args) {
    super(...args);
    // Subscribe to main process replies
    ipcRenderer.on(Channels.PROJECT_OPEN_SUCCESS, (_, payload) =>
      this.proceedValidProject(payload)
    );
    ipcRenderer.on(Channels.PROJECT_OPEN_ERROR, (_, payload) =>
      this.proceedInvalidProject(payload)
    );
    ipcRenderer.on(Channels.PROJECTS_LOADED, () => {
      this.setState({ ready: true });
    });
    // Load added projects
    ipcRenderer.send(Channels.PROJECTS_LOAD);
  }

  // Preferences
  syncPreferences() {
    this.preferences.set(
      'projects',
      this.state.projects.map(project => project.path)
    );
    this.preferences.set(
      'pinned',
      this.state.projects
        .filter(project => project.pinned)
        .map(project => project.path)
    );
  }

  clearPreferences() {
    this.preferences.clear();
    this.setState({
      projects: [],
      pinned: [],
      search: null,
    });
  }

  editPreferences() {
    this.preferences.openInEditor();
  }

  getTerminalApp() {
    return this.state.terminal;
  }

  // Projects related
  isReady() {
    return this.state.ready;
  }

  hasProjects() {
    return this.state.projects.length > 0;
  }

  getFilteredProjects() {
    const { search, projects } = this.state;
    return search
      ? new Fuse(projects, { keys: ['name'] }).search(search)
      : projects.sort(sortFn);
  }

  refreshProjects() {
    this.state.projects.forEach(project => {
      ipcRenderer.send(Channels.PROJECT_OPEN_REQUEST, project.path);
    });
  }

  setSelected(project) {
    this.setState({ selected: project });
  }

  getSelected(code) {
    return this.state.selected;
  }

  proceedValidProject(newProject) {
    // Check if project already exist in list
    const existingProject = this.state.projects.find(
      project => project.path === newProject.path
    );
    // Replace old project with a new data, or append it if new one
    this.setState({
      projects: existingProject
        ? this.state.projects.map(
            project => (project === existingProject ? newProject : project)
          )
        : [...this.state.projects, newProject],
    });
    // Save settings
    this.syncPreferences();
  }

  // Since adding projects is async, we need to make sure send notification once
  notifyAboutFailedProjects = debounce(() => {
    const failedCount = this.state.failed.length;
    const message =
      failedCount > 1
        ? `Failed to load ${failedCount} projects.`
        : `Failed to load project. Make sure ${formatPath(
            this.state.failed[0]
          )} is a valid npm module.`;
    ipcRenderer.send(Channels.NOTIFICATION_SHOW, { body: message });
    this.setState({ failed: [] });
  }, FAILED_DEBOUNCE_WAIT);

  proceedInvalidProject(reason) {
    this.setState({ failed: [...this.state.failed, reason.path] });
    this.notifyAboutFailedProjects();
  }

  setPinned(project, value) {
    this.setState({
      projects: this.state.projects.map(
        current =>
          current === project ? { ...current, pinned: value } : current
      ),
    });
    this.syncPreferences();
  }

  pin(project) {
    this.setPinned(project, true);
  }

  unpin(project) {
    this.setPinned(project, false);
  }

  removeProject(project) {
    this.setState({
      projects: this.state.projects.filter(current => current !== project),
    });
    this.syncPreferences();
  }

  // Project search related
  setSearchInputRef(node) {
    this.searchInputRef = node;
  }

  getSearch() {
    return this.state.search;
  }

  hasSearch() {
    return typeof this.getSearch() === 'string';
  }

  setSearch(keyword) {
    this.setSelected(null);
    this.setState({ search: keyword });
    if (this.searchInputRef) {
      this.searchInputRef.focus();
    }
  }

  clearSearch() {
    this.setState({ search: null });
  }

  // Drag and drop related
  fileDragEnter() {
    this.setState({ entered: true });
  }

  fileDragLeave() {
    this.setState({ entered: false });
  }

  fileDrop(files) {
    Array.from(files).forEach(file =>
      ipcRenderer.send(Channels.PROJECT_OPEN_REQUEST, file.path)
    );
  }

  checkForUpdates() {
    ipcRenderer.send(Channels.CHECK_FOR_UPDATE);
  }
}
<commit_msg>chore: clean up app state container<commit_after>import firstBy from 'thenby';
import { ipcRenderer } from 'electron';
import { Container } from 'unstated';
import debounce from 'lodash.debounce';
import Fuse from 'fuse.js';
import formatPath from '~/common/format-path';
import createStore from '~/common/preferences-store';
import Channels from '~/common/channels';

const FAILED_DEBOUNCE_WAIT = 250;

// Sort function for projects
const sortFn = firstBy('pinned', { direction: -1 })
  .thenBy('name', { ignoreCase: true })
  .thenBy('path');

// This state container keeps key info about projects
export default class AppState extends Container {
  state = {
    ready: false,
    projects: [],
    selected: null,
    search: null,
    failed: [],
  };

  searchInputRef = null;

  preferences = createStore();

  constructor(...args) {
    super(...args);
    // Subscribe to main process replies
    ipcRenderer.on(Channels.PROJECT_OPEN_SUCCESS, (_, payload) =>
      this.proceedValidProject(payload)
    );
    ipcRenderer.on(Channels.PROJECT_OPEN_ERROR, (_, payload) =>
      this.proceedInvalidProject(payload)
    );
    ipcRenderer.on(Channels.PROJECTS_LOADED, () => {
      this.setState({ ready: true });
    });
    // Load added projects
    ipcRenderer.send(Channels.PROJECTS_LOAD);
  }

  syncPreferences() {
    this.preferences.set(
      'projects',
      this.state.projects.map(project => project.path)
    );
    this.preferences.set(
      'pinned',
      this.state.projects
        .filter(project => project.pinned)
        .map(project => project.path)
    );
  }

  clearPreferences() {
    this.preferences.clear();
    this.setState({
      projects: [],
      pinned: [],
      search: null,
    });
  }

  editPreferences() {
    this.preferences.openInEditor();
  }

  getTerminalApp() {
    return this.state.terminal;
  }

  isReady() {
    return this.state.ready;
  }

  hasProjects() {
    return this.state.projects.length > 0;
  }

  getFilteredProjects() {
    const { search, projects } = this.state;
    return search
      ? new Fuse(projects, { keys: ['name'] }).search(search)
      : projects.sort(sortFn);
  }

  refreshProjects() {
    this.state.projects.forEach(project => {
      ipcRenderer.send(Channels.PROJECT_OPEN_REQUEST, project.path);
    });
  }

  setSelected(project) {
    this.setState({ selected: project });
  }

  getSelected(code) {
    return this.state.selected;
  }

  proceedValidProject(newProject) {
    // Check if project already exist in list
    const existingProject = this.state.projects.find(
      p => p.path === newProject.path
    );
    // Replace old project with a new data, or append it if new one
    const nextProjects = existingProject
      ? this.state.projects.map(p => (p === existingProject ? newProject : p))
      : [...this.state.projects, newProject];
    this.setState({ projects: nextProjects });
    this.syncPreferences();
  }

  // Since adding projects is async, we need to make sure send notification once
  notifyAboutFailedProjects = debounce(() => {
    const failedCount = this.state.failed.length;
    const message =
      failedCount > 1
        ? `Failed to load ${failedCount} projects.`
        : `Failed to load project. Make sure ${formatPath(
            this.state.failed[0]
          )} is a valid npm module.`;
    ipcRenderer.send(Channels.NOTIFICATION_SHOW, { body: message });
    this.setState({ failed: [] });
  }, FAILED_DEBOUNCE_WAIT);

  proceedInvalidProject(reason) {
    this.setState({ failed: [...this.state.failed, reason.path] });
    this.notifyAboutFailedProjects();
  }

  setPinned(project, value) {
    const nextProjects = this.state.projects.map(
      p => (p === project ? { ...p, pinned: value } : p)
    );
    this.setState({ projects: nextProjects });
    this.syncPreferences();
  }

  pin(project) {
    this.setPinned(project, true);
  }

  unpin(project) {
    this.setPinned(project, false);
  }

  removeProject(project) {
    const nextProjects = this.state.projects.filter(p => p !== project);
    this.setState({ projects: nextProjects });
    this.syncPreferences();
  }

  setSearchInputRef(node) {
    this.searchInputRef = node;
  }

  getSearch() {
    return this.state.search;
  }

  hasSearch() {
    return this.state.search !== null;
  }

  setSearch(keyword) {
    this.setSelected(null);
    this.setState({ search: keyword });
    if (this.searchInputRef) {
      this.searchInputRef.focus();
    }
  }

  clearSearch() {
    this.setState({ search: null });
  }

  fileDragEnter() {
    this.setState({ entered: true });
  }

  fileDragLeave() {
    this.setState({ entered: false });
  }

  fileDrop(files) {
    Array.from(files).forEach(file =>
      ipcRenderer.send(Channels.PROJECT_OPEN_REQUEST, file.path)
    );
  }

  checkForUpdates() {
    ipcRenderer.send(Channels.CHECK_FOR_UPDATE);
  }
}
<|endoftext|>


<commit_before>var authorTemplate = Handlebars.compile($('#timeline-author-template').html());
function formatAuthor(author) {
    var login = author.login;
    var avatarURL = author.avatar_url;
    var githubURL = 'http://github.com/' + login;

    return authorTemplate({
        login: login,
        avatarURL: avatarURL,
        githubURL: githubURL
    });
}

function formatLink(href, text) {
    return '' + text + '';
}

function formatIssue(event) {
    var issue = event.payload.issue;
    return formatLink(issue.html_url, '#' + issue.number);
}

function formatComment(event) {
    return formatLink(event.payload.comment.html_url, 'link');
}

var TIMELINE_MAPPING = {
    'CommitCommentEvent': {
        action: function(e) {
            return "commented on";
        },
        object: function(e) {
            var url = e.payload.comment.html_url;
            url = url.substring(0, url.lastIndexOf('#'));

            var sha = url.substring(url.lastIndexOf('/') + 1);
            return formatLink(url, sha.substring(0, 7));
        },
        link: formatComment
    },
    'CreateEvent': {
        action: function(e) {
            var s = "created";
            if (e.payload.ref_type == 'branch') {
                return s + " branch";
            } else if (e.payload.ref_type == 'tag') {
                return s + " tag";
            }
            return s;
        },
        object: function(e) {
            return e.payload.ref;
        },
    },
    'DeleteEvent': {
        action: function(e) {
            var s = "deleted";
            if (e.payload.ref_type == 'branch') {
                return s + " branch";
            } else if (e.payload.ref_type == 'tag') {
                return s + " tag";
            }
            return s;
        },
        object: function(e) {
            return e.payload.ref;
        },
    },
    'ForkEvent': {
        action: function(e) {
            return "forked to";
        },
        object: function(e) {
            var name = e.payload.forkee.full_name;
            var url = e.payload.forkee.html_url;
            return formatLink(url, name);
        }
    },
    'GollumEvent': {
        action: function(e) {
            var page = e.payload.pages[0];
            return page.action + " wiki page";
        },
        object: function(e) {
            var page = e.payload.pages[0];
            return formatLink(page.html_url, page.page_name);
        }
    },
    'IssueCommentEvent': {
        action: function(e) {
            return "commented on";
        },
        object: formatIssue,
        link: formatComment
    },
    'IssuesEvent': {
        action: function(e) {
            return e.payload.action;
        },
        object: formatIssue
    },
    'MemberEvent': {
        action: function(e) {
            return "added";
        },
        object: function(e) {
            var user = e.payload.member;
            return formatLink(user.html_url, user.login) + " as a collaborator";
        }
    }
};

<commit_msg>PublicEvent<commit_after>var authorTemplate = Handlebars.compile($('#timeline-author-template').html());
function formatAuthor(author) {
    var login = author.login;
    var avatarURL = author.avatar_url;
    var githubURL = 'http://github.com/' + login;

    return authorTemplate({
        login: login,
        avatarURL: avatarURL,
        githubURL: githubURL
    });
}

function formatLink(href, text) {
    return '' + text + '';
}

function formatIssue(event) {
    var issue = event.payload.issue;
    return formatLink(issue.html_url, '#' + issue.number);
}

function formatComment(event) {
    return formatLink(event.payload.comment.html_url, 'link');
}

var TIMELINE_MAPPING = {
    'CommitCommentEvent': {
        action: function(e) {
            return "commented on";
        },
        object: function(e) {
            var url = e.payload.comment.html_url;
            url = url.substring(0, url.lastIndexOf('#'));

            var sha = url.substring(url.lastIndexOf('/') + 1);
            return formatLink(url, sha.substring(0, 7));
        },
        link: formatComment
    },
    'CreateEvent': {
        action: function(e) {
            var s = "created";
            if (e.payload.ref_type == 'branch') {
                return s + " branch";
            } else if (e.payload.ref_type == 'tag') {
                return s + " tag";
            }
            return s;
        },
        object: function(e) {
            return e.payload.ref;
        },
    },
    'DeleteEvent': {
        action: function(e) {
            var s = "deleted";
            if (e.payload.ref_type == 'branch') {
                return s + " branch";
            } else if (e.payload.ref_type == 'tag') {
                return s + " tag";
            }
            return s;
        },
        object: function(e) {
            return e.payload.ref;
        },
    },
    'ForkEvent': {
        action: function(e) {
            return "forked to";
        },
        object: function(e) {
            var name = e.payload.forkee.full_name;
            var url = e.payload.forkee.html_url;
            return formatLink(url, name);
        }
    },
    'GollumEvent': {
        action: function(e) {
            var page = e.payload.pages[0];
            return page.action + " wiki page";
        },
        object: function(e) {
            var page = e.payload.pages[0];
            return formatLink(page.html_url, page.page_name);
        }
    },
    'IssueCommentEvent': {
        action: function(e) {
            return "commented on";
        },
        object: formatIssue,
        link: formatComment
    },
    'IssuesEvent': {
        action: function(e) {
            return e.payload.action;
        },
        object: formatIssue
    },
    'MemberEvent': {
        action: function(e) {
            return "added";
        },
        object: function(e) {
            var user = e.payload.member;
            return formatLink(user.html_url, user.login) + " as a collaborator";
        }
    },
    'PublicEvent': {
        action: function(e) {
            return "open sourced";
        },
        object: function(e) {
            return "the repository";
        }
    }
};

<|endoftext|>


<commit_before>"use strict";

/**
  *
  */
var Graph = function(s){

	var settings;

	var default_settings = {
		directed: true,
		weighted: false,
	};

	settings = (typeof s !== 'object') ? default_settings : s;

	// list of vertices
	var vertices = [];

	// adjacency list
	var adjList = [];

	function addVertex(data){
		var v = { data: data };

		// push to vertices array
		var vIndex = (vertices.push(v) - 1);

		// push to adjacency list
		adjList[vIndex] = [];

		return vIndex;
	}

	function addEdge(v1, v2){
		adjList[v1].push(v2);
		adjList[v2].push(v1);
	}

	var publicApi = {
		addVertex: addVertex,
		addEdge: addEdge,
		vertices: vertices,
		adjList: adjList,
	};

	return publicApi;

};

module.exports = Graph;<commit_msg>Graph basic functions implemented<commit_after>"use strict";

/**
  * Implementation of the graph using JavaScript
  * The graph is represented in the form of a hashed adjecency list
  *
  * author: Przemyslaw Jazlo <przemek@devrunner.pl>
  *
  */
var Graph = function(s){

	// graph settings
	var settings = {};

	// default settings
	var default_settings = {
		directed: true,
		multiedge: false,
		selfloops: false,
		weighted: false,
	};

	// initialize settings
	(function init_settings(){
		if(typeof s === 'undefined') {
			settings = default_settings;
		} else {
			settings['directed'] = (typeof s['directed'] === 'undefined') ? default_settings['directed'] : s['directed'];
			settings['multiedge'] = (typeof s['multiedge'] === 'undefined') ? default_settings['multiedge'] : s['multiedge'];
			settings['selfloops'] = (typeof s['selfloops'] === 'undefined') ? default_settings['selfloops'] : s['selfloops'];
			settings['weighted'] = (typeof s['weighted'] === 'undefined') ? default_settings['weighted'] : s['weighted'];
		}
	})();

	// hashmap of vertices
	var vertices = {};

	// vertex constructor function
	function Vertex(key, data){
		this.key = key;
		this.data = data;
		this.neighbours = {}; // adjecency list for the vertex
	}
	Vertex.prototype = {
		getKey: function(){
			return this.key;
		},
		addNeighbour: function(vKey, weight){
			this.neighbours[vKey] = weight;
		}
	};

	/**
	  * Inserts new vertex to the graph, key must be unique
	  */
	function addVertex(key, data){

		// check if key is provided
		if(typeof key === 'undefined'){
			throw "Vertex must have a unique key.";
		}

		// check if vertex already exists
		if(key in vertices){
			throw "Vertex with key = '" + key + "' already exists.";
		}

		// push to the vertices hash map
		vertices[key] = new Vertex(key, data);
	}

	/**
	  * Removes vertex from the graph
	  */
	function deleteVertex(vKey){
		if(typeof vKey === 'undefined'){
			throw "Must privide vertex key.";
		}
		if((vKey in vertices) === false){
			throw "Vertex " + vKey + " does not exist.";
		}

		// delete vertex from vertices object
		delete vertices[vKey];

		// delete all edges where vKey was an end vertex
		for(let v in vertices){
			delete vertices[v].neighbours[vKey];
		}
	}

	/**
	  * Inserts new edge connecting two vertices given by the key
	  */
	function addEdge(v1Key, v2Key, weight){

		if(typeof vertices[v1Key] === 'undefined'){
			throw "Vertex " + v1Key + " does not exist.";
		}
		if(typeof vertices[v2Key] === 'undefined'){
			throw "Vertex " + v2Key + " does not exist.";
		}

		// handle selfloops
		if(v1Key === v2Key && settings['selfloops'] === false){
			throw "Self-loops are not allowed.";
		}

		// handle multiedges
		if(areConnected(v1Key, v2Key) && settings['multiedge'] === false){
			throw "Multiedges are not allowed.";
		}

		// handle missing weights
		if(typeof weight === 'undefined' && settings['weighted'] === true){
			throw "Edges must have weights.";
		}

		// set weights
		weight = (settings['weighted'] === true) ? weight : true;

		// set v2 as v1's neighbour
		vertices[v1Key].addNeighbour(v2Key, weight);

		// for undirected graph add edge from v2 to v1 as well
		if(settings['directed'] === false){
			vertices[v2Key].addNeighbour(v1Key, weight);
		}

	}

	/**
	  * Deletes an edge
	  */
	function deleteEdge(v1Key, v2Key){
		if(typeof vertices[v1Key] === 'undefined'){
			throw "Vertex " + v1Key + " does not exist.";
		}
		if(typeof vertices[v2Key] === 'undefined'){
			throw "Vertex " + v2Key + " does not exist.";
		}

		if(typeof vertices[v1Key].neighbours[v2Key] !== 'undefined'){

			// remove connection from v1 to v2
			delete vertices[v1Key].neighbours[v2Key];

			// for undirected graphs, remove connection from v2 to v1 as well
			if(settings['directed'] === false){
				delete vertices[v2Key].neighbours[v1Key];
			}
			return true;
		}
		return false;
	}

	/**
	  * Checks if two nodes are connected with an edge
	  */
	function areConnected(v1Key, v2Key){

		if(typeof vertices[v1Key] === 'undefined'){
			throw "Vertex " + v1Key + " does not exist.";
		}
		if(typeof vertices[v2Key] === 'undefined'){
			throw "Vertex " + v2Key + " does not exist.";
		}

		if(typeof vertices[v1Key].neighbours[v2Key] !== 'undefined'){
			return true;
		}
		return false;
	}

	/**
	  * Finds all adjacent vertices for the given vertex
	  */
	function getAdjacentVertices(vKey){

		if(typeof vertices[vKey] === 'undefined'){
			throw "Vertex " + vKey + " does not exist.";
		}

		return vertices[vKey].neighbours;
	}

	var publicApi = {
		addVertex: addVertex,
		deleteVertex: deleteVertex,
		addEdge: addEdge,
		deleteEdge: deleteEdge,
		areConnected: areConnected,
		getAdjacentVertices: getAdjacentVertices,
	};

	return publicApi;

};

module.exports = Graph;<|endoftext|>