<commit_before>(function () {
    'use strict';

    // Define object config of properties for this chart directive
    var BarChartDefaults = {
        plotWidth: 800,
        plotHeight: 200,
        transitionMillis: 500,
        lazyLoad: true,
        binType: 'temporal'
    };

    /**
     * ngInject
     */
    function barChart (CartoConfig, BarChartDefaults) {

        var PLOT_CLASS = 'mos-barchart';

        // Begin directive module definition
        var module = {};

        module.restrict = 'EA';
        module.templateUrl = 'scripts/charting/barchart-partial.html';
        module.controller = 'ChartingController';

        module.scope = {
            data: '=',              // Required
            id: '@',                // Required
            plotWidth: '@',
            selectedY: '=',
            selectLabel: '=',
            selectUnit: '=',
            plotHeight: '@',
            margin: '&',
            binType: '@',
            lazyLoad: '@',
            transitionMillis: '@'
        };

        // This is a helper function for transforming api data into percentiles based on sqft
        function binBySqFt(data, groups) {
            data = _.filter(data, function(d) { return d.sqfeet > 0; });
            _.forEach(data, function(d) {
                d.log = Math.log10(d.sqfeet);
            });
            var maxLog = _.max(data, function(d) { return d.log; }).log;
            var binnedBySqFt = {};
            var binSize = maxLog / groups;
            var getBinSize =  function(d) {
                return binSize * i < d.log && (binSize * i) + binSize >= d.log;
            };
            for (var i = 0; i < groups; i++) {
                binnedBySqFt[i] = _.filter(data, getBinSize);
            }
            var output = [];
            _.forEach(binnedBySqFt, function(d, i) {
                // Naive count
                var kCount = d.length;
                // Special counts for fair avging
                var nEnergystar = _.filter(d, function(e) { return !isNaN(e.energystar) && e.energystar > 0; }).length;
                var nEUI = _.filter(d, function(e) { return !isNaN(e.eui) && e.eui > 0; }).length;
                // The bin boundaries
                var lowBound = Math.round(Math.pow(10, (i * binSize))).toLocaleString();
                var highBound = Math.round(Math.pow(10, (i * binSize) + binSize)).toLocaleString();
                // Min and max sq ft
                var minsqft = _.min(d, function(val) { return val.sqfeet; }).sqfeet;
                var maxsqft = _.max(d, function(val) { return val.sqfeet; }).sqfeet;
                output = output.concat({
                    totalemissions: _.reduce(d, function(memo, val) { return val.emissions + memo; }, 0),
                    avgenergystar: nEnergystar > 0 ? _.reduce(d, function(memo, val) { return val.energystar + memo; }, 0) / nEnergystar : 0,
                    avgeui: nEUI > 0 ? _.reduce(d, function(memo, val) { return val.eui + memo; }, 0) / nEUI : 0,
                    minsqft: minsqft ? minsqft : 0,
                    maxsqft: maxsqft ? maxsqft : 0,
                    avgsqft: _.reduce(d, function(memo, val) { return val.sqfeet + memo; }, 0) / kCount,
                    totalsqft: _.reduce(d, function(memo, val) { return val.sqfeet + memo; }, 0),
                    totalenergy: _.reduce(d, function(memo, val) { return (val.sqfeet * val.eui) + memo; }, 0),
                    count: kCount,
                    key: lowBound + '-' + highBound,
                    yearRange: _.min(d, function(d) { return d.yearbuilt; }).yearbuilt +
                        '-' + _.max(d, function(d) { return d.yearbuilt; }).yearbuilt
                });
            });
            return _.rest(output, function(d) { return d.count === 0; }).reverse(); // Prune empty head
        }

        // Helper function for transforming unshaped data into data binned and aggregated over 5 year periods
        function binByYears(data) {
            var filteredData = _.filter(data, function(d) { return d.yearbuilt > 1849; });
            var dateRanges = _.zip(_.range(1849, 2015, 5), _.range(1853, 2019, 5));
            var rolledData =
                d3.nest()
                    .key(function(d) {
                        var years = _.find(dateRanges, function(range) {
                            return range[0] <= d.yearbuilt && d.yearbuilt <= range[1];
                        });
                        return years[0]+'-'+years[1];
                    })
                    .rollup(function(d) {
                        return {
                            totalsqft: d3.mean(d, function(e) { return e.sqfeet; }),
                            avgeui: d3.mean(d, function(e) { return e.eui; }),
                            totalemissions: d3.mean(d, function(e) { return e.emissions; }),
                            avgenergystar: d3.mean(d, function(e) { return e.energystar; }),
                            count: d3.sum(d, function() { return 1; }),
                            totalenergy: d3.sum(d, function(e) { return e.eui * e.sqfeet; })
                        };
                    })
                    .entries(filteredData);
            return _.chain(rolledData)
                    .sortBy(function(d) { return d.key; })
                    .map(function(d) {
                        var obj = d.values;
                        obj.key = d.key;
                        obj.avgenergystar = isNaN(obj.avgenergystar) ? 0 : obj.avgenergystar;
                        return obj;
                    })
                    .reverse()
                    .value();
        }

        module.link = function ($scope, element, attrs) {
            $scope.configure(BarChartDefaults);
            var config = $scope.config;
            config.margin.left = 0;

            // D3 margin, sizing, and spacing code
            element.addClass(PLOT_CLASS);
            var chart = d3.select('#' + attrs.id + ' .chart')
                    .attr('width', config.plotWidth);

            // Overridden ChartingController method
            $scope.plot = function(data) {
                // The dimension of choice for representation along Y
                var yAttr = $scope.selectedY;
                // Chart height
                chart.attr('height', config.plotHeight);

                // Choose appropriate binning algorithm
                if (config.binType === 'temporal') {
                    data = binByYears(data);
                } else if (config.binType === 'area') {
                    data = binBySqFt(data, 62); // 62 is the magic number for generating a
                                                // logarithmically generated set of bins which
                                                // have  32 after pruning the empty head (30 removed)
                }

                // Axes and scales
                var x = d3.scale.ordinal()
                    .domain(data.map(function(d) { return d.key; }))
                    .rangeRoundBands([config.plotWidth-config.margin.left-config.margin.right, 0], 0.05);
                var y = d3.scale.linear()
                    .domain([0, d3.max(data, function(d) { return d[yAttr]; })])
                    .range([config.plotHeight-config.margin.bottom-config.margin.top, 0]);
                var bottomAxis = d3.svg.axis()
                    .orient('bottom')
                    .scale(x)
                    .tickSize(3,1)
                    .tickValues([]);
                chart.append('g')
                    .attr('class', 'x axis')
                    .attr('transform', 'translate(' +config.margin.left + ',' + (config.plotHeight - config.margin.bottom) + ')')
                    .call(bottomAxis);

                // Axes should have custom labels to prevent cluttering; tooltips can handle detail
                var labelStart = chart.append('text')
                    .attr('class', 'x startLabel')
                    .attr('text-anchor', 'start')
                    .attr('y', config.plotHeight-config.margin.top);
                var labelEnd = chart.append('text')
                    .attr('class', 'x endLabel')
                    .attr('text-anchor', 'end')
                    .attr('x', config.plotWidth-config.margin.right)
                    .attr('y', config.plotHeight-config.margin.top);
                // Depending on bin type, our axis labeling should look rather different
                if (config.binType === 'temporal') {
                    labelStart.text('1850');
                    labelEnd.text('2013');
                } else if (config.binType === 'area') {
                    labelStart.text(Math.round(data[data.length - 1].minsqft / 1000).toLocaleString() + 'k');
                    labelEnd.text(Math.round(data[0].maxsqft / 1000).toLocaleString() + 'k');
                }

                // Tooltips
                var tip = d3.tip()
                  .attr('class', 'd3-tip')
                  .offset([-10, 0])
                  .html(function(d) {
                    var dataLabel = d.key + (config.binType === 'area' ? ' Sq Ft' : '');
                    return '
' + dataLabel + '
' +
                           '
' + humanLabels[yAttr] + Math.round(d[yAttr]) + '
';
                  });
                chart.call(tip);

                // Bars
                chart.selectAll('bar')
                    .data(data)
                    .enter().append('rect')
                    .attr('transform', function(d) { return 'translate(' + (x(d.key) + config.margin.left).toString() + ',0)'; })
                    .attr('y', config.plotHeight - config.margin.bottom)
                    // Height of zero initially so it can be animated on load
                    .attr('height', 0)
                    .attr('width', x.rangeBand())
                    .attr('opacity', function(d, i) { return 1 - (i / data.length) * 0.3; })
                    .attr('rx', 3) // Rounding of corners
                    .attr('ry', 3)
                    .on('mouseover', tip.show)
                    .on('mouseout', tip.hide);

                // Perform the initial animation
                refreshData();

                // Animation functions
                function refreshScale() {
                    y.domain([0, d3.max(data, function(d) { return d[yAttr]; })]);
                }

                function refreshData() {
                    var transitionMillis = config.transitionMillis;
                    refreshScale();
                    chart.selectAll('rect')
                        .transition()
                        .ease('linear')
                        .duration(transitionMillis)
                        .attr('class', config.binType)
                        .attr('y', function(d) {
                            var val = isNaN(d[yAttr]) ? 0 : y(d[yAttr]);
                            return val - 10 + config.margin.top; })
                        .attr('height', function(d) {
                            var val = isNaN(d[yAttr]) ? 0 : y(d[yAttr]);
                            return config.plotHeight - val - config.margin.top - config.margin.bottom;
                        });
                }

                $scope.$watch('selectedY', function(newValue) {
                    yAttr = newValue;
                    refreshData();
                });
            };
        };

        return module;
    }

    angular.module('mos.charting')
      .constant('BarChartDefaults', BarChartDefaults)
      .directive('mosBarchart', barChart);

})();
<commit_msg>Fix tooltip text<commit_after>(function () {
    'use strict';

    // Define object config of properties for this chart directive
    var BarChartDefaults = {
        plotWidth: 800,
        plotHeight: 200,
        transitionMillis: 500,
        lazyLoad: true,
        binType: 'temporal'
    };

    /**
     * ngInject
     */
    function barChart (CartoConfig, BarChartDefaults) {

        var PLOT_CLASS = 'mos-barchart';

        // Begin directive module definition
        var module = {};

        module.restrict = 'EA';
        module.templateUrl = 'scripts/charting/barchart-partial.html';
        module.controller = 'ChartingController';

        module.scope = {
            data: '=',              // Required
            id: '@',                // Required
            plotWidth: '@',
            selectedY: '=',
            selectLabel: '=',
            selectUnit: '=',
            plotHeight: '@',
            margin: '&',
            binType: '@',
            lazyLoad: '@',
            transitionMillis: '@'
        };

        // This is a helper function for transforming api data into percentiles based on sqft
        function binBySqFt(data, groups) {
            data = _.filter(data, function(d) { return d.sqfeet > 0; });
            _.forEach(data, function(d) {
                d.log = Math.log10(d.sqfeet);
            });
            var maxLog = _.max(data, function(d) { return d.log; }).log;
            var binnedBySqFt = {};
            var binSize = maxLog / groups;
            var getBinSize =  function(d) {
                return binSize * i < d.log && (binSize * i) + binSize >= d.log;
            };
            for (var i = 0; i < groups; i++) {
                binnedBySqFt[i] = _.filter(data, getBinSize);
            }
            var output = [];
            _.forEach(binnedBySqFt, function(d, i) {
                // Naive count
                var kCount = d.length;
                // Special counts for fair avging
                var nEnergystar = _.filter(d, function(e) { return !isNaN(e.energystar) && e.energystar > 0; }).length;
                var nEUI = _.filter(d, function(e) { return !isNaN(e.eui) && e.eui > 0; }).length;
                // The bin boundaries
                var lowBound = Math.round(Math.pow(10, (i * binSize))).toLocaleString();
                var highBound = Math.round(Math.pow(10, (i * binSize) + binSize)).toLocaleString();
                // Min and max sq ft
                var minsqft = _.min(d, function(val) { return val.sqfeet; }).sqfeet;
                var maxsqft = _.max(d, function(val) { return val.sqfeet; }).sqfeet;
                output = output.concat({
                    totalemissions: _.reduce(d, function(memo, val) { return val.emissions + memo; }, 0),
                    avgenergystar: nEnergystar > 0 ? _.reduce(d, function(memo, val) { return val.energystar + memo; }, 0) / nEnergystar : 0,
                    avgeui: nEUI > 0 ? _.reduce(d, function(memo, val) { return val.eui + memo; }, 0) / nEUI : 0,
                    minsqft: minsqft ? minsqft : 0,
                    maxsqft: maxsqft ? maxsqft : 0,
                    avgsqft: _.reduce(d, function(memo, val) { return val.sqfeet + memo; }, 0) / kCount,
                    totalsqft: _.reduce(d, function(memo, val) { return val.sqfeet + memo; }, 0),
                    totalenergy: _.reduce(d, function(memo, val) { return (val.sqfeet * val.eui) + memo; }, 0),
                    count: kCount,
                    key: lowBound + '-' + highBound,
                    yearRange: _.min(d, function(d) { return d.yearbuilt; }).yearbuilt +
                        '-' + _.max(d, function(d) { return d.yearbuilt; }).yearbuilt
                });
            });
            return _.rest(output, function(d) { return d.count === 0; }).reverse(); // Prune empty head
        }

        // Helper function for transforming unshaped data into data binned and aggregated over 5 year periods
        function binByYears(data) {
            var filteredData = _.filter(data, function(d) { return d.yearbuilt > 1849; });
            var dateRanges = _.zip(_.range(1849, 2015, 5), _.range(1853, 2019, 5));
            var rolledData =
                d3.nest()
                    .key(function(d) {
                        var years = _.find(dateRanges, function(range) {
                            return range[0] <= d.yearbuilt && d.yearbuilt <= range[1];
                        });
                        return years[0]+'-'+years[1];
                    })
                    .rollup(function(d) {
                        return {
                            totalsqft: d3.mean(d, function(e) { return e.sqfeet; }),
                            avgeui: d3.mean(d, function(e) { return e.eui; }),
                            totalemissions: d3.mean(d, function(e) { return e.emissions; }),
                            avgenergystar: d3.mean(d, function(e) { return e.energystar; }),
                            count: d3.sum(d, function() { return 1; }),
                            totalenergy: d3.sum(d, function(e) { return e.eui * e.sqfeet; })
                        };
                    })
                    .entries(filteredData);
            return _.chain(rolledData)
                    .sortBy(function(d) { return d.key; })
                    .map(function(d) {
                        var obj = d.values;
                        obj.key = d.key;
                        obj.avgenergystar = isNaN(obj.avgenergystar) ? 0 : obj.avgenergystar;
                        return obj;
                    })
                    .reverse()
                    .value();
        }

        module.link = function ($scope, element, attrs) {
            $scope.configure(BarChartDefaults);
            var config = $scope.config;
            config.margin.left = 0;

            // D3 margin, sizing, and spacing code
            element.addClass(PLOT_CLASS);
            var chart = d3.select('#' + attrs.id + ' .chart')
                    .attr('width', config.plotWidth);

            // Overridden ChartingController method
            $scope.plot = function(data) {
                // The dimension of choice for representation along Y
                var yAttr = $scope.selectedY;
                // Chart height
                chart.attr('height', config.plotHeight);

                // Choose appropriate binning algorithm
                if (config.binType === 'temporal') {
                    data = binByYears(data);
                } else if (config.binType === 'area') {
                    data = binBySqFt(data, 62); // 62 is the magic number for generating a
                                                // logarithmically generated set of bins which
                                                // have  32 after pruning the empty head (30 removed)
                }

                // Axes and scales
                var x = d3.scale.ordinal()
                    .domain(data.map(function(d) { return d.key; }))
                    .rangeRoundBands([config.plotWidth-config.margin.left-config.margin.right, 0], 0.05);
                var y = d3.scale.linear()
                    .domain([0, d3.max(data, function(d) { return d[yAttr]; })])
                    .range([config.plotHeight-config.margin.bottom-config.margin.top, 0]);
                var bottomAxis = d3.svg.axis()
                    .orient('bottom')
                    .scale(x)
                    .tickSize(3,1)
                    .tickValues([]);
                chart.append('g')
                    .attr('class', 'x axis')
                    .attr('transform', 'translate(' +config.margin.left + ',' + (config.plotHeight - config.margin.bottom) + ')')
                    .call(bottomAxis);

                // Axes should have custom labels to prevent cluttering; tooltips can handle detail
                var labelStart = chart.append('text')
                    .attr('class', 'x startLabel')
                    .attr('text-anchor', 'start')
                    .attr('y', config.plotHeight-config.margin.top);
                var labelEnd = chart.append('text')
                    .attr('class', 'x endLabel')
                    .attr('text-anchor', 'end')
                    .attr('x', config.plotWidth-config.margin.right)
                    .attr('y', config.plotHeight-config.margin.top);
                // Depending on bin type, our axis labeling should look rather different
                if (config.binType === 'temporal') {
                    labelStart.text('1850');
                    labelEnd.text('2013');
                } else if (config.binType === 'area') {
                    labelStart.text(Math.round(data[data.length - 1].minsqft / 1000).toLocaleString() + 'k');
                    labelEnd.text(Math.round(data[0].maxsqft / 1000).toLocaleString() + 'k');
                }

                // Tooltips
                var tip = d3.tip()
                  .attr('class', 'd3-tip')
                  .offset([-10, 0])
                  .html(function(d) {
                    var dataLabel = d.key + (config.binType === 'area' ? ' Sq Ft' : '');
                    return '
' + dataLabel + '
' +
                           '
' + $scope.selectLabel + ': ' + Math.round(d[yAttr]) + ' ' + $scope.selectUnit + '
';
                  });
                chart.call(tip);

                // Bars
                chart.selectAll('bar')
                    .data(data)
                    .enter().append('rect')
                    .attr('transform', function(d) { return 'translate(' + (x(d.key) + config.margin.left).toString() + ',0)'; })
                    .attr('y', config.plotHeight - config.margin.bottom)
                    // Height of zero initially so it can be animated on load
                    .attr('height', 0)
                    .attr('width', x.rangeBand())
                    .attr('opacity', function(d, i) { return 1 - (i / data.length) * 0.3; })
                    .attr('rx', 3) // Rounding of corners
                    .attr('ry', 3)
                    .on('mouseover', tip.show)
                    .on('mouseout', tip.hide);

                // Perform the initial animation
                refreshData();

                // Animation functions
                function refreshScale() {
                    y.domain([0, d3.max(data, function(d) { return d[yAttr]; })]);
                }

                function refreshData() {
                    var transitionMillis = config.transitionMillis;
                    refreshScale();
                    chart.selectAll('rect')
                        .transition()
                        .ease('linear')
                        .duration(transitionMillis)
                        .attr('class', config.binType)
                        .attr('y', function(d) {
                            var val = isNaN(d[yAttr]) ? 0 : y(d[yAttr]);
                            return val - 10 + config.margin.top; })
                        .attr('height', function(d) {
                            var val = isNaN(d[yAttr]) ? 0 : y(d[yAttr]);
                            return config.plotHeight - val - config.margin.top - config.margin.bottom;
                        });
                }

                $scope.$watch('selectedY', function(newValue) {
                    yAttr = newValue;
                    refreshData();
                });
            };
        };

        return module;
    }

    angular.module('mos.charting')
      .constant('BarChartDefaults', BarChartDefaults)
      .directive('mosBarchart', barChart);

})();
<|endoftext|>


<commit_before>var App = {};

var server = '/';

$(function() {

	var Player = Backbone.Model.extend({
		idAttribute: 'id_player',
		urlRoot: server + 'player',
		party: function() {
			return CreatureCollection.filter({team: 1});
		},
		creatures: function() {
			return this._creatures;
		}
	});

	var Creature = Backbone.Model.extend({
		idAttribute: 'id_creature',
		localStorage: new Backbone.LocalStorage('Creature')
	});

	var CreatureCollection = Backbone.Collection.extend({
		model: Creature,
		localStorage: new Backbone.LocalStorage('CreatureCollection')
	});

	var Event = Backbone.Model.extend({
		idAttribute: 'id_event',
		urlRoot: server + 'event',
		zones: function() {
			if (!this.__zones) {
				this.__zones = [];
				for (x in this.attributes._zones) {
					this.__zones[this.__zones.length] = new Zone(this.attributes._zones[x]);
				}
			}
			return this.__zones;
		}
	});

	var EventCollection = Backbone.Collection.extend({
		model: Event
	});

	var Zone = Backbone.Model.extend({
		idAttribute: 'id_zone',
		urlRoot: server + 'zone',
		quests: function() {
			if (!this.__quests) {
				this.__quests = [];
				for (x in this.attributes._quests) {
					this.__quests[this.__quests.length] = new Quest(this.attributes._quests[x]);
				}
			}
			return this.__quests;
		}
	});

	var ZoneCollection = Backbone.Collection.extend({
		model: Zone
	});

	var Quest = Backbone.Model.extend({
		idAttribute: 'id_quest',
		urlRoot: server + 'quest',
		progress: function() {
			var self = this;
			$.getJSON(server + 'quest/' + this.attributes.id_quest + '/progress', function(res, status) {
				if (status == 'success') {
					var quest = res.quest;
					var loot = res.loot;
					for (var x in quest) {
						self.attributes[x] = quest[x];
					}
					self._view.loot = loot;
					self.trigger('change');
				}
			});
		}
	});

	var MainView = Backbone.View.extend({
		id: 'mainView',
		el: $('body'),
		subviews:null,
		subviewsNavigator:null,

		render:function () {
			var html = this.template();
			this.el.innerHTML = html;

			App.navigator = new BackStack.StackNavigator({
				el: '#wrapper'
			});
			App.navigator.defaultPushTransition = new BackStack.NoEffect();

			Backbone.history.start({pushState: true});

		},
		initialize: function (args) {
			this.template = _.template($('#root-template').html());
		}
	});

	var ErrorView = Backbone.View.extend({
		el: $('body'),
		render:function (er) {
			this.el.innerHTML  = this.template({
				error: er
			});
		},
		initialize: function (args) {
			this.template = _.template($('#error-template').html());
		}
	});

	var UpdateView = Backbone.View.extend({
		className: 'update content',
		el: $('body'),
		render : function() {
			this.el.innerHTML = this.template();

			var $topLoader = $('#updateLoader').percentageLoader({
				width: 256,
				height: 256,
				controllable: true,
				progress: 0,
				onProgressUpdate: function(val) {
					$topLoader.setValue(Math.round(val * 100.0));
				}
			});

			this.loader = $topLoader;

		},
		initialize: function (args) {
			this.template = _.template($('#update-template').html());
		}
	});

	var NewsView = Backbone.View.extend({
		className: 'news content',
		model: 'Event',
		render : function() {
			var html = this.template({
				player: App.player,
				events: App.events
			});
			this.$el.html(html);
		},
		events: {
			'click .event-link': 'eventClick'
		},
		eventClick: function(e) {
			var event = App.events.get(
				$(e.toElement).attr('data-id_event')
			);
			App.router.navigate('/event/' + event.attributes.id_event, {trigger: true});
		},
		initialize: function (args) {
			this.template = _.template($('#news-template').html());
			//_.bindAll(this, 'changeName');
			//this.model.bind('change:name', this.changeName);
		}
	});

	var CreaturesView = Backbone.View.extend({
		tagName : 'div',
		className: 'creatures content',
		render : function() {
			var html = this.template({
				player: App.player
			});
			this.el.innerHTML = html;
		},
		initialize: function (args) {
			this.template = _.template($('#creatures-template').html());
			//_.bindAll(this, 'changeName');
			//this.model.bind('change:name', this.changeName);
		}
	});

	var SettingsView = Backbone.View.extend({
		tagName : 'div',
		className: 'settings content',
		render : function() {
			this.el.innerHTML = this.template();
		},
		initialize: function (args) {
			this.template = _.template($('#settings-template').html());
			//_.bindAll(this, 'changeName');
			//this.model.bind('change:name', this.changeName);
		}
	});

	var StoryView = Backbone.View.extend({
		tagName : 'div',
		className: 'story content',
		render : function() {
			this.el.innerHTML = this.template({
				events: App.events
			});
		},
		events: {
			'click .event-link': 'eventClick'
		},
		eventClick: function(e) {
			var event = App.events.get(
				$(e.toElement).attr('data-id_event')
			);
			App.router.navigate('/event/' + event.attributes.id_event, {trigger: true});
		},
		initialize: function (args) {
			this.template = _.template($('#story-template').html());
			//_.bindAll(this, 'changeName');
			//this.model.bind('change:name', this.changeName);
		}
	});

	var EventView = Backbone.View.extend({
		tagName : 'div',
		className: 'events content',
		model: 'Event',
		render : function() {
			if (!this.model.attributes.name) {
				return;
			}
			var html = this.template({
				event: this.model,
			});
			this.el.innerHTML = html;
		},
		events: {
			'click .zone-link': 'zoneClick'
		},
		zoneClick: function(e) {
			App.router.navigate('/zone/' + $(e.toElement).attr('data-id_zone'), {trigger: true});
		},
		modelChanged: function() {
			this._view.render();
		},
		initialize: function (args) {
			this.template = _.template($('#event-template').html());
			this.model.bind('change', this.modelChanged);
		}
	});

	var ZoneView = Backbone.View.extend({
		className: 'zones content',
		model: 'Zone',
		render : function() {
			if (!this.model.attributes.name) {
				return;
			}
			var html = this.template({
				zone: this.model
			});
			this.el.innerHTML = html;
		},
		events: {
			'click .quest-link': 'questClick'
		},
		questClick: function(e) {
			App.router.navigate('/quest/' + $(e.toElement).attr('data-id_quest'), {trigger: true});
		},
		modelChanged: function() {
			this._view.render();
		},
		initialize: function (args) {
			this.template = _.template($('#zone-template').html());
			this.model.bind('change', this.modelChanged);
		}
	});

	var QuestView = Backbone.View.extend({
		className: 'quests content',
		model: 'Quest',
		loot: [],
		render : function() {
			if (!this.model.attributes.name) {
				return;
			}

			var html = this.template({
				quest: this.model,
				loot: this.loot
			});
			this.el.innerHTML = html;
		},
		events: {
			'click .quest-progress': 'questProgressClick'
		},
		questProgressClick: function(e) {
			this.model.progress();
		},
		modelChanged: function() {
			this._view.render();
		},
		initialize: function (args) {
			this.template = _.template($('#quest-template').html());
			this.model.bind('change', this.modelChanged);
		}
	});

	var HomeView = Backbone.View.extend({
		className: 'home content',
		render : function() {
			this.el.innerHTML = this.template();
		},
		initialize: function (args) {
			this.template = _.template($('#home-template').html());
			//_.bindAll(this, 'changeName');
			//this.model.bind('change:name', this.changeName);
		}
	});

	var PlayerView = Backbone.View.extend({
		className: 'player content',
		render: function() {
			// prevents triggering model changes prior to stack push
			if (!this.model.attributes.name) {
				return;
			}
			var html = this.template({
				player: this.model
			});
			this.el.innerHTML = html;
		},
		modelChanged: function() {
			this._view.render();
		},
		initialize: function (args) {
			this.template = _.template($('#player-template').html());
			this.model.bind('change', this.modelChanged);
		}
	});

	var AppRouter = Backbone.Router.extend({
		routes: {
			'event/:id': 'getEvent',
			'player/:id': 'getPlayer',
			'player': 'getPlayer',
			'quest/:id': 'getQuest',
			'zone/:id': 'getZone',
			'me': 'getPlayer',
			'settings': 'getSettings',
			'creatures': 'getCreatures',
			'game': 'getStory',
			'news': 'defaultRoute',
			'home': 'getHome',
			'items': 'getItems',
			'forceupdate': 'getUpdate',
			'*actions': 'defaultRoute',
		}
	});

	App.router = new AppRouter;

	App.router.on('route:getItems', function(actions) {

		var zone = new Zone({id_zone: actions});
		var itemsView = new ItemsView({
			model: zone
		});
		zone._view = zoneView;
		zone.fetch();

		App.navigator.pushView(zoneView);
	});

	App.router.on('route:getZone', function(actions) {

		var zone = new Zone({id_zone: actions});
		var zoneView = new ZoneView({
			model: zone
		});
		zone._view = zoneView;
		zone.fetch();

		App.navigator.pushView(zoneView);
	});

	App.router.on('route:getQuest', function(actions) {

		var quest = new Quest({id_quest: actions});
		var questView = new QuestView({
			model: quest
		});
		quest._view = questView;
		quest.fetch();

		App.navigator.pushView(questView);
	});

	App.router.on('route:defaultRoute', function(actions) {
		App.navigator.pushView(NewsView);
	});

	App.router.on('route:getUpdate', function(actions) {
		localStorage.clear();
		$.getJSON(server + 'start', function(res, status) {
			if (status == 'success') {
				AppUpdate(res);
			}
		});
	});

	App.router.on('route:getSettings', function(actions) {
		App.navigator.pushView(SettingsView);
	});

	App.router.on('route:getCreatures', function(actions) {
		App.navigator.pushView(CreaturesView);
	});

	App.router.on('route:getStory', function(actions) {
		App.navigator.pushView(StoryView);
	});

	App.router.on('route:getHome', function(actions) {
		App.navigator.pushView(HomeView);
	});

	App.router.on('route:getEvent', function(actions) {
		var event = new Event({id_event: actions});
		var eventView = new EventView({
			model: event
		});
		event._view = eventView;
		eventView.model.fetch();

		App.navigator.pushView(eventView);
	});

	App.router.on('route:getPlayer', function(actions) {
		if (actions) {
			var player = new Player({id_player: actions});
			var playerView = new PlayerView({
				model: player
			});
			player._view = playerView;
			playerView.model.fetch();
		} else {
			var playerView = new PlayerView({
				model: App.player
			});
		}
		App.navigator.pushView(playerView);
	});

	var AppInit = function(res) {
		localStorage.setItem('version', res.version);

		var player = res.player;
		var creatures = res.player.creatures;
		delete player.creatures;

		for (var x in creatures) {
			var creature = new Creature(creatures[x].creature);
			creature.save();
		}

		App.player = new Player(player);
		App.events = new EventCollection(res.events);
		App.zones = new ZoneCollection();

		var main = new MainView;
		main.render();
	};

	var AppUpdate = function(res) {
		AppInit(res);
	};

	$.getJSON(server + 'config', function(res, status) {
		if (status == 'success') {
			var currentVersion = localStorage.getItem('version');
			if (currentVersion != res.version) {
				// need to update
				AppUpdate(res);
			} else {
				AppInit(res);
			}
		}
	}).error(function() {
		var main = new ErrorView;
		main.render('The server is currently down. Sorry!');
	})
});
<commit_msg>unused<commit_after><|endoftext|>


<commit_before>var anim = (function(){
    var content     = $('#test-content');
    var dims         = getViewportDimensions();
    var height       = dims.height;
    var width        = dims.width;
    var radius       = width / 4;
    var canvas_elem  = document.querySelector('canvas');
    var color;
    var complements;

    function create(top, left) {
        var points = [];
        var zigzag;
        var x = width;
        var y = height;
        doSomethingABunch(function(){
            points.push(randomPolygonCoords(x, y));
            x = x / 1.2;
            y = y / 1.2;
        }, clamp(rando(20), 2, 20));
        zigzag = new fabric.Polyline(points, {
            left: rando(width),
            top: rando(height),
            selectable: false,
            opacity: 0,
            fill: randomArrayValue(complements).toHexString()
        });
        animate(zigzag, 400);
        doSomethingABunch(function(){
            canvas.add(new fabric.Circle({
                radius: radius,
                left: width / 2,
                top: height / 2,
                fill: randomArrayValue(complements).toHexString()
            }));
        });
        canvas.add(zigzag);
    }

    function animate(el, speed) {
        el.animate({
            opacity: clamp(rando(8), 2, 8) * 0.1,
            left: rando(width),
            top: rando(height)
        }, {
            onChange: canvas.renderAll.bind(canvas),
            duration: speed,
            easing: fabric.util.ease.easeIn,
        });
    }

    function randomizeCoord(coord) {
        return rando(width / 50);
    }

    function generate() {
        var left = rando(width);
        var top = rando(height);
        create(left, top);
    }

    function generateAll(event) {
        var counter    = 0;
        var increments = Math.floor(width / 80);
        var left       = width / 2;
        var top        = height / 2;
        color          = randomColor(clamp(255, 200, 255));
        complements    = tinycolor.analogous(color, increments);
        canvas.clear();
        if(event) {
            event.stopImmediatePropagation();
            event.preventDefault();
            color = $(this).data('color');
        }
        canvas.backgroundColor = color;
        doSomethingABunch(function(){
            setTimeout(generate, counter * 100);
            counter += 1;
        }, increments);
        canvas.renderAll();
    }

    function init() {
        bootstrapCanvas(null, true);
        addCanvasUI(generateAll);
        generateAll();
        content.find('a').on('mouseover', generateAll);
    }

    return {
        init: init
    };

})();

$(document).ready(anim.init);
<commit_msg>Remove unused code<commit_after>var anim = (function(){
    var content     = $('#test-content');
    var dims         = getViewportDimensions();
    var height       = dims.height;
    var width        = dims.width;
    var radius       = width / 4;
    var canvas_elem  = document.querySelector('canvas');
    var color;
    var complements;

    function create(top, left) {
        var points = [];
        var zigzag;
        var x = width;
        var y = height;
        doSomethingABunch(function(){
            points.push(randomPolygonCoords(x, y));
            x = x / 1.2;
            y = y / 1.2;
        }, clamp(rando(20), 2, 20));
        zigzag = new fabric.Polyline(points, {
            left: rando(width),
            top: rando(height),
            selectable: false,
            opacity: 0,
            fill: randomArrayValue(complements).toHexString()
        });
        animate(zigzag, 400);
        canvas.add(zigzag);
    }

    function animate(el, speed) {
        el.animate({
            opacity: clamp(rando(8), 2, 8) * 0.1,
            left: rando(width),
            top: rando(height)
        }, {
            onChange: canvas.renderAll.bind(canvas),
            duration: speed,
            easing: fabric.util.ease.easeIn,
        });
    }

    function randomizeCoord(coord) {
        return rando(width / 50);
    }

    function generate() {
        var left = rando(width);
        var top = rando(height);
        create(left, top);
    }

    function generateAll(event) {
        var counter    = 0;
        var increments = Math.floor(width / 80);
        var left       = width / 2;
        var top        = height / 2;
        color          = randomColor(clamp(255, 200, 255));
        complements    = tinycolor.analogous(color, increments);
        canvas.clear();
        if(event) {
            event.stopImmediatePropagation();
            event.preventDefault();
            color = $(this).data('color');
        }
        canvas.backgroundColor = color;
        doSomethingABunch(function(){
            setTimeout(generate, counter * 100);
            counter += 1;
        }, increments);
        canvas.renderAll();
    }

    function init() {
        bootstrapCanvas(null, true);
        addCanvasUI(generateAll);
        generateAll();
        content.find('a').on('mouseover', generateAll);
    }

    return {
        init: init
    };

})();

$(document).ready(anim.init);
<|endoftext|>


<commit_before>'use strict';

describe('charm token', function() {
  var charm_container, Y;

  before(function(done) {
    Y = YUI(GlobalConfig).use(
        ['browser-charm-token', 'node-event-simulate'], function(Y) {
          done();
        });
  });

  beforeEach(function() {
    charm_container = Y.Node.create('

');
    Y.one(document.body).prepend(charm_container);
  });

  afterEach(function() {
    Y.one('#charm-container').remove(true);
  });

  it('exists', function() {
    var charm = new Y.juju.widgets.browser.CharmToken();
    assert.isObject(charm);
  });

  it('renders with the right metadata', function() {
    var cfg = {
      name: 'some-charm',
      description: 'some description',
      recent_commits: 1,
      recent_downloads: 1,
      tested_providers: ['ec2']
    };
    var charm = new Y.juju.widgets.browser.CharmToken(cfg);
    charm.render(charm_container);

    var rendered_charm = Y.one('.yui3-charmtoken'),
        downloads = rendered_charm.one('.downloads')._node.childNodes[2],
        commits = rendered_charm.one('.commits')._node.childNodes[2];
    assert.equal('some-charm', rendered_charm.one('.title a').get('text'));
    assert.equal(
        'some description',
        rendered_charm.one('.description').get('text'));
    downloads.wholeText.replace(/\s+/g, '');
    assert.equal(
        '1', downloads.wholeText.replace(/\s+/g, ''),
        'Wrong number of downloads.');
    assert.equal(
        '1', commits.wholeText.replace(/\s+/g, ''),
        'Wrong number of commits.');
    var actual_url = rendered_charm.one('.providers').one('img').get('src');
    assert.equal(
        actual_url.split('/').slice(3).join('/'),
        'juju-ui/assets/svgs/provider-ec2.svg');
  });
});
<commit_msg>New tests.<commit_after>'use strict';

describe('charm token', function() {
  var charm_container, Y;

  before(function(done) {
    Y = YUI(GlobalConfig).use(
        ['browser-charm-token', 'node-event-simulate'], function(Y) {
          done();
        });
  });

  beforeEach(function() {
    charm_container = Y.Node.create('

');
    Y.one(document.body).prepend(charm_container);
  });

  afterEach(function() {
    Y.one('#charm-container').remove(true);
  });

  it('exists', function() {
    var charm = new Y.juju.widgets.browser.CharmToken();
    assert.isObject(charm);
  });

  it('has the right metadata', function() {
    var cfg = {
      name: 'some-charm',
      description: 'some description',
      recent_commits: 1,
      recent_downloads: 1,
      tested_providers: ['ec2']
    };
    var charm = new Y.juju.widgets.browser.CharmToken(cfg),
        expected = cfg;
    expected.tested_providers = [{name: 'ec2'}];
    assert.equal(expected, charm._getTemplateAttrs(charm.getAttrs()));

  });

  it('renders with provider icons', function() {
    var cfg = {
      name: 'some-charm',
      description: 'some description',
      recent_commits: 1,
      recent_downloads: 1,
      tested_providers: ['ec2']
    };
    var charm = new Y.juju.widgets.browser.CharmToken(cfg);
    charm.render(charm_container);
    var actual_url = Y.one(
        '.yui3-charmtoken').one('.providers').one('img').get('src');
    assert.equal(
        actual_url.split('/').slice(3).join('/'),
        'juju-ui/assets/svgs/provider-ec2.svg');
  });
});
<|endoftext|>


<commit_before>function toHTML(value, options = {}) {
  const { parenthesis = false, ascending = false } = options;
  value = JSON.parse(JSON.stringify(value));
  if (value && value.peak) {
    if (ascending) {
      value.peak.sort((a, b) => a.wavelength - b.wavelength);
    } else {
      value.peak.sort((a, b) => b.wavelength - a.wavelength);
    }
  }
  if (parenthesis) return format2(value);
  return format1(value);
}

// IR (cm-1): 1955w, 1881w, 1807w, 1614m, 1500S, 1454m
function format1(value) {
  var acsString = '';
  if (value && value.peak) {
    acsString += 'IR (cm-1): ';
    acsString += value.peak
      .map((a) => Math.round(a.wavelength) + (a.kind ? `<i>${a.kind}</i>` : ''))
      .join(', ');
  }
  return acsString;
}

// IR (νmax, cm-1) 2929 (w), 3521 (w), 3016 (w), 3065 (w), 2853 (w), 1766S, 1495 (w),
function format2(value) {
  var acsString = '';
  if (value && value.peak) {
    acsString += 'IR (νmax, cm-1) ';
    acsString += value.peak
      .map(
        (a) =>
          Math.round(a.wavelength) +
          (a.kind ? ` (${a.kind.toLowerCase()})` : '')
      )
      .join(', ');
  }
  return acsString;
}

module.exports = toHTML;
<commit_msg>fix: use ir wavenumber and not wavelength<commit_after>function toHTML(value, options = {}) {
  const { parenthesis = false, ascending = false } = options;
  value = JSON.parse(JSON.stringify(value));
  if (value && value.peak) {
    value.peak.forEach((item) => {
      if (!item.wavenumber) item.wavenumber = item.wavelength;
    });
    if (ascending) {
      value.peak.sort((a, b) => a.wavenumber - b.wavelength);
    } else {
      value.peak.sort((a, b) => b.wavenumber - a.wavelength);
    }
  }
  if (parenthesis) return format2(value);
  return format1(value);
}

// IR (cm-1): 1955w, 1881w, 1807w, 1614m, 1500S, 1454m
function format1(value) {
  var acsString = '';
  if (value && value.peak) {
    acsString += 'IR (cm-1): ';
    acsString += value.peak
      .map((a) => Math.round(a.wavenumber) + (a.kind ? `<i>${a.kind}</i>` : ''))
      .join(', ');
  }
  return acsString;
}

// IR (νmax, cm-1) 2929 (w), 3521 (w), 3016 (w), 3065 (w), 2853 (w), 1766S, 1495 (w),
function format2(value) {
  var acsString = '';
  if (value && value.peak) {
    acsString += 'IR (νmax, cm-1) ';
    acsString += value.peak
      .map(
        (a) =>
          Math.round(a.wavenumber) +
          (a.kind ? ` (${a.kind.toLowerCase()})` : ''),
      )
      .join(', ');
  }
  return acsString;
}

module.exports = toHTML;
<|endoftext|>


<commit_before>var marty = require('marty')
var orderConstants = require('../constants/orderConstants.js');
console.log(orderConstants);

var orderSourceActionCreators = marty.createActionCreators({
  receiveOrders: orderConstants.RECEIVE_ORDERS(function(users) {
    this.dispatch(users);
  })
});

module.exports = orderSourceActionCreators;
<commit_msg>Fix non-bug typo<commit_after>var marty = require('marty')
var orderConstants = require('../constants/orderConstants.js');
console.log(orderConstants);

var orderSourceActionCreators = marty.createActionCreators({
  receiveOrders: orderConstants.RECEIVE_ORDERS(function(orders) {
    this.dispatch(orders);
  })
});

module.exports = orderSourceActionCreators;
<|endoftext|>


<commit_before>(function () {
  'use strict';
  /*jshint camelcase:false */
  angular
    .module('porttare.controllers')
    .controller('CartController', CartController);

  function CartController($auth,
                          $scope,
                          $state,
                          $translate,
                          $ionicPopup,
                          $ionicHistory,
                          $ionicLoading,
                          APP,
                          billingAddresses,
                          deliveryAddresses,
                          ModalService,
                          BillingAddressesService,
                          ProfileAddressesService,
                          CartService,
                          CustomerOrderDeliveryService) {
    var cartVm = this,
        performingPost = false;
    cartVm.total = 0;
    cartVm.showCheckoutModal = showCheckoutModal;
    cartVm.closeModal = closeModal;
    cartVm.runCheckout = runCheckout;
    cartVm.paymentMethods = APP.paymentMethods;
    cartVm.assignBillingAddress = assignBillingAddress;
    cartVm.assignAddress = assignAddress;
    cartVm.messages = {};
    cartVm.clearDeliveryTime = clearDeliveryTime;
    cartVm.updateOrderItem = updateOrderItem;
    cartVm.removeOrderItem = removeOrderItem;
    cartVm.chooseAnonBillingAddress = chooseAnonBillingAddress;
    cartVm.showCustomerOrderDelivery = showCustomerOrderDelivery;
    cartVm.submitCustomerOrderDelivery = submitCustomerOrderDelivery;
    cartVm.CustomerOrderDeliverySelect = CustomerOrderDeliverySelect;
    cartVm.checkoutFormAddBillingAddress = checkoutFormAddBillingAddress;
    cartVm.customerOrderDeliveryNewAddress = customerOrderDeliveryNewAddress;
    cartVm.editCustomerOrderDeliveryAddress = editCustomerOrderDeliveryAddress;
    cartVm.customerOrderDeliverySelectPickup = customerOrderDeliverySelectPickup;
    cartVm.currentCurrency = getCurrentCurrency();
    cartVm.checkoutForm = {
      forma_de_pago: 'efectivo' // only method supported ATM
    };
    cartVm.openEditModal = openEditModal;
    cartVm.slickSettings = {
      infinite: false,
      lazyLoad: 'progressive',
      responsive: [
        {
          breakpoint: 320,
          settings: {
            slidesToShow: 3
          }
        },
        {
          breakpoint: 480,
          settings: {
            slidesToShow: 4
          }
        },
        {
          breakpoint: 768,
          settings: {
            slidesToShow: 5
          }
        },
        {
          breakpoint: 992,
          settings: {
            slidesToShow: 6
          }
        }
      ]
    };

    init();

    function init() {
      cartVm.cart = $auth.user.customer_order;
      cartVm.total = calculateTotal();
      cartVm.billingAddresses = billingAddresses;
      cartVm.addresses = deliveryAddresses;
      cartVm.slickFlag = true; // https://github.com/devmark/angular-slick-carousel#slide-data
      getDeliveryMethods();
    }

    function saveNewBillingAddress(){
      if ($scope.billingAddressesVm.form.$valid) {
        BillingAddressesService.createBillingAddress(
          $scope.billingAddressesVm.billingAddress
        ).then(function success(resp){
          cartVm.billingAddresses.push(resp.customer_billing_address); //jshint ignore:line
          closeModal().then(showCheckoutModal);
        }, function(error){
          $scope.billingAddressesVm.messages = error.data.errors;
        });
      }
    }

    function saveNewAddress(){
      if (!performingPost && $scope.pfaVm.addressForm.$valid) {
        performingPost = true;
        ProfileAddressesService.createAddresses(
          $scope.pfaVm.addressFormData
        ).then(function(response){
          var newCustomerAddress = response.customer_address; //jshint ignore:line
          cartVm.addresses.push(newCustomerAddress);
          assignAddress(newCustomerAddress);
          submitCustomerOrderDelivery().then(clearCurrentOrderDelivery);
          closeModal().then(function (){
            performingPost = false;
          });
        }, function(error){
          $scope.pfaVm.messages = error.errors;
        });
      }
    }

    function updateAddress(){
      if ($scope.pfaVm.addressForm.$valid) {
        ProfileAddressesService.updateAddresses(
          $scope.pfaVm.addressFormData
        ).then(function (response){
          var newAddress = response.customer_address,
              oldAddress = cartVm.addresses.find(function (address){
            return address.id === newAddress.id;
          });
          angular.merge(oldAddress, newAddress);
          closeModal();
        }, function(error){
          $scope.pfaVm.messages = error.errors;
        });
      }
    }

    function showCheckoutModal() {
      if (needsToAddDeliveryAddress()) {
        customerOrderDeliveryNewAddress();
      } else {
        ModalService.showModal({
          parentScope: $scope,
          fromTemplateUrl: 'templates/cart/checkout.html'
        });
      }
    }

    function closeModal() {
      clearData();
      return ModalService.closeModal();
    }

    function clearData() {
      cartVm.checkoutForm = {};
      cartVm.messages = {};
      cartVm.updateErrors = {};
      cartVm.currentItem = null;
      $scope.billingAddressesVm = {};
      $scope.pfaVm = {};
    }

    function runCheckout() {
      $ionicLoading.show({
        template: '{{::("globals.loading"|translate)}}'
      });
      CartService.checkout(cartVm.checkoutForm)
        .then(function success(response) {
          nextViewIsRoot();
          $state.go('app.customerorders.show', {
            id: response.customer_order.id,
            customerOrder: response.customer_order
          }).then(function () {
              $auth.user.customer_order = null;
              $ionicPopup.alert({
                title: 'Alerta',
                template: '{{::("cart.successfullyOrder"|translate)}}'
              }).then(closeModal);
            });
        }, function error(res) {
          $ionicLoading.hide();
          if (res && res.errors) {
            cartVm.messages = res.errors;
          } else {
            var message = '{{::("globals.pleaseTryAgain"|translate)}}';
            $ionicPopup.alert({
              title: 'Error',
              template: message
            });
          }
        });
    }

    function assignBillingAddress(billingAddress) {
      selectItem(cartVm.billingAddresses, billingAddress);
      if (billingAddress) {
        cartVm.checkoutForm.anon_billing_address = null;
        cartVm.checkoutForm.customer_billing_address_id = billingAddress.id;
      }
    }

    function assignAddress(address) {
      cartVm.providerProfile.customer_order_delivery.delivery_method = 'shipping'; // jshint ignore:line
      cartVm.providerProfile.customer_order_delivery.customer_address_id = address.id;
      selectItem(cartVm.addresses, address);
    }

    function selectItem(items, item) {
      angular.forEach(items, function (elem) {
        elem.selected = false;
      });
      if (item) {
        item.selected = true;
      }
    }

    function calculateTotal() {
      var totalCents = 0,
        centValue = 0.01;

      if (cartVm.cart && cartVm.cart.provider_profiles) {
        angular.forEach(cartVm.cart.provider_profiles, function (provider) {
          totalCents = totalCents + getTotalValueItems(provider);
        });
      }
      return totalCents * centValue;
    }

    function getTotalValueItems(provider) {
      var total = 0;
      if(provider.customer_order_items) {
        angular.forEach(provider.customer_order_items, function (item) {
          total = total + (item.provider_item_precio_cents * item.cantidad);
        });
      }
      return total;
    }

    function clearDeliveryTime(){
      cartVm.providerProfile.customer_order_delivery.deliver_at = null;
    }

    function getDeliveryMethods(){
      var methodsKeys, translationMapping, translationKeys;
      methodsKeys = APP.deliveryMethods;
      translationMapping = methodsKeys.reduce(function (memo, method) {
        memo['cart.deliveryMethods.' + method] = method;
        return memo;
      }, {});
      translationKeys = Object.keys(translationMapping);
      $translate(translationKeys).then(function (translations) {
        var formattedMethods = translationKeys.map(function (translationKey) {
          return {
            value: translationMapping[translationKey],
            label: translations[translationKey]
          };
        });
        cartVm.formattedDeliveryMethods = formattedMethods;
      });
    }

    function openEditModal(item){
      cartVm.currentItem = angular.copy(item);
      // jshint ignore:start
      cartVm.counterOptions = {
        limit: 1,
        cantidad: cartVm.currentItem.cantidad,
        cartItem: cartVm.currentItem,
        providerItem: cartVm.currentItem.provider_item,
        priceCents: cartVm.currentItem.provider_item_precio_cents,
        currencyCode: cartVm.currentItem.provider_item_precio_currency,
        onChangeValue: function (data) {
          cartVm.currentItem.cantidad = data.itemsCount;
        }
      };
      // jshint ignore:end
      ModalService.showModal({
        parentScope: $scope,
        fromTemplateUrl: 'templates/cart/order-item.html',
        backdropClickToClose: true,
        focusFirstInput: false
      });
    }

    function updateOrderItem(){
      cartVm.slickFlag = false;
      CartService.updateOrderItem(cartVm.currentItem).then(function(response){
        cartVm.cart = response.customer_order; //jshint ignore:line
        cartVm.total = calculateTotal();
        cartVm.slickFlag = true;
        closeModal();
      }, function(errorResponse){
        cartVm.updateErrors = errorResponse.errors;
      });
    }

    function removeOrderItem(item){
      cartVm.slickFlag = false;
      CartService.removeOrderItem(item).then(function success(resp){
        $auth.user.customer_order=resp.data.customer_order;
        cartVm.cart=resp.data.customer_order;
        cartVm.total= calculateTotal();
        cartVm.slickFlag = true;
        $auth.user.customer_order = resp.data.customer_order; //jshint ignore:line
        $scope.$emit('update-number');
        closeModal();
        if( CartService.isCartEmpty(cartVm.cart) ){
          nextViewIsRoot();
          $state.go(APP.successState);
        }
      });
    }

    function CustomerOrderDeliverySelect(){
      angular.forEach(cartVm.addresses, function (elem) {
        if (elem.id==cartVm.providerProfile.customer_order_delivery.customer_address_id){ //jshint ignore:line
          selectItem(cartVm.addresses, elem);
          return;
        }
      });
    }

    function showCustomerOrderDelivery(providerProfile){
      cartVm.providerProfile = angular.copy(providerProfile);
      if (providerProfile.customer_order_delivery.deliver_at){ //jshint ignore:line
        cartVm.providerProfile.customer_order_delivery.deliver_at = new Date(providerProfile.customer_order_delivery.deliver_at); //jshint ignore:line
      }
      cartVm.CustomerOrderDeliverySelect();
      ModalService.showModal({
        parentScope: $scope,
        backdropClickToClose: true,
        fromTemplateUrl: 'templates/cart/customer-order-delivery-modal.html',
      });
    }

    function submitCustomerOrderDelivery() {
      $ionicLoading.show({
        template: '{{::("globals.updating"|translate)}}'
      });
      return CustomerOrderDeliveryService.updateCustomerOrderDelivery(
        cartVm.providerProfile.customer_order_delivery //jshint ignore:line
      ).then(function success(resp){
        $ionicLoading.hide().then(function(){
          $auth.user.customer_order = resp.customer_order; //jshint ignore:line
          init();
          closeModal();
        });
      }, function error(res) {
        $ionicLoading.hide();
        if (res && res.data.errors ) {
          cartVm.messages = res.data.errors;
        } else {
          var message = '{{::("globals.pleaseTryAgain"|translate)}}';
          $ionicPopup.alert({
            title: 'Error',
            template: message
          });
        }
      });
    }

    function customerOrderDeliverySelectPickup(){
      selectItem(cartVm.addresses);
      cartVm.providerProfile.customer_order_delivery.customer_address_id = null;
      cartVm.providerProfile.customer_order_delivery.delivery_method = 'pickup';
    }

    function customerOrderDeliveryNewAddress(){
      closeModal().then(function(){
        $scope.pfaVm = {
          closeModal: closeModal,
          processAddress: saveNewAddress,
          defaultInCurrentGeolocation: true
        };
        ModalService.showModal({
          parentScope: $scope,
          fromTemplateUrl: 'templates/profile/addresses/modal-form.html'
        });
      });
    }

    function nextViewIsRoot(){
      $ionicHistory.nextViewOptions({
        historyRoot: true
      });
    }

    function anyDeliveryIsShipping(){
      return cartVm.cart.provider_profiles.some(function (providerProfile){
        return providerProfile.customer_order_delivery.delivery_method === 'shipping'; // jshint ignore:line
      });
    }

    function needsToAddDeliveryAddress(){
      return cartVm.addresses.length === 0 && anyDeliveryIsShipping();
    }

    function chooseAnonBillingAddress(){
      cartVm.checkoutForm.anon_billing_address = true;
      assignBillingAddress(null);
    }

    function checkoutFormAddBillingAddress(){
      closeModal().then(function(){
        $scope.billingAddressesVm = {
          billingAddress: initBillingAddress(),
          closeModal: closeModal,
          submitModal: saveNewBillingAddress
        };
        ModalService.showModal({
          parentScope: $scope,
          fromTemplateUrl: 'templates/billing-addresses/new-edit.html'
        });
      });
    }

    function initBillingAddress() {
      var billingAddress = {};
      billingAddress.email = $auth.user.email;
      billingAddress.ciudad = $auth.user.ciudad;
      if ($auth.user.provider_profile){ // jshint ignore:line
        billingAddress.ruc = $auth.user.provider_profile.ruc;// jshint ignore:line
        billingAddress.razon_social = $auth.user.provider_profile.razon_social;// jshint ignore:line
        billingAddress.telefono = $auth.user.provider_profile.telefono;// jshint ignore:line
      }
      return billingAddress;
    }

    function clearCurrentOrderDelivery(){
      cartVm.providerProfile = null;
    }

    function editCustomerOrderDeliveryAddress(customerAddress) {
      closeModal().then(function(){
        $scope.pfaVm = {
          closeModal: closeModal,
          processAddress: updateAddress,
          addressFormData: angular.copy(customerAddress)
        };
        ModalService.showModal({
          parentScope: $scope,
          fromTemplateUrl: 'templates/profile/addresses/modal-form.html'
        });
      });
    }

    function getCurrentCurrency(){
      // jshint ignore:start
      if (cartVm.cart && cartVm.cart.subtotal_items_currency) {
        return cartVm.cart.subtotal_items_currency;
      } else {
        return $auth.user.current_place.currency_iso_code;
      }
      // jshint ignore:end
    }
  }
})();
<commit_msg>fixing case when there’s no delivery address<commit_after>(function () {
  'use strict';
  /*jshint camelcase:false */
  angular
    .module('porttare.controllers')
    .controller('CartController', CartController);

  function CartController($auth,
                          $scope,
                          $state,
                          $translate,
                          $ionicPopup,
                          $ionicHistory,
                          $ionicLoading,
                          APP,
                          billingAddresses,
                          deliveryAddresses,
                          ModalService,
                          BillingAddressesService,
                          ProfileAddressesService,
                          CartService,
                          CustomerOrderDeliveryService) {
    var cartVm = this,
        performingPost = false;
    cartVm.total = 0;
    cartVm.showCheckoutModal = showCheckoutModal;
    cartVm.closeModal = closeModal;
    cartVm.runCheckout = runCheckout;
    cartVm.paymentMethods = APP.paymentMethods;
    cartVm.assignBillingAddress = assignBillingAddress;
    cartVm.assignAddress = assignAddress;
    cartVm.messages = {};
    cartVm.clearDeliveryTime = clearDeliveryTime;
    cartVm.updateOrderItem = updateOrderItem;
    cartVm.removeOrderItem = removeOrderItem;
    cartVm.chooseAnonBillingAddress = chooseAnonBillingAddress;
    cartVm.showCustomerOrderDelivery = showCustomerOrderDelivery;
    cartVm.submitCustomerOrderDelivery = submitCustomerOrderDelivery;
    cartVm.CustomerOrderDeliverySelect = CustomerOrderDeliverySelect;
    cartVm.checkoutFormAddBillingAddress = checkoutFormAddBillingAddress;
    cartVm.customerOrderDeliveryNewAddress = customerOrderDeliveryNewAddress;
    cartVm.editCustomerOrderDeliveryAddress = editCustomerOrderDeliveryAddress;
    cartVm.customerOrderDeliverySelectPickup = customerOrderDeliverySelectPickup;
    cartVm.currentCurrency = getCurrentCurrency();
    cartVm.checkoutForm = {
      forma_de_pago: 'efectivo' // only method supported ATM
    };
    cartVm.openEditModal = openEditModal;
    cartVm.slickSettings = {
      infinite: false,
      lazyLoad: 'progressive',
      responsive: [
        {
          breakpoint: 320,
          settings: {
            slidesToShow: 3
          }
        },
        {
          breakpoint: 480,
          settings: {
            slidesToShow: 4
          }
        },
        {
          breakpoint: 768,
          settings: {
            slidesToShow: 5
          }
        },
        {
          breakpoint: 992,
          settings: {
            slidesToShow: 6
          }
        }
      ]
    };

    init();

    function init() {
      cartVm.cart = $auth.user.customer_order;
      cartVm.total = calculateTotal();
      cartVm.billingAddresses = billingAddresses;
      cartVm.addresses = deliveryAddresses;
      cartVm.slickFlag = true; // https://github.com/devmark/angular-slick-carousel#slide-data
      getDeliveryMethods();
    }

    function saveNewBillingAddress(){
      if ($scope.billingAddressesVm.form.$valid) {
        BillingAddressesService.createBillingAddress(
          $scope.billingAddressesVm.billingAddress
        ).then(function success(resp){
          cartVm.billingAddresses.push(resp.customer_billing_address); //jshint ignore:line
          closeModal().then(showCheckoutModal);
        }, function(error){
          $scope.billingAddressesVm.messages = error.data.errors;
        });
      }
    }

    function saveNewAddress(){
      if (!performingPost && $scope.pfaVm.addressForm.$valid) {
        performingPost = true;
        ProfileAddressesService.createAddresses(
          $scope.pfaVm.addressFormData
        ).then(function(response){
          var newCustomerAddress = response.customer_address; //jshint ignore:line
          cartVm.addresses.push(newCustomerAddress);
          assignAddress(newCustomerAddress);
          setAddressInEmptyDeliveries(newCustomerAddress);
          submitCustomerOrderDelivery().then(clearCurrentOrderDelivery);
          closeModal().then(function (){
            performingPost = false;
          });
        }, function(error){
          $scope.pfaVm.messages = error.errors;
        });
      }
    }

    function setAddressInEmptyDeliveries(newCustomerAddress){
      angular.forEach(
        cartVm.cart.provider_profiles,
        function (providerProfile){
          var delivery = providerProfile.customer_order_delivery,
              isShipping = delivery.delivery_method === 'shipping',
              withoutAddress = delivery.customer_address_id === null;
          if (isShipping && withoutAddress) {
            setAddressInProviderProfile(
              newCustomerAddress,
              providerProfile
            );
          }
        }
      );
    }

    function updateAddress(){
      if ($scope.pfaVm.addressForm.$valid) {
        ProfileAddressesService.updateAddresses(
          $scope.pfaVm.addressFormData
        ).then(function (response){
          var newAddress = response.customer_address,
              oldAddress = cartVm.addresses.find(function (address){
            return address.id === newAddress.id;
          });
          angular.merge(oldAddress, newAddress);
          closeModal();
        }, function(error){
          $scope.pfaVm.messages = error.errors;
        });
      }
    }

    function showCheckoutModal() {
      if (needsToAddDeliveryAddress()) {
        customerOrderDeliveryNewAddress();
      } else {
        ModalService.showModal({
          parentScope: $scope,
          fromTemplateUrl: 'templates/cart/checkout.html'
        });
      }
    }

    function closeModal() {
      clearData();
      return ModalService.closeModal();
    }

    function clearData() {
      cartVm.checkoutForm = {};
      cartVm.messages = {};
      cartVm.updateErrors = {};
      cartVm.currentItem = null;
      $scope.billingAddressesVm = {};
      $scope.pfaVm = {};
    }

    function runCheckout() {
      $ionicLoading.show({
        template: '{{::("globals.loading"|translate)}}'
      });
      CartService.checkout(cartVm.checkoutForm)
        .then(function success(response) {
          nextViewIsRoot();
          $state.go('app.customerorders.show', {
            id: response.customer_order.id,
            customerOrder: response.customer_order
          }).then(function () {
              $auth.user.customer_order = null;
              $ionicPopup.alert({
                title: 'Alerta',
                template: '{{::("cart.successfullyOrder"|translate)}}'
              }).then(closeModal);
            });
        }, function error(res) {
          $ionicLoading.hide();
          if (res && res.errors) {
            cartVm.messages = res.errors;
          } else {
            var message = '{{::("globals.pleaseTryAgain"|translate)}}';
            $ionicPopup.alert({
              title: 'Error',
              template: message
            });
          }
        });
    }

    function assignBillingAddress(billingAddress) {
      selectItem(cartVm.billingAddresses, billingAddress);
      if (billingAddress) {
        cartVm.checkoutForm.anon_billing_address = null;
        cartVm.checkoutForm.customer_billing_address_id = billingAddress.id;
      }
    }

    function assignAddress(address) {
      selectItem(cartVm.addresses, address);
      if (cartVm.providerProfile) {
        // AKA when editing
        setAddressInProviderProfile(
          address,
          cartVm.providerProfile
        );
      }
    }

    function setAddressInProviderProfile(address,
                                         providerProfile){
      providerProfile.customer_order_delivery.delivery_method = 'shipping'; // jshint ignore:line
      providerProfile.customer_order_delivery.customer_address_id = address.id;
    }

    function selectItem(items, item) {
      angular.forEach(items, function (elem) {
        elem.selected = false;
      });
      if (item) {
        item.selected = true;
      }
    }

    function calculateTotal() {
      var totalCents = 0,
        centValue = 0.01;

      if (cartVm.cart && cartVm.cart.provider_profiles) {
        angular.forEach(cartVm.cart.provider_profiles, function (provider) {
          totalCents = totalCents + getTotalValueItems(provider);
        });
      }
      return totalCents * centValue;
    }

    function getTotalValueItems(provider) {
      var total = 0;
      if(provider.customer_order_items) {
        angular.forEach(provider.customer_order_items, function (item) {
          total = total + (item.provider_item_precio_cents * item.cantidad);
        });
      }
      return total;
    }

    function clearDeliveryTime(){
      cartVm.providerProfile.customer_order_delivery.deliver_at = null;
    }

    function getDeliveryMethods(){
      var methodsKeys, translationMapping, translationKeys;
      methodsKeys = APP.deliveryMethods;
      translationMapping = methodsKeys.reduce(function (memo, method) {
        memo['cart.deliveryMethods.' + method] = method;
        return memo;
      }, {});
      translationKeys = Object.keys(translationMapping);
      $translate(translationKeys).then(function (translations) {
        var formattedMethods = translationKeys.map(function (translationKey) {
          return {
            value: translationMapping[translationKey],
            label: translations[translationKey]
          };
        });
        cartVm.formattedDeliveryMethods = formattedMethods;
      });
    }

    function openEditModal(item){
      cartVm.currentItem = angular.copy(item);
      // jshint ignore:start
      cartVm.counterOptions = {
        limit: 1,
        cantidad: cartVm.currentItem.cantidad,
        cartItem: cartVm.currentItem,
        providerItem: cartVm.currentItem.provider_item,
        priceCents: cartVm.currentItem.provider_item_precio_cents,
        currencyCode: cartVm.currentItem.provider_item_precio_currency,
        onChangeValue: function (data) {
          cartVm.currentItem.cantidad = data.itemsCount;
        }
      };
      // jshint ignore:end
      ModalService.showModal({
        parentScope: $scope,
        fromTemplateUrl: 'templates/cart/order-item.html',
        backdropClickToClose: true,
        focusFirstInput: false
      });
    }

    function updateOrderItem(){
      cartVm.slickFlag = false;
      CartService.updateOrderItem(cartVm.currentItem).then(function(response){
        cartVm.cart = response.customer_order; //jshint ignore:line
        cartVm.total = calculateTotal();
        cartVm.slickFlag = true;
        closeModal();
      }, function(errorResponse){
        cartVm.updateErrors = errorResponse.errors;
      });
    }

    function removeOrderItem(item){
      cartVm.slickFlag = false;
      CartService.removeOrderItem(item).then(function success(resp){
        $auth.user.customer_order=resp.data.customer_order;
        cartVm.cart=resp.data.customer_order;
        cartVm.total= calculateTotal();
        cartVm.slickFlag = true;
        $auth.user.customer_order = resp.data.customer_order; //jshint ignore:line
        $scope.$emit('update-number');
        closeModal();
        if( CartService.isCartEmpty(cartVm.cart) ){
          nextViewIsRoot();
          $state.go(APP.successState);
        }
      });
    }

    function CustomerOrderDeliverySelect(){
      angular.forEach(cartVm.addresses, function (elem) {
        if (elem.id==cartVm.providerProfile.customer_order_delivery.customer_address_id){ //jshint ignore:line
          selectItem(cartVm.addresses, elem);
          return;
        }
      });
    }

    function showCustomerOrderDelivery(providerProfile){
      cartVm.providerProfile = angular.copy(providerProfile);
      if (providerProfile.customer_order_delivery.deliver_at){ //jshint ignore:line
        cartVm.providerProfile.customer_order_delivery.deliver_at = new Date(providerProfile.customer_order_delivery.deliver_at); //jshint ignore:line
      }
      cartVm.CustomerOrderDeliverySelect();
      ModalService.showModal({
        parentScope: $scope,
        backdropClickToClose: true,
        fromTemplateUrl: 'templates/cart/customer-order-delivery-modal.html',
      });
    }

    function submitCustomerOrderDelivery() {
      $ionicLoading.show({
        template: '{{::("globals.updating"|translate)}}'
      });
      return CustomerOrderDeliveryService.updateCustomerOrderDelivery(
        cartVm.providerProfile.customer_order_delivery //jshint ignore:line
      ).then(function success(resp){
        $ionicLoading.hide().then(function(){
          $auth.user.customer_order = resp.customer_order; //jshint ignore:line
          init();
          closeModal();
        });
      }, function error(res) {
        $ionicLoading.hide();
        if (res && res.data.errors ) {
          cartVm.messages = res.data.errors;
        } else {
          var message = '{{::("globals.pleaseTryAgain"|translate)}}';
          $ionicPopup.alert({
            title: 'Error',
            template: message
          });
        }
      });
    }

    function customerOrderDeliverySelectPickup(){
      selectItem(cartVm.addresses);
      cartVm.providerProfile.customer_order_delivery.customer_address_id = null;
      cartVm.providerProfile.customer_order_delivery.delivery_method = 'pickup';
    }

    function customerOrderDeliveryNewAddress(){
      closeModal().then(function(){
        $scope.pfaVm = {
          closeModal: closeModal,
          processAddress: saveNewAddress,
          defaultInCurrentGeolocation: true
        };
        ModalService.showModal({
          parentScope: $scope,
          fromTemplateUrl: 'templates/profile/addresses/modal-form.html'
        });
      });
    }

    function nextViewIsRoot(){
      $ionicHistory.nextViewOptions({
        historyRoot: true
      });
    }

    function anyDeliveryIsShipping(){
      return cartVm.cart.provider_profiles.some(function (providerProfile){
        return providerProfile.customer_order_delivery.delivery_method === 'shipping'; // jshint ignore:line
      });
    }

    function needsToAddDeliveryAddress(){
      return cartVm.addresses.length === 0 && anyDeliveryIsShipping();
    }

    function chooseAnonBillingAddress(){
      cartVm.checkoutForm.anon_billing_address = true;
      assignBillingAddress(null);
    }

    function checkoutFormAddBillingAddress(){
      closeModal().then(function(){
        $scope.billingAddressesVm = {
          billingAddress: initBillingAddress(),
          closeModal: closeModal,
          submitModal: saveNewBillingAddress
        };
        ModalService.showModal({
          parentScope: $scope,
          fromTemplateUrl: 'templates/billing-addresses/new-edit.html'
        });
      });
    }

    function initBillingAddress() {
      var billingAddress = {};
      billingAddress.email = $auth.user.email;
      billingAddress.ciudad = $auth.user.ciudad;
      if ($auth.user.provider_profile){ // jshint ignore:line
        billingAddress.ruc = $auth.user.provider_profile.ruc;// jshint ignore:line
        billingAddress.razon_social = $auth.user.provider_profile.razon_social;// jshint ignore:line
        billingAddress.telefono = $auth.user.provider_profile.telefono;// jshint ignore:line
      }
      return billingAddress;
    }

    function clearCurrentOrderDelivery(){
      cartVm.providerProfile = null;
    }

    function editCustomerOrderDeliveryAddress(customerAddress) {
      closeModal().then(function(){
        $scope.pfaVm = {
          closeModal: closeModal,
          processAddress: updateAddress,
          addressFormData: angular.copy(customerAddress)
        };
        ModalService.showModal({
          parentScope: $scope,
          fromTemplateUrl: 'templates/profile/addresses/modal-form.html'
        });
      });
    }

    function getCurrentCurrency(){
      // jshint ignore:start
      if (cartVm.cart && cartVm.cart.subtotal_items_currency) {
        return cartVm.cart.subtotal_items_currency;
      } else {
        return $auth.user.current_place.currency_iso_code;
      }
      // jshint ignore:end
    }
  }
})();
<|endoftext|>


<commit_before><commit_msg>Fix #628: Authorization fails in firefox<commit_after><|endoftext|>


<commit_before>"use strict";

angular.module('openshiftConsole')
  .directive('buildTrendsChart', function($filter, $location, $rootScope, $timeout) {
    return {
      restrict: 'E',
      scope: {
        builds: '='
      },
      templateUrl: 'views/_build-trends-chart.html',
      link: function($scope) {
        var buildByNumber;
        var completePhases = ['Complete', 'Failed', 'Cancelled', 'Error'];

        // Minimum number of builds to show.
        $scope.minBuilds = _.constant(4);

        // Simple humanize function that returns an abbreviated duration like "1h 4m" or "2m 4s"
        // suitable for use on the chart y-axis.
        var humanize = function(value) {
          var result = [];
          var duration = moment.duration(value);
          var hours = Math.floor(duration.asHours());
          var minutes = duration.minutes();
          var seconds = duration.seconds();

          if (!hours && !minutes && !seconds) {
            return '';
          }

          if (hours) {
            result.push(hours + "h");
          }
          if (minutes) {
            result.push(minutes + "m");
          }

          // Only show seconds if not duration doesn't include hours.
          // Always show seconds otherwise (even 0s).
          if (!hours) {
            result.push(seconds + "s");
          }

          return result.join(" ");
        };

        var getStartTimestsamp = function(build) {
          return build.status.startTimestamp || build.metadata.creationTimestamp;
        };

        // Chart configuration, see http://c3js.org/reference.html
        $scope.chartID = _.uniqueId('build-trends-chart-');
        var animationDuration = _.constant(350);
        var config = {
          bindto: '#' + $scope.chartID,
          padding: {
            right: 30,
            // Prevent problems with the y-axis label getting cut off on updates.
            left: 80
          },
          axis: {
            x: {
              fit: true,
              label: {
                text: 'Build Number',
                position: 'outer-right'
              },
              tick: {
                culling: true,
                fit: true,
                format: function(x) {
                  return '#' + x;
                }
              }
            },
            y: {
              label: {
                text: 'Duration',
                position: 'outer-top'
              },
              min: 0,
              padding: {
                bottom: 0
              },
              tick: {
                count: 5,
                culling: true,
                fit: true,
                format: humanize
              }
            }
          },
          bar: {
            width: {
              max: 50
            }
          },
          legend: {
            item: {
              // Disable clicking to filter groups
              onclick: _.noop
            }
          },
          size: {
            height: 200
          },
          tooltip: {
            format: {
              title: function(x) {
                var build = buildByNumber[x];
                var startTimestamp = getStartTimestsamp(build);
                return '#' + x + ' (' + moment(startTimestamp).fromNow() + ')';
              }
            }
          },
          transition: {
            duration: animationDuration()
          },
          data: {
            // https://www.patternfly.org/styles/color-palette/
            colors: {
              Cancelled: "#d1d1d1",
              Complete: "#00b9e4",
              Error: "#393f44",
              Failed: "#cc0000"
            },
            empty: {
              label: {
                text: "No Completed Builds"
              }
            },
            onclick: function(d) {
              var build = buildByNumber[d.x];
              var url = $filter('navigateResourceURL')(build);
              if (url) {
                $rootScope.$apply(function() {
                  $location.path(url);
                });
              }
            },
            selection: {
              enabled: true
            },
            type: 'bar'
          }
        };

        var updateCompleteBuilds = function() {
          $scope.completeBuilds = [];
          var isIncomplete = $filter('isIncompleteBuild');
          angular.forEach($scope.builds, function(build) {
            if (!isIncomplete(build)) {
              $scope.completeBuilds.push(build);
            }
          });
        };

        var numCompleteBuilds = function() {
          updateCompleteBuilds();
          return $scope.completeBuilds.length;
        };

        var annotationFilter = $filter('annotation');
        var getBuildNumber = function(build) {
          var buildNumber = annotationFilter(build, 'buildNumber') || parseInt(build.metadata.name.match(/(\d+)$/), 10);
          if (isNaN(buildNumber)) {
            return null;
          }

          return buildNumber;
        };

        var getBuildDuration = function(build) {
          var startTimestamp = getStartTimestsamp(build);
          var endTimestamp = build.status.completionTimestamp;
          if (!startTimestamp || !endTimestamp) {
            return 0;
          }

          return moment(endTimestamp).diff(moment(startTimestamp));
        };

        var chart, averageDuration, showAverageLine = false;
        var updateAvgLine = function() {
          if (averageDuration && showAverageLine) {
            chart.ygrids([{
              value: averageDuration,
              'class': 'build-trends-avg-line'
            }]);
          } else {
            chart.ygrids.remove();
          }
        };

        $scope.toggleAvgLine = function() {
          showAverageLine = !showAverageLine;
          updateAvgLine();
        };

        var update = function() {
          // Keep a map of builds by number so we can find the build later when a data point is clicked.
          buildByNumber = {};
          var data = {
            json: [],
            keys: {
              x: 'buildNumber'
            }
          };

          var sum = 0, count = 0;
          angular.forEach($scope.completeBuilds, function(build) {
            var buildNumber = getBuildNumber(build);
            if (!buildNumber) {
              return;
            }

            var duration = getBuildDuration(build);

            // Track the sum and count to calculate the average duration.
            sum += duration;
            count++;

            var buildData = {
              buildNumber: buildNumber,
              phase: build.status.phase
            };
            buildData[build.status.phase] = duration;
            data.json.push(buildData);
            buildByNumber[buildNumber] = build;
          });

          // Show only the last 50 builds.
          if (data.json.length > 50) {
            data.json.sort(function(a, b) {
              return a.buildNumber - b.buildNumber;
            });
            data.json = data.json.slice(data.json.length - 50);
          }

          // Check for found phases only after we've sliced the array.
          var foundPhases = {};
          angular.forEach(data.json, function(buildData) {
            foundPhases[buildData.phase] = true;
          });

          // Calculate the average duration.
          // TODO: Should we only show the average for the last 50 builds
          //       instead of all builds?
          if (count) {
            averageDuration = sum / count;
            $scope.averageDurationText = humanize(averageDuration);
          } else {
            averageDuration = null;
            $scope.averageDurationText = null;
          }

          var groups = [], unload = [];
          angular.forEach(completePhases, function(phase) {
            if (foundPhases[phase]) {
              // Only show found phases in the chart legend.
              groups.push(phase);
            } else {
              // Unload any groups not found to remove them from the chart.
              // This can happen for filters, deleted builds, or if a build
              // phase is no longer in the last 50.
              unload.push(phase);
            }
          });
          data.keys.value = groups;
          data.groups = [groups];

          if (!chart) {
            config.data = angular.extend(data, config.data);
            // Allow the rest of the page to load so c3 calculates the correct
            // width for the chart based on the parent width.
            $timeout(function() {
              chart = c3.generate(config);
              updateAvgLine();
            });
          } else {
            data.unload = unload;
            // Call flush to work around a c3.js bug where the y-axis label
            // overlaps the tick values when the data changes.
            data.done = function() {
              // done() is called before the chart animation finishes.
              // Wait until the animation is complete to call flush().
              setTimeout(function() {
                chart.flush();
              }, animationDuration() + 25);
            };
            chart.load(data);
            updateAvgLine();
          }
        };

        $scope.$watch(numCompleteBuilds, update);

        $scope.$on('destroy', function() {
          if (chart) {
            // http://c3js.org/reference.html#api-destroy
            chart = chart.destroy();
          }
        });
      }
    };
  });
<commit_msg>Remove gaps from build chart for deleted builds<commit_after>"use strict";

angular.module('openshiftConsole')
  .directive('buildTrendsChart', function($filter, $location, $rootScope, $timeout) {
    return {
      restrict: 'E',
      scope: {
        builds: '='
      },
      templateUrl: 'views/_build-trends-chart.html',
      link: function($scope) {
        var data;
        var completePhases = ['Complete', 'Failed', 'Cancelled', 'Error'];

        // Minimum number of builds to show.
        $scope.minBuilds = _.constant(4);

        // Simple humanize function that returns an abbreviated duration like "1h 4m" or "2m 4s"
        // suitable for use on the chart y-axis.
        var humanize = function(value) {
          var result = [];
          var duration = moment.duration(value);
          var hours = Math.floor(duration.asHours());
          var minutes = duration.minutes();
          var seconds = duration.seconds();

          if (!hours && !minutes && !seconds) {
            return '';
          }

          if (hours) {
            result.push(hours + "h");
          }
          if (minutes) {
            result.push(minutes + "m");
          }

          // Only show seconds if not duration doesn't include hours.
          // Always show seconds otherwise (even 0s).
          if (!hours) {
            result.push(seconds + "s");
          }

          return result.join(" ");
        };

        var getStartTimestsamp = function(build) {
          return build.status.startTimestamp || build.metadata.creationTimestamp;
        };

        // Chart configuration, see http://c3js.org/reference.html
        $scope.chartID = _.uniqueId('build-trends-chart-');
        var animationDuration = _.constant(350);
        var config = {
          bindto: '#' + $scope.chartID,
          padding: {
            right: 30,
            // Prevent problems with the y-axis label getting cut off on updates.
            left: 80
          },
          axis: {
            x: {
              fit: true,
              label: {
                text: 'Build Number',
                position: 'outer-right'
              },
              tick: {
                culling: true,
                format: function(x) {
                  return '#' + data.json[x].buildNumber;
                },
                width: 30
              },
              type: 'category'
            },
            y: {
              label: {
                text: 'Duration',
                position: 'outer-top'
              },
              min: 0,
              padding: {
                bottom: 0
              },
              tick: {
                count: 5,
                culling: true,
                fit: true,
                format: humanize
              }
            }
          },
          bar: {
            width: {
              max: 50
            }
          },
          legend: {
            item: {
              // Disable clicking to filter groups
              onclick: _.noop
            }
          },
          size: {
            height: 200
          },
          tooltip: {
            format: {
              title: function(x) {
                var json = data.json[x];
                var startTimestamp = getStartTimestsamp(json.build);
                return '#' + json.buildNumber + ' (' + moment(startTimestamp).fromNow() + ')';
              }
            }
          },
          transition: {
            duration: animationDuration()
          },
          data: {
            // https://www.patternfly.org/styles/color-palette/
            colors: {
              Cancelled: "#d1d1d1",
              Complete: "#00b9e4",
              Error: "#393f44",
              Failed: "#cc0000"
            },
            empty: {
              label: {
                text: "No Completed Builds"
              }
            },
            onclick: function(d) {
              var build = data.json[d.x].build;
              var url = $filter('navigateResourceURL')(build);
              if (url) {
                $rootScope.$apply(function() {
                  $location.path(url);
                });
              }
            },
            selection: {
              enabled: true
            },
            type: 'bar'
          }
        };

        var updateCompleteBuilds = function() {
          $scope.completeBuilds = [];
          var isIncomplete = $filter('isIncompleteBuild');
          angular.forEach($scope.builds, function(build) {
            if (!isIncomplete(build)) {
              $scope.completeBuilds.push(build);
            }
          });
        };

        var numCompleteBuilds = function() {
          updateCompleteBuilds();
          return $scope.completeBuilds.length;
        };

        var annotationFilter = $filter('annotation');
        var getBuildNumber = function(build) {
          var buildNumber = annotationFilter(build, 'buildNumber') || parseInt(build.metadata.name.match(/(\d+)$/), 10);
          if (isNaN(buildNumber)) {
            return null;
          }

          return buildNumber;
        };

        var getBuildDuration = function(build) {
          var startTimestamp = getStartTimestsamp(build);
          var endTimestamp = build.status.completionTimestamp;
          if (!startTimestamp || !endTimestamp) {
            return 0;
          }

          return moment(endTimestamp).diff(moment(startTimestamp));
        };

        var chart, averageDuration, showAverageLine = false;
        var updateAvgLine = function() {
          if (averageDuration && showAverageLine) {
            chart.ygrids([{
              value: averageDuration,
              'class': 'build-trends-avg-line'
            }]);
          } else {
            chart.ygrids.remove();
          }
        };

        $scope.toggleAvgLine = function() {
          showAverageLine = !showAverageLine;
          updateAvgLine();
        };

        var update = function() {
          // Keep a map of builds by number so we can find the build later when a data point is clicked.
          data = {
            json: [],
            keys: {
              x: 'buildNumber'
            }
          };

          var sum = 0, count = 0;
          angular.forEach($scope.completeBuilds, function(build) {
            var buildNumber = getBuildNumber(build);
            if (!buildNumber) {
              return;
            }

            var duration = getBuildDuration(build);

            // Track the sum and count to calculate the average duration.
            sum += duration;
            count++;

            var buildData = {
              buildNumber: buildNumber,
              phase: build.status.phase,
              build: build
            };
            buildData[build.status.phase] = duration;
            data.json.push(buildData);
          });

          data.json.sort(function(a, b) {
            return a.buildNumber - b.buildNumber;
          });

          // Show only the last 50 builds.
          if (data.json.length > 50) {
            data.json = data.json.slice(data.json.length - 50);
          }

          // Check for found phases only after we've sliced the array.
          var foundPhases = {};
          angular.forEach(data.json, function(buildData) {
            foundPhases[buildData.phase] = true;
          });

          // Calculate the average duration.
          // TODO: Should we only show the average for the last 50 builds
          //       instead of all builds?
          if (count) {
            averageDuration = sum / count;
            $scope.averageDurationText = humanize(averageDuration);
          } else {
            averageDuration = null;
            $scope.averageDurationText = null;
          }

          var groups = [], unload = [];
          angular.forEach(completePhases, function(phase) {
            if (foundPhases[phase]) {
              // Only show found phases in the chart legend.
              groups.push(phase);
            } else {
              // Unload any groups not found to remove them from the chart.
              // This can happen for filters, deleted builds, or if a build
              // phase is no longer in the last 50.
              unload.push(phase);
            }
          });
          data.keys.value = groups;
          data.groups = [groups];

          if (!chart) {
            config.data = angular.extend(data, config.data);
            // Allow the rest of the page to load so c3 calculates the correct
            // width for the chart based on the parent width.
            $timeout(function() {
              chart = c3.generate(config);
              updateAvgLine();
            });
          } else {
            data.unload = unload;
            // Call flush to work around a c3.js bug where the y-axis label
            // overlaps the tick values when the data changes.
            data.done = function() {
              // done() is called before the chart animation finishes.
              // Wait until the animation is complete to call flush().
              setTimeout(function() {
                chart.flush();
              }, animationDuration() + 25);
            };
            chart.load(data);
            updateAvgLine();
          }
        };

        $scope.$watch(numCompleteBuilds, update);

        $scope.$on('destroy', function() {
          if (chart) {
            // http://c3js.org/reference.html#api-destroy
            chart = chart.destroy();
          }
        });
      }
    };
  });
<|endoftext|>