<commit_before>define(['vendor/lodash'], function (_) {
    var LABEL_VALUE_FONT_SIZE = 14,
        LABEL_FONT_SIZE = 10,
        MIN_LABEL_RADIUS = 60,
        ARROW_WIDTH = 30,
        MIN_PIE_RADIUS = 30,
        T_ANGLE = 0;
    var Pie = function () {
        this.options = {};
        this.slices = [];
        this.cachedData = [];
        this.labelSizes = [];
        this.labelParts = [];
        this.showLabels = true;
    };

    Pie.prototype.config = function (_options) {
        this.options = _.extend (this.options, _options);
        this.cachedData = _options.series.data;
    };

    Pie.prototype.renderTo = function (paper, core, w, h) {
        this.paper = paper;
        this.core = core;
        this.width = w;
        this.height = h;
        this.r = getRadius (this);
        createSlices (this);
        drawLabels (this);
        setSlicePaths (this);
    };

    Pie.prototype.hasLabels = function () {
        return this.showLabels;
    };

    function getRadius (self) {
        var radiusObj = {};
        var iterations = 6;
        while (iterations--) {
            radiusObj[T_ANGLE] = calcRadius(self);
            T_ANGLE +=60;
        }
        var maxRadius = _.max(_.values(radiusObj));
        for (var key in radiusObj) {
            if (radiusObj[key] === maxRadius) {
                T_ANGLE = +key;
                break;
            }
        }
        return maxRadius;
    }

    function createSlices (self) {
        var paper = self.paper,
            core = self.core,
            series = self.options.series,
            data = series.data;
        for(var i=0; i<data.length; i++) {
            var slice = paper.path ();
            slice.attr ('fill', series.colors[i]);
            self.slices[i] = slice;
            core.append (slice);
            !function (idx) {
                self.slices[idx].mousemove(function (me) {
                    self.options.eventManager.trigger ('tooltip', {
                        seriesLabel: self.options.labels[idx],
                        value: 42,
                        color: series.colors[idx],
                        position: {
                            x: me.clientX,
                            y: me.clientY
                        },
                    });
                });

                self.slices[idx].mouseout(function (me) {
                    self.options.eventManager.trigger ('tooltip.mouseout');
                });
            } (i);
        }
    };

    function setSlicePaths (self, animate) {
         var series = self.options.series,
            data = series.data,
            w = self.width,
            h = self.height,
            cx = w / 2,
            cy = h / 2,
            r = self.r,
            total = _.reduce (data, function(mem, num) { return mem + num; }, 0),
            tAngle = T_ANGLE;

        for(var i=0; i<self.slices.length; i++) {
            var currAngle = data[i] / total * 360;
            var pathString = slicePath (cx, cy, tAngle, tAngle + currAngle, 0, r);
            if(animate) {
                self.slices[i].animatePath (pathString);
            } else {
                self.slices[i].setPath (pathString);
            }
            self.slices[i].attr({
                "stroke" : "none"
            });
            tAngle += currAngle;
        }
    };

    Pie.prototype.resizeTo = function (w, h) {
        this.width = w;
        this.height = h;
        this.r = calcRadius (this);
        updateLabels (this);
        setSlicePaths (this);
    };

    Pie.prototype.update = function (_options) {
        var slices = this.slices;
        var series = this.options.series,
            data = _options.series.data,
            valueLabels = self.options.valueLabels,
            w = this.width,
            h = this.height,
            cx = w / 2,
            cy = h / 2,
            r = this.r = calcRadius (this),
            total = _.reduce (data, function(mem, num) { return mem + num; }, 0),
            tAngle = T_ANGLE,
            oldData = this.cachedData,
            oldTotal = _.reduce (oldData, function(mem, num) { return mem + num; }, 0),
            oldTAngle = T_ANGLE;

        this.options = _.extend(this.options, _options);
        for(var i=0; i<slices.length; i++) {
            var currAngle = data[i] / total * 360;
            var oldCurrAngle = oldData[i] / oldTotal * 360;

            !function(startAngle, endAngle, oStartAngle, oEndAngle) {
                slices[i].animateWith(function (el, d) {
                    var pathString = slicePath (cx, cy,
                                        oStartAngle + (startAngle - oStartAngle) * d,
                                        oEndAngle + (endAngle - oEndAngle) * d, 0, r);
                    el.setPath (pathString);
                }, 500);
            }(tAngle, tAngle + currAngle, oldTAngle, oldTAngle + oldCurrAngle);
            tAngle += currAngle;
            oldTAngle += oldCurrAngle
        }
        this.cachedData = data;
        updateLabels (this);
    };

    var slicePath = function(cx, cy, startAngle, endAngle, innerRadius, outerRadius){
        var cut = Math.abs(startAngle - endAngle) > 180 ? 1 : 0;
        var startX  = cx + innerRadius * Math.cos(Math.PI * startAngle / 180),
            startY = cy + innerRadius * Math.sin(Math.PI * startAngle / 180),
            endX  = cx + innerRadius * Math.cos(Math.PI * endAngle / 180),
            endY = cy + innerRadius * Math.sin(Math.PI * endAngle / 180);

        var x1 = cx + outerRadius * Math.cos(Math.PI * startAngle/180),
            y1 = cy + outerRadius * Math.sin(Math.PI * startAngle/180),
            x2 = cx + outerRadius * Math.cos(Math.PI * endAngle/180),
            y2 = cy + outerRadius * Math.sin(Math.PI * endAngle/180);

        var pathString = "M"+ startX + " " + startY + " L" + x1 + " " + y1 + " A" + outerRadius + " " + outerRadius + " 0 " + cut + " 1 " + x2 + " " + y2 +
                            " L"+ endX + " " + endY +" A" + innerRadius + " " + innerRadius + " 0 " + cut + " 0 " + startX + " " + startY + " z";

        return pathString;
    };

    var angleToPoint = function(cx, cy, r, angle) {
        var point = {};

        point.x = cx + r * Math.cos(Math.PI * angle / 180);
        point.y = cy + r * Math.sin(Math.PI * angle / 180);

        return point;
    };

    function findWidths (self) {
        var paper = self.paper,
            data = self.options.series.data,
            labels = self.options.labels;

        for(var i=0; i<data.length; i++) {
            var labelText = data[i] + '##' + labels[i];
            var label = paper.text (0, 0, labelText);
            label.attr ('font-size', LABEL_FONT_SIZE);
            paper.append (label);
            var bbox = label.getBBox ();
            self.labelSizes[i] = {
                width: bbox.width,
                height: bbox.height
            };
            label.remove ();
        }
    }

    function calcRadius (self) {
        var paper = self.paper,
            w = self.width,
            h = self.height,
            labels = self.options.labels,
            data = self.options.series.data,
            maxR = _.min([w/2, h/2]),
            cx = w / 2,
            cy = h / 2,
            total = _.reduce (data, function(mem, num) { return mem + num; }, 0),
            tAngle = T_ANGLE;

        findWidths (self);
        var labelBBoxes = _.cloneDeep (self.labelSizes);
        for(var i=0; i<labels.length; i++) {
            var startAngle = tAngle;
            var endAngle = tAngle + data[i] / total * 360;
            var pos = angleToPoint (cx, cy, maxR, (startAngle + endAngle) / 2);
            if(pos.x < cx) {
                labelBBoxes[i].x = pos.x - labelBBoxes[i].width - ARROW_WIDTH;
            } else {
                labelBBoxes[i].x = pos.x + ARROW_WIDTH;
            }

            labelBBoxes[i].y = pos.y - labelBBoxes[i].height / 2;
            tAngle = endAngle;
            var rect = paper.rect (labelBBoxes[i].x, labelBBoxes[i].y, labelBBoxes[i].width, labelBBoxes[i].height);
            self.core.append (rect);
            rect.remove ();
        }
        var xs = _.pluck(labelBBoxes, 'x');
        var ys = _.pluck(labelBBoxes, 'y');
        var xDiffs = _.map(xs, function(num, idx) {
            if(num + labelBBoxes[idx].width > w) {
                return Math.abs(num - w + labelBBoxes[idx].width);
            } else if(num < 0) {
                return Math.abs(num);
            }
            return 0;
        });
        var yDiffs = _.map(ys, function(num, idx) {
            if(num + labelBBoxes[idx].height > h) {
                return Math.abs(num - h + labelBBoxes[idx].height);
            } else if(num < 0) {
                return Math.abs(num);
            }
            return 0;
        });
        var xMax = _.max (xDiffs);
        var yMax = _.max (yDiffs);
        var r = _.max([xMax, yMax]);

        optimizeLabel(maxR);

        var pieRadius = maxR - r - MIN_LABEL_RADIUS;

        if (pieRadius <= MIN_PIE_RADIUS) {
            pieRadius = maxR;
            self.showLabels = false;
        } else {
            self.showLabels = true;
        }
        return pieRadius;
    };

    function optimizeLabel(maxR) {
        MIN_LABEL_RADIUS = maxR * 0.2;
    }

    function drawLabels (self) {
        var paper = self.paper,
            w = self.width,
            h = self.height,
            labels = self.options.labels,
            data = self.options.series.data,
            valueLabels = self.options.valueLabels,
            cx = w / 2,
            cy = h / 2,
            r = self.r,
            total = _.reduce (data, function(mem, num) { return mem + num; }, 0),
            tAngle = T_ANGLE;
        for(var i=0; i<labels.length; i++) {
            var startAngle = tAngle;
            var endAngle = tAngle + data[i] / total * 360;
            var pos = angleToPoint (cx, cy, r + MIN_LABEL_RADIUS, (startAngle + endAngle) / 2);
            var circlePos = angleToPoint (cx, cy, r, (startAngle + endAngle) / 2);
            var label = paper.text (pos.x < cx ? 10 : -10, 5, "");
            var tspanValue = paper.tspan(valueLabels[i]);
            var tspanLabel = paper.tspan(" " + labels[i]);
            tspanValue.attr({
                'fill' : '#000',
                'font-size': LABEL_VALUE_FONT_SIZE,
                'font-weight': 700
            });
            tspanLabel.attr('font-size', LABEL_FONT_SIZE);
            label.append(tspanValue);
            label.append(tspanLabel);
            var x, y;
            if(pos.x < cx) {
                x = pos.x - ARROW_WIDTH;
                label.attr ({'text-anchor': 'end'});
            } else {
                x = pos.x + ARROW_WIDTH;
                label.attr ({'text-anchor': 'start'});
            }
            y = pos.y;
            var line1 = paper.line (circlePos.x, circlePos.y, pos.x, pos.y);
            var line2 = paper.line (pos.x, pos.y, x + (pos.x < cx ? 15: -15), y);
            self.labelParts[i] = {
                label: label,
                line1: line1,
                line2: line2,
                tspanValue: tspanValue,
                tspanLabel: tspanLabel
            };
            line1.attr({
                "stroke" : "#666",
                "stroke-width" : 1
            });
            line2.attr({
                "stroke" : "#666",
                "stroke-width" : 1
            });
            label.attr({
                "fill" : "#666",
                "font-size" : "10px"
            });
            tAngle = endAngle;
            label.translate (x, y);
            self.core.append (label);
            self.core.append (line1);
            self.core.append (line2);
        }
        if(!self.showLabels) {
            hideLabels(self);
        } else {
            showLabels(self);
        }
    };

    function hideLabels (self) {
        var labelParts = self.labelParts;
        for (var i=0; i< labelParts.length; i++) {
            labelParts[i].line1.css({
                "display" : "none"
            });
            labelParts[i].line2.css({
                "display" : "none"
            });
            labelParts[i].label.css({
                "display" : "none"
            });
        }
    };

    function showLabels (self) {
        var labelParts = self.labelParts;
        for (var i=0; i< labelParts.length; i++) {
            labelParts[i].line1.css({
                "display" : "initial"
            });
            labelParts[i].line2.css({
                "display" : "initial"
            });
            labelParts[i].label.css({
                "display" : "initial"
            });
        }
    };

    function updateLabels (self) {
        var paper = self.paper,
            w = self.width,
            h = self.height,
            labels = self.options.labels,
            data = self.options.series.data,
            valueLabels = self.options.valueLabels,
            cx = w / 2,
            cy = h / 2,
            r = self.r,
            total = _.reduce (data, function(mem, num) { return mem + num; }, 0),
            tAngle = T_ANGLE;
        for(var i=0; i<labels.length; i++) {
            var startAngle = tAngle;
            var endAngle = tAngle + data[i] / total * 360;
            var pos = angleToPoint (cx, cy, r + MIN_LABEL_RADIUS, (startAngle + endAngle) / 2);
            var circlePos = angleToPoint (cx, cy, r, (startAngle + endAngle) / 2);
            var label = self.labelParts[i].label;
            var tspanLabel = self.labelParts[i].tspanLabel;
            var tspanValue = self.labelParts[i].tspanValue;

            tspanValue.text(valueLabels[i]);
            tspanLabel.text(" " + labels[i]);
            label.attr ({
                x: pos.x < cx ? 10 : -10
            });

            var x, y;
            if(pos.x < cx) {
                x = pos.x - ARROW_WIDTH;
                label.attr ({'text-anchor': 'end'});
            } else {
                x = pos.x + ARROW_WIDTH;
                label.attr ({'text-anchor': 'start'});
            }
            y = pos.y;
            var line1 = self.labelParts[i].line1;
            var line2 = self.labelParts[i].line2;
            line1.attr({
                x1: circlePos.x,
                y1: circlePos.y,
                x2: pos.x,
                y2: pos.y
            });
            line2.attr ({
                x1: pos.x,
                y1: pos.y,
                x2: x + (pos.x < cx ? 15: -15),
                y2: y
            });
            tAngle = endAngle;
            label.translate (x, y);
        }
        if(!self.showLabels) {
            hideLabels(self);
        } else {
            showLabels(self);
        }
    };

    return Pie;
});<commit_msg>Pie chart tooltip position fix<commit_after>define(['vendor/lodash'], function (_) {
    var LABEL_VALUE_FONT_SIZE = 14,
        LABEL_FONT_SIZE = 10,
        MIN_LABEL_RADIUS = 60,
        ARROW_WIDTH = 30,
        MIN_PIE_RADIUS = 30,
        T_ANGLE = 0;
    var Pie = function () {
        this.options = {};
        this.slices = [];
        this.cachedData = [];
        this.labelSizes = [];
        this.labelParts = [];
        this.showLabels = true;
    };

    Pie.prototype.config = function (_options) {
        this.options = _.extend (this.options, _options);
        this.cachedData = _options.series.data;
    };

    Pie.prototype.renderTo = function (paper, core, w, h) {
        this.paper = paper;
        this.core = core;
        this.width = w;
        this.height = h;
        this.r = getRadius (this);
        createSlices (this);
        drawLabels (this);
        setSlicePaths (this);
    };

    Pie.prototype.hasLabels = function () {
        return this.showLabels;
    };

    function getRadius (self) {
        var radiusObj = {};
        var iterations = 6;
        while (iterations--) {
            radiusObj[T_ANGLE] = calcRadius(self);
            T_ANGLE +=60;
        }
        var maxRadius = _.max(_.values(radiusObj));
        for (var key in radiusObj) {
            if (radiusObj[key] === maxRadius) {
                T_ANGLE = +key;
                break;
            }
        }
        return maxRadius;
    }

    function createSlices (self) {
        var paper = self.paper,
            core = self.core,
            series = self.options.series,
            data = series.data;
        for(var i=0; i<data.length; i++) {
            var slice = paper.path ();
            slice.attr ('fill', series.colors[i]);
            self.slices[i] = slice;
            core.append (slice);
            !function (idx) {
                self.slices[idx].mousemove(function (me) {
                    self.options.eventManager.trigger ('tooltip', {
                        seriesLabel: self.options.labels[idx],
                        value: 42,
                        color: series.colors[idx],
                        position: {
                            x: me.clientX + $('body').scrollLeft(),
                            y: me.clientY + $('body').scrollTop()
                        },
                    });
                });

                self.slices[idx].mouseout(function (me) {
                    self.options.eventManager.trigger ('tooltip.mouseout');
                });
            } (i);
        }
    };

    function setSlicePaths (self, animate) {
         var series = self.options.series,
            data = series.data,
            w = self.width,
            h = self.height,
            cx = w / 2,
            cy = h / 2,
            r = self.r,
            total = _.reduce (data, function(mem, num) { return mem + num; }, 0),
            tAngle = T_ANGLE;

        for(var i=0; i<self.slices.length; i++) {
            var currAngle = data[i] / total * 360;
            var pathString = slicePath (cx, cy, tAngle, tAngle + currAngle, 0, r);
            if(animate) {
                self.slices[i].animatePath (pathString);
            } else {
                self.slices[i].setPath (pathString);
            }
            self.slices[i].attr({
                "stroke" : "none"
            });
            tAngle += currAngle;
        }
    };

    Pie.prototype.resizeTo = function (w, h) {
        this.width = w;
        this.height = h;
        this.r = calcRadius (this);
        updateLabels (this);
        setSlicePaths (this);
    };

    Pie.prototype.update = function (_options) {
        var slices = this.slices;
        var series = this.options.series,
            data = _options.series.data,
            valueLabels = self.options.valueLabels,
            w = this.width,
            h = this.height,
            cx = w / 2,
            cy = h / 2,
            r = this.r = calcRadius (this),
            total = _.reduce (data, function(mem, num) { return mem + num; }, 0),
            tAngle = T_ANGLE,
            oldData = this.cachedData,
            oldTotal = _.reduce (oldData, function(mem, num) { return mem + num; }, 0),
            oldTAngle = T_ANGLE;

        this.options = _.extend(this.options, _options);
        for(var i=0; i<slices.length; i++) {
            var currAngle = data[i] / total * 360;
            var oldCurrAngle = oldData[i] / oldTotal * 360;

            !function(startAngle, endAngle, oStartAngle, oEndAngle) {
                slices[i].animateWith(function (el, d) {
                    var pathString = slicePath (cx, cy,
                                        oStartAngle + (startAngle - oStartAngle) * d,
                                        oEndAngle + (endAngle - oEndAngle) * d, 0, r);
                    el.setPath (pathString);
                }, 500);
            }(tAngle, tAngle + currAngle, oldTAngle, oldTAngle + oldCurrAngle);
            tAngle += currAngle;
            oldTAngle += oldCurrAngle
        }
        this.cachedData = data;
        updateLabels (this);
    };

    var slicePath = function(cx, cy, startAngle, endAngle, innerRadius, outerRadius){
        var cut = Math.abs(startAngle - endAngle) > 180 ? 1 : 0;
        var startX  = cx + innerRadius * Math.cos(Math.PI * startAngle / 180),
            startY = cy + innerRadius * Math.sin(Math.PI * startAngle / 180),
            endX  = cx + innerRadius * Math.cos(Math.PI * endAngle / 180),
            endY = cy + innerRadius * Math.sin(Math.PI * endAngle / 180);

        var x1 = cx + outerRadius * Math.cos(Math.PI * startAngle/180),
            y1 = cy + outerRadius * Math.sin(Math.PI * startAngle/180),
            x2 = cx + outerRadius * Math.cos(Math.PI * endAngle/180),
            y2 = cy + outerRadius * Math.sin(Math.PI * endAngle/180);

        var pathString = "M"+ startX + " " + startY + " L" + x1 + " " + y1 + " A" + outerRadius + " " + outerRadius + " 0 " + cut + " 1 " + x2 + " " + y2 +
                            " L"+ endX + " " + endY +" A" + innerRadius + " " + innerRadius + " 0 " + cut + " 0 " + startX + " " + startY + " z";

        return pathString;
    };

    var angleToPoint = function(cx, cy, r, angle) {
        var point = {};

        point.x = cx + r * Math.cos(Math.PI * angle / 180);
        point.y = cy + r * Math.sin(Math.PI * angle / 180);

        return point;
    };

    function findWidths (self) {
        var paper = self.paper,
            data = self.options.series.data,
            labels = self.options.labels;

        for(var i=0; i<data.length; i++) {
            var labelText = data[i] + '##' + labels[i];
            var label = paper.text (0, 0, labelText);
            label.attr ('font-size', LABEL_FONT_SIZE);
            paper.append (label);
            var bbox = label.getBBox ();
            self.labelSizes[i] = {
                width: bbox.width,
                height: bbox.height
            };
            label.remove ();
        }
    }

    function calcRadius (self) {
        var paper = self.paper,
            w = self.width,
            h = self.height,
            labels = self.options.labels,
            data = self.options.series.data,
            maxR = _.min([w/2, h/2]),
            cx = w / 2,
            cy = h / 2,
            total = _.reduce (data, function(mem, num) { return mem + num; }, 0),
            tAngle = T_ANGLE;

        findWidths (self);
        var labelBBoxes = _.cloneDeep (self.labelSizes);
        for(var i=0; i<labels.length; i++) {
            var startAngle = tAngle;
            var endAngle = tAngle + data[i] / total * 360;
            var pos = angleToPoint (cx, cy, maxR, (startAngle + endAngle) / 2);
            if(pos.x < cx) {
                labelBBoxes[i].x = pos.x - labelBBoxes[i].width - ARROW_WIDTH;
            } else {
                labelBBoxes[i].x = pos.x + ARROW_WIDTH;
            }

            labelBBoxes[i].y = pos.y - labelBBoxes[i].height / 2;
            tAngle = endAngle;
            var rect = paper.rect (labelBBoxes[i].x, labelBBoxes[i].y, labelBBoxes[i].width, labelBBoxes[i].height);
            self.core.append (rect);
            rect.remove ();
        }
        var xs = _.pluck(labelBBoxes, 'x');
        var ys = _.pluck(labelBBoxes, 'y');
        var xDiffs = _.map(xs, function(num, idx) {
            if(num + labelBBoxes[idx].width > w) {
                return Math.abs(num - w + labelBBoxes[idx].width);
            } else if(num < 0) {
                return Math.abs(num);
            }
            return 0;
        });
        var yDiffs = _.map(ys, function(num, idx) {
            if(num + labelBBoxes[idx].height > h) {
                return Math.abs(num - h + labelBBoxes[idx].height);
            } else if(num < 0) {
                return Math.abs(num);
            }
            return 0;
        });
        var xMax = _.max (xDiffs);
        var yMax = _.max (yDiffs);
        var r = _.max([xMax, yMax]);

        optimizeLabel(maxR);

        var pieRadius = maxR - r - MIN_LABEL_RADIUS;

        if (pieRadius <= MIN_PIE_RADIUS) {
            pieRadius = maxR;
            self.showLabels = false;
        } else {
            self.showLabels = true;
        }
        return pieRadius;
    };

    function optimizeLabel(maxR) {
        MIN_LABEL_RADIUS = maxR * 0.2;
    }

    function drawLabels (self) {
        var paper = self.paper,
            w = self.width,
            h = self.height,
            labels = self.options.labels,
            data = self.options.series.data,
            valueLabels = self.options.valueLabels,
            cx = w / 2,
            cy = h / 2,
            r = self.r,
            total = _.reduce (data, function(mem, num) { return mem + num; }, 0),
            tAngle = T_ANGLE;
        for(var i=0; i<labels.length; i++) {
            var startAngle = tAngle;
            var endAngle = tAngle + data[i] / total * 360;
            var pos = angleToPoint (cx, cy, r + MIN_LABEL_RADIUS, (startAngle + endAngle) / 2);
            var circlePos = angleToPoint (cx, cy, r, (startAngle + endAngle) / 2);
            var label = paper.text (pos.x < cx ? 10 : -10, 5, "");
            var tspanValue = paper.tspan(valueLabels[i]);
            var tspanLabel = paper.tspan(" " + labels[i]);
            tspanValue.attr({
                'fill' : '#000',
                'font-size': LABEL_VALUE_FONT_SIZE,
                'font-weight': 700
            });
            tspanLabel.attr('font-size', LABEL_FONT_SIZE);
            label.append(tspanValue);
            label.append(tspanLabel);
            var x, y;
            if(pos.x < cx) {
                x = pos.x - ARROW_WIDTH;
                label.attr ({'text-anchor': 'end'});
            } else {
                x = pos.x + ARROW_WIDTH;
                label.attr ({'text-anchor': 'start'});
            }
            y = pos.y;
            var line1 = paper.line (circlePos.x, circlePos.y, pos.x, pos.y);
            var line2 = paper.line (pos.x, pos.y, x + (pos.x < cx ? 15: -15), y);
            self.labelParts[i] = {
                label: label,
                line1: line1,
                line2: line2,
                tspanValue: tspanValue,
                tspanLabel: tspanLabel
            };
            line1.attr({
                "stroke" : "#666",
                "stroke-width" : 1
            });
            line2.attr({
                "stroke" : "#666",
                "stroke-width" : 1
            });
            label.attr({
                "fill" : "#666",
                "font-size" : "10px"
            });
            tAngle = endAngle;
            label.translate (x, y);
            self.core.append (label);
            self.core.append (line1);
            self.core.append (line2);
        }
        if(!self.showLabels) {
            hideLabels(self);
        } else {
            showLabels(self);
        }
    };

    function hideLabels (self) {
        var labelParts = self.labelParts;
        for (var i=0; i< labelParts.length; i++) {
            labelParts[i].line1.css({
                "display" : "none"
            });
            labelParts[i].line2.css({
                "display" : "none"
            });
            labelParts[i].label.css({
                "display" : "none"
            });
        }
    };

    function showLabels (self) {
        var labelParts = self.labelParts;
        for (var i=0; i< labelParts.length; i++) {
            labelParts[i].line1.css({
                "display" : "initial"
            });
            labelParts[i].line2.css({
                "display" : "initial"
            });
            labelParts[i].label.css({
                "display" : "initial"
            });
        }
    };

    function updateLabels (self) {
        var paper = self.paper,
            w = self.width,
            h = self.height,
            labels = self.options.labels,
            data = self.options.series.data,
            valueLabels = self.options.valueLabels,
            cx = w / 2,
            cy = h / 2,
            r = self.r,
            total = _.reduce (data, function(mem, num) { return mem + num; }, 0),
            tAngle = T_ANGLE;
        for(var i=0; i<labels.length; i++) {
            var startAngle = tAngle;
            var endAngle = tAngle + data[i] / total * 360;
            var pos = angleToPoint (cx, cy, r + MIN_LABEL_RADIUS, (startAngle + endAngle) / 2);
            var circlePos = angleToPoint (cx, cy, r, (startAngle + endAngle) / 2);
            var label = self.labelParts[i].label;
            var tspanLabel = self.labelParts[i].tspanLabel;
            var tspanValue = self.labelParts[i].tspanValue;

            tspanValue.text(valueLabels[i]);
            tspanLabel.text(" " + labels[i]);
            label.attr ({
                x: pos.x < cx ? 10 : -10
            });

            var x, y;
            if(pos.x < cx) {
                x = pos.x - ARROW_WIDTH;
                label.attr ({'text-anchor': 'end'});
            } else {
                x = pos.x + ARROW_WIDTH;
                label.attr ({'text-anchor': 'start'});
            }
            y = pos.y;
            var line1 = self.labelParts[i].line1;
            var line2 = self.labelParts[i].line2;
            line1.attr({
                x1: circlePos.x,
                y1: circlePos.y,
                x2: pos.x,
                y2: pos.y
            });
            line2.attr ({
                x1: pos.x,
                y1: pos.y,
                x2: x + (pos.x < cx ? 15: -15),
                y2: y
            });
            tAngle = endAngle;
            label.translate (x, y);
        }
        if(!self.showLabels) {
            hideLabels(self);
        } else {
            showLabels(self);
        }
    };

    return Pie;
});<|endoftext|>


<commit_before>/*#ifndef(UMD)*/
"use strict";
/*global _gpfMainContext*/ // Main context object
/*exported _gpfResolveScope*/ // Translate the parameter into a valid scope
/*#endif*/

/**
 * Translate the parameter into a valid scope
 *
 * @param {*} scope
 */
function _gpfResolveScope (scope) {
    if (null === scope || "object" !== typeof scope) {
        return _gpfMainContext;
    }
    return scope;
}

/*#ifndef(UMD)*/

gpf.internals._gpfResolveScope = _gpfResolveScope;

/*#endif*/
<commit_msg>documentation<commit_after>/**
 * @file Scope management
 */
/*#ifndef(UMD)*/
"use strict";
/*global _gpfMainContext*/ // Main context object
/*exported _gpfResolveScope*/ // Translate the parameter into a valid scope
/*#endif*/

/**
 * Translate the parameter into a valid scope
 *
 * @param {*} scope
 */
function _gpfResolveScope (scope) {
    if (null === scope || "object" !== typeof scope) {
        return _gpfMainContext;
    }
    return scope;
}

/*#ifndef(UMD)*/

gpf.internals._gpfResolveScope = _gpfResolveScope;

/*#endif*/
<|endoftext|>


<commit_before>window.GOVUK = window.GOVUK || {}
window.GOVUK.Modules = window.GOVUK.Modules || {};

(function (Modules) {
  function MobileFiltersModal ($module) {
    this.$module = $module
    this.$facetsBox = this.$module.querySelector('.facets__box')
    this.$closeTriggers = this.$module.querySelectorAll('.js-close-filters')
    this.$showResultsButton = this.$module.querySelector('.js-show-results')
    this.$clearFiltersTrigger = this.$module.querySelector('.js-clear-selected-filters')
    this.$body = document.querySelector('body')

    this.$module.open = this.handleOpen.bind(this)
    this.$module.close = this.handleClose.bind(this)
    this.$module.clearFilters = this.handleClearFilters.bind(this)
    this.$module.ModalFocus = this.handleModalFocus.bind(this)
    this.$module.boundKeyDown = this.handleKeyDown.bind(this)
  }

  MobileFiltersModal.prototype.init = function () {
    var $triggerElement = document.querySelector(
      '[data-toggle="mobile-filters-modal"][data-target="' + this.$module.id + '"]'
    )
    if ($triggerElement) {
      $triggerElement.addEventListener('click', this.$module.open)
    }

    if (this.$closeTriggers) {
      for (var t = 0; t < this.$closeTriggers.length; t++) {
        this.$closeTriggers[t].addEventListener('click', this.$module.close)
      }
    }

    if (this.$clearFiltersTrigger) {
      this.$clearFiltersTrigger.addEventListener('click', this.$module.clearFilters)
    }
  }

  MobileFiltersModal.prototype.handleOpen = function (event) {
    if (event) {
      event.preventDefault()
    }
    // when our support browser matrix is above iOS 13
    // we can use overflow: hidden on the <body> and remove position: fixed
    this.$body.style.top = '-' + window.scrollY + 'px'
    this.$body.style.position = 'fixed'
    this.$focusedElementBeforeOpen = document.activeElement
    this.$module.classList.add('facets--visible')
    this.$facetsBox.setAttribute('aria-modal', true)
    this.$facetsBox.setAttribute('tabindex', 0)
    this.$facetsBox.focus()

    document.addEventListener('keydown', this.$module.boundKeyDown, true)
  }

  MobileFiltersModal.prototype.handleClose = function (event) {
    if (event) {
      event.preventDefault()
    }

    var offsetTop = this.$body.style.top
    this.$body.style.position = ''
    this.$body.style.top = ''
    window.scrollTo(0, parseInt(offsetTop || '0') * -1)
    this.$module.classList.remove('facets--visible')
    this.$facetsBox.removeAttribute('aria-modal')
    this.$facetsBox.removeAttribute('tabindex')
    this.$focusedElementBeforeOpen.focus()

    document.removeEventListener('keydown', this.$module.boundKeyDown, true)
  }

  MobileFiltersModal.prototype.handleModalFocus = function () {
    this.$facetsBox.focus()
  }

  // while open, prevent tabbing to outside the dialogue
  // and listen for ESC key to close the dialogue
  MobileFiltersModal.prototype.handleKeyDown = function (event) {
    var KEY_TAB = 9
    var KEY_ESC = 27

    switch (event.keyCode) {
      case KEY_TAB:
        if (event.shiftKey) {
          if (document.activeElement === this.$facetsBox) {
            event.preventDefault()
            this.$showResultsButton.focus()
          }
        } else {
          if (document.activeElement === this.$showResultsButton) {
            event.preventDefault()
            this.$facetsBox.focus()
          }
        }
        break
      case KEY_ESC:
        this.$module.close()
        break
      default:
        break
    }
  }

  MobileFiltersModal.prototype.handleClearFilters = function (event) {
    if (event) {
      event.preventDefault()
    }
    // reset all selects, uncheck checkboxes, clear text input values
    // and remove the selected count on each facet
    var $elements = this.$module.querySelectorAll('input, select, .js-selected-counter')
    var $form = document.querySelector('.js-live-search-form')
    var customEvent = document.createEvent('HTMLEvents')
    customEvent.initEvent('customFormChange', true, false)
    for (var i = 0; i < $elements.length; i++) {
      var $el = $elements[i]
      var tagName = $el.tagName
      switch (tagName) {
        case 'INPUT':
          if ($el.type === 'checkbox' && $el.checked === true) {
            $el.checked = false
          } else if ($el.type === 'text' && $el.value !== '') {
            $el.value = ''
          }
          break
        case 'SELECT':
          $el.value = ''
          break
        case 'DIV':
          $el.parentNode.removeChild($el)
          break
        default:
          break
      }
    }
    // fire a single custom change event on the form once all filters have been cleared
    // so that we only fetch new search-api data once
    $form.dispatchEvent(customEvent)
  }

  Modules.MobileFiltersModal = MobileFiltersModal
})(window.GOVUK.Modules)
<commit_msg>Make mobile filters test pass in IE10+<commit_after>window.GOVUK = window.GOVUK || {}
window.GOVUK.Modules = window.GOVUK.Modules || {};

(function (Modules) {
  function MobileFiltersModal ($module) {
    this.$module = $module
    this.$facetsBox = this.$module.querySelector('.facets__box')
    this.$closeTriggers = this.$module.querySelectorAll('.js-close-filters')
    this.$showResultsButton = this.$module.querySelector('.js-show-results')
    this.$clearFiltersTrigger = this.$module.querySelector('.js-clear-selected-filters')
    this.$body = document.querySelector('body')

    this.$module.open = this.handleOpen.bind(this)
    this.$module.close = this.handleClose.bind(this)
    this.$module.clearFilters = this.handleClearFilters.bind(this)
    this.$module.ModalFocus = this.handleModalFocus.bind(this)
    this.$module.boundKeyDown = this.handleKeyDown.bind(this)
  }

  MobileFiltersModal.prototype.init = function () {
    var $triggerElement = document.querySelector(
      '[data-toggle="mobile-filters-modal"][data-target="' + this.$module.id + '"]'
    )
    if ($triggerElement) {
      $triggerElement.addEventListener('click', this.$module.open)
    }

    if (this.$closeTriggers) {
      for (var t = 0; t < this.$closeTriggers.length; t++) {
        this.$closeTriggers[t].addEventListener('click', this.$module.close)
      }
    }

    if (this.$clearFiltersTrigger) {
      this.$clearFiltersTrigger.addEventListener('click', this.$module.clearFilters)
    }
  }

  MobileFiltersModal.prototype.handleOpen = function (event) {
    if (event) {
      event.preventDefault()
    }
    // when our support browser matrix is above iOS 13
    // we can use overflow: hidden on the <body> and remove position: fixed
    this.$body.style.top = '-' + window.scrollY + 'px'
    this.$body.style.position = 'fixed'
    this.$focusedElementBeforeOpen = document.activeElement
    this.$module.classList.add('facets--visible')
    this.$facetsBox.setAttribute('aria-modal', true)
    this.$facetsBox.setAttribute('tabindex', 0)
    this.$facetsBox.focus()

    document.addEventListener('keydown', this.$module.boundKeyDown, true)
  }

  MobileFiltersModal.prototype.handleClose = function (event) {
    if (event) {
      event.preventDefault()
    }

    var offsetTop = this.$body.style.top
    this.$body.style.position = ''
    this.$body.style.top = ''
    window.scrollTo(0, parseInt(offsetTop || '0') * -1)
    this.$module.classList.remove('facets--visible')
    this.$facetsBox.removeAttribute('aria-modal')
    this.$facetsBox.removeAttribute('tabindex')
    this.$focusedElementBeforeOpen.focus()

    document.removeEventListener('keydown', this.$module.boundKeyDown, true)
  }

  MobileFiltersModal.prototype.handleModalFocus = function () {
    this.$facetsBox.focus()
  }

  // while open, prevent tabbing to outside the dialogue
  // and listen for ESC key to close the dialogue
  MobileFiltersModal.prototype.handleKeyDown = function (event) {
    var KEY_TAB = 9
    var KEY_ESC = 27

    switch (event.keyCode) {
      case KEY_TAB:
        if (event.shiftKey) {
          if (document.activeElement === this.$facetsBox) {
            event.preventDefault()
            this.$showResultsButton.focus()
          }
        } else {
          if (document.activeElement === this.$showResultsButton) {
            event.preventDefault()
            this.$facetsBox.focus()
          }
        }
        break
      case KEY_ESC:
        this.$module.close()
        break
      default:
        break
    }
  }

  MobileFiltersModal.prototype.handleClearFilters = function (event) {
    if (event) {
      event.preventDefault()
    }
    // reset all selects, uncheck checkboxes, clear text input values
    // and remove the selected count on each facet
    var $elements = this.$module.querySelectorAll('input, select, .js-selected-counter')
    var $form = document.querySelector('.js-live-search-form')
    var customEvent = document.createEvent('HTMLEvents')
    customEvent.initEvent('customFormChange', true, false)
    for (var i = 0; i < $elements.length; i++) {
      var $el = $elements[i]
      var tagName = $el.tagName
      switch (tagName) {
        case 'INPUT':
          if ($el.type === 'checkbox' && $el.checked === true) {
            $el.checked = false
          } else if ($el.type === 'text' && $el.value !== '') {
            $el.value = ''
          }
          break
        case 'SELECT':
          $el.selectedIndex = null
          break
        case 'DIV':
          $el.parentNode.removeChild($el)
          break
        default:
          break
      }
    }
    // fire a single custom change event on the form once all filters have been cleared
    // so that we only fetch new search-api data once
    $form.dispatchEvent(customEvent)
  }

  Modules.MobileFiltersModal = MobileFiltersModal
})(window.GOVUK.Modules)
<|endoftext|>


<commit_before>/*
 * save_saga.js
 * @flow
 */

import _ from "lodash";
import app from "app";
import Request from "libs/request";
import messages from "messages";
import Toast from "libs/toast";
import { call, put, take, select, race } from "redux-saga/effects";
import { delay } from "redux-saga";
import { shiftSaveQueueAction, setSaveBusyAction, setLastSaveTimestampAction, pushSaveQueueAction, setVersionNumberAction } from "oxalis/model/actions/save_actions";
import { createTreeAction, SkeletonTracingActions } from "oxalis/model/actions/skeletontracing_actions";
import { FlycamActions } from "oxalis/model/actions/flycam_actions";
import { alert } from "libs/window";
import { diffSkeletonTracing } from "oxalis/model/sagas/skeletontracing_saga";
import { diffVolumeTracing } from "oxalis/model/sagas/volumetracing_saga";
import type { UpdateAction } from "oxalis/model/sagas/update_actions";
import type { TracingType, FlycamType } from "oxalis/store";
import { moveTreeComponent } from "oxalis/model/sagas/update_actions";

const PUSH_THROTTLE_TIME = 30000; // 30s
const SAVE_RETRY_WAITING_TIME = 5000;

export function* pushAnnotationAsync(): Generator<*, *, *> {
  yield take(["INITIALIZE_SKELETONTRACING", "INITIALIZE_VOLUMETRACING"]);
  yield put(setLastSaveTimestampAction());
  while (true) {
    const pushAction = yield take("PUSH_SAVE_QUEUE");
    if (!pushAction.pushNow) {
      yield race({
        timeout: call(delay, PUSH_THROTTLE_TIME),
        forcePush: take("SAVE_NOW"),
      });
    }
    yield put(setSaveBusyAction(true));
    const saveQueue = yield select(state => state.save.queue);
    if (saveQueue.length > 0) {
      yield call(sendRequestToServer);
    }
    yield put(setSaveBusyAction(false));
  }
}

export function* sendRequestToServer(timestamp: number = Date.now()): Generator<*, *, *> {
  const batch = yield select(state => state.save.queue);
  const compactBatch = compactUpdateActions(batch);
  const { version, tracingType, tracingId } = yield select(state => state.tracing);
  try {
    yield call(Request.sendJSONReceiveJSON,
      `/annotations/${tracingType}/${tracingId}?version=${version + 1}`, {
        method: "PUT",
        headers: { "X-Date": timestamp },
        data: compactBatch,
      });
    yield put(setVersionNumberAction(version + 1));
    yield put(setLastSaveTimestampAction());
    yield put(shiftSaveQueueAction(batch.length));
    yield call(toggleErrorHighlighting, false);
  } catch (error) {
    yield call(toggleErrorHighlighting, true);
    if (error.status >= 400 && error.status < 500) {
      app.router.off("beforeunload");
      // HTTP Code 409 'conflict' for dirty state
      if (error.status === 409) {
        yield call(alert, messages["save.failed_simultaneous_tracing"]);
      } else {
        yield call(alert, messages["save.failed_client_error"]);
      }
      app.router.reload();
      return;
    }
    yield delay(SAVE_RETRY_WAITING_TIME);
    yield call(sendRequestToServer);
  }
}

export function toggleErrorHighlighting(state: boolean) {
  if (document.body != null) {
    document.body.classList.toggle("save-error", state);
  }
  if (state) {
    Toast.error(messages["save.failed"], true);
  } else {
    Toast.delete("danger", messages["save.failed"]);
  }
}

function cantor(a, b) {
  return 0.5 * (a + b) * (a + b + 1) + b;
}

export function compactUpdateActions(updateActionsBatches: Array<Array<UpdateAction>>): Array<UpdateAction> {
  let result = updateActionsBatches;

  // This part of the code detects tree merges and splits.
  // It does so by identifying nodes and edges that were deleted in one tree only to be created
  // in another tree again afterwards.
  // It replaces the original deleteNode/createNode and deleteEdge/createEdge update actions
  // with a moveTreeComponent update action.
  // As in theory multiple tree merges/splits could be part of one updateActionBatch, the moved nodes
  // and edges have to be grouped by their old and new treeId. Then one moveTreeComponent update action
  // is inserted for each group, containing the respective moved node ids.
  // The exact spot where the moveTreeComponent update action is inserted is important. This is
  // described later.
  result = result.map((batch) => {
    let compactedBatch = batch;
    // Detect moved nodes and edges
    const movedNodesAndEdges = [];
    const deleteNodeActions = batch.filter(ua => ua.action === "deleteNode");
    const deleteEdgeActions = batch.filter(ua => ua.action === "deleteEdge");
    for (const createUA of batch) {
      if (createUA.action === "createNode") {
        const deleteUA = deleteNodeActions.find(ua =>
          // The first predicate will always be true, since we already filtered
          // for that, but we still need it here to satisfy flow :(
          ua.action === "deleteNode" &&
          ua.value.id === createUA.value.id &&
          ua.value.treeId !== createUA.value.treeId);
        if (deleteUA != null && deleteUA.action === "deleteNode") {
          movedNodesAndEdges.push([createUA, deleteUA]);
        }
      } else if (createUA.action === "createEdge") {
        const deleteUA = deleteEdgeActions.find(ua =>
          // The first predicate will always be true, since we already filtered
          // for that, but we still need it here to satisfy flow :(
          ua.action === "deleteEdge" &&
          ua.value.source === createUA.value.source &&
          ua.value.target === createUA.value.target &&
          ua.value.treeId !== createUA.value.treeId);
        if (deleteUA != null && deleteUA.action === "deleteEdge") {
          movedNodesAndEdges.push([createUA, deleteUA]);
        }
      }
    }

    // Group moved nodes and edges by their old and new treeId using the cantor pairing function
    // to create a single unique id
    const groupedMovedNodesAndEdges = _.groupBy(movedNodesAndEdges, ([createUA, deleteUA]) =>
      cantor(createUA.value.treeId, deleteUA.value.treeId));

    // Create a moveTreeComponent update action for each of the groups and insert it at the right spot
    for (const movedPairings of _.values(groupedMovedNodesAndEdges)) {
      const oldTreeId = movedPairings[0][1].value.treeId;
      const newTreeId = movedPairings[0][0].value.treeId;
      const nodeIds = movedPairings
        .filter(([createUA]) => createUA.action === "createNode")
        .map(([createUA]) => createUA.value.id);
      // The moveTreeComponent update action needs to be placed:
      // BEFORE the possible deleteTree update action of the oldTreeId and
      // AFTER the possible createTree update action of the newTreeId
      const deleteTreeUAIndex = compactedBatch.findIndex(ua =>
        ua.action === "deleteTree" &&
        ua.value.id === oldTreeId);
      const createTreeUAIndex = compactedBatch.findIndex(ua =>
        ua.action === "createTree" &&
        ua.value.id === newTreeId);

      if (deleteTreeUAIndex > -1 && createTreeUAIndex > -1) {
        // This should not happen, but in case it does, the moveTreeComponent update action
        // cannot be inserted as the createTreeUA is after the deleteTreeUA
        // Skip the removal of the original create/delete update actions!
        continue;
      } else if (createTreeUAIndex > -1) {
        // Insert after the createTreeUA
        compactedBatch.splice(createTreeUAIndex + 1, 0, moveTreeComponent(oldTreeId, newTreeId, nodeIds));
      } else if (deleteTreeUAIndex > -1) {
        // Insert before the deleteTreeUA
        compactedBatch.splice(deleteTreeUAIndex, 0, moveTreeComponent(oldTreeId, newTreeId, nodeIds));
      } else {
        // Insert in front
        compactedBatch.unshift(moveTreeComponent(oldTreeId, newTreeId, nodeIds));
      }

      // Remove the original create/delete update actions of the moved nodes and edges
      compactedBatch = _.without(compactedBatch, ..._.flatten(movedPairings));
    }

    return compactedBatch;
  });

  // Remove all but the last updateTracing update actions
  let flatResult = _.flatten(result);
  const updateTracingUpdateActions = flatResult.filter(ua => ua.action === "updateTracing");
  if (updateTracingUpdateActions.length > 1) {
    flatResult = _.without(flatResult, ...updateTracingUpdateActions.slice(0, -1));
  }

  return flatResult;
}

export function performDiffTracing(
  prevTracing: TracingType,
  tracing: TracingType,
  flycam: FlycamType,
): Array<UpdateAction> {
  if (tracing.type === "skeleton" && prevTracing.type === "skeleton") {
    return Array.from(diffSkeletonTracing(prevTracing, tracing, flycam));
  } else if (tracing.type === "volume" && prevTracing.type === "volume") {
    return Array.from(diffVolumeTracing(prevTracing, tracing, flycam));
  } else {
    return [];
  }
}

export function* saveTracingAsync(): Generator<*, *, *> {
  const { initSkeleton } = yield race({
    initSkeleton: take("INITIALIZE_SKELETONTRACING"),
    initVolume: take("INITIALIZE_VOLUMETRACING"),
  });
  let prevTracing = yield select(state => state.tracing);
  if (initSkeleton) {
    if (yield select(state => state.tracing.activeTreeId == null)) {
      yield put(createTreeAction());
    }
  }
  yield take("WK_READY");
  const allowUpdate = yield select(state => state.tracing.restrictions.allowUpdate);
  if (!allowUpdate) return;

  while (true) {
    if (initSkeleton) {
      yield take([...SkeletonTracingActions, ...FlycamActions]);
    } else {
      yield take(FlycamActions);
    }
    const tracing = yield select(state => state.tracing);
    const flycam = yield select(state => state.flycam);
    const items = Array.from(yield call(performDiffTracing,
      prevTracing, tracing, flycam));
    if (items.length > 0) {
      yield put(pushSaveQueueAction(items));
    }
    prevTracing = tracing;
  }
}
<commit_msg>avoid manipulating the original save queue batch (#1857)<commit_after>/*
 * save_saga.js
 * @flow
 */

import _ from "lodash";
import app from "app";
import Request from "libs/request";
import messages from "messages";
import Toast from "libs/toast";
import { call, put, take, select, race } from "redux-saga/effects";
import { delay } from "redux-saga";
import { shiftSaveQueueAction, setSaveBusyAction, setLastSaveTimestampAction, pushSaveQueueAction, setVersionNumberAction } from "oxalis/model/actions/save_actions";
import { createTreeAction, SkeletonTracingActions } from "oxalis/model/actions/skeletontracing_actions";
import { FlycamActions } from "oxalis/model/actions/flycam_actions";
import { alert } from "libs/window";
import { diffSkeletonTracing } from "oxalis/model/sagas/skeletontracing_saga";
import { diffVolumeTracing } from "oxalis/model/sagas/volumetracing_saga";
import type { UpdateAction } from "oxalis/model/sagas/update_actions";
import type { TracingType, FlycamType } from "oxalis/store";
import { moveTreeComponent } from "oxalis/model/sagas/update_actions";

const PUSH_THROTTLE_TIME = 30000; // 30s
const SAVE_RETRY_WAITING_TIME = 5000;

export function* pushAnnotationAsync(): Generator<*, *, *> {
  yield take(["INITIALIZE_SKELETONTRACING", "INITIALIZE_VOLUMETRACING"]);
  yield put(setLastSaveTimestampAction());
  while (true) {
    const pushAction = yield take("PUSH_SAVE_QUEUE");
    if (!pushAction.pushNow) {
      yield race({
        timeout: call(delay, PUSH_THROTTLE_TIME),
        forcePush: take("SAVE_NOW"),
      });
    }
    yield put(setSaveBusyAction(true));
    const saveQueue = yield select(state => state.save.queue);
    if (saveQueue.length > 0) {
      yield call(sendRequestToServer);
    }
    yield put(setSaveBusyAction(false));
  }
}

export function* sendRequestToServer(timestamp: number = Date.now()): Generator<*, *, *> {
  const batch = yield select(state => state.save.queue);
  const compactBatch = compactUpdateActions(batch);
  const { version, tracingType, tracingId } = yield select(state => state.tracing);
  try {
    yield call(Request.sendJSONReceiveJSON,
      `/annotations/${tracingType}/${tracingId}?version=${version + 1}`, {
        method: "PUT",
        headers: { "X-Date": timestamp },
        data: compactBatch,
      });
    yield put(setVersionNumberAction(version + 1));
    yield put(setLastSaveTimestampAction());
    yield put(shiftSaveQueueAction(batch.length));
    yield call(toggleErrorHighlighting, false);
  } catch (error) {
    yield call(toggleErrorHighlighting, true);
    if (error.status >= 400 && error.status < 500) {
      app.router.off("beforeunload");
      // HTTP Code 409 'conflict' for dirty state
      if (error.status === 409) {
        yield call(alert, messages["save.failed_simultaneous_tracing"]);
      } else {
        yield call(alert, messages["save.failed_client_error"]);
      }
      app.router.reload();
      return;
    }
    yield delay(SAVE_RETRY_WAITING_TIME);
    yield call(sendRequestToServer);
  }
}

export function toggleErrorHighlighting(state: boolean) {
  if (document.body != null) {
    document.body.classList.toggle("save-error", state);
  }
  if (state) {
    Toast.error(messages["save.failed"], true);
  } else {
    Toast.delete("danger", messages["save.failed"]);
  }
}

function cantor(a, b) {
  return 0.5 * (a + b) * (a + b + 1) + b;
}

export function compactUpdateActions(updateActionsBatches: Array<Array<UpdateAction>>): Array<UpdateAction> {
  // This part of the code detects tree merges and splits.
  // It does so by identifying nodes and edges that were deleted in one tree only to be created
  // in another tree again afterwards.
  // It replaces the original deleteNode/createNode and deleteEdge/createEdge update actions
  // with a moveTreeComponent update action.
  // As in theory multiple tree merges/splits could be part of one updateActionBatch, the moved nodes
  // and edges have to be grouped by their old and new treeId. Then one moveTreeComponent update action
  // is inserted for each group, containing the respective moved node ids.
  // The exact spot where the moveTreeComponent update action is inserted is important. This is
  // described later.
  const result = updateActionsBatches.map((batch) => {
    let compactedBatch = [...batch];
    // Detect moved nodes and edges
    const movedNodesAndEdges = [];
    const deleteNodeActions = batch.filter(ua => ua.action === "deleteNode");
    const deleteEdgeActions = batch.filter(ua => ua.action === "deleteEdge");
    for (const createUA of batch) {
      if (createUA.action === "createNode") {
        const deleteUA = deleteNodeActions.find(ua =>
          // The first predicate will always be true, since we already filtered
          // for that, but we still need it here to satisfy flow :(
          ua.action === "deleteNode" &&
          ua.value.id === createUA.value.id &&
          ua.value.treeId !== createUA.value.treeId);
        if (deleteUA != null && deleteUA.action === "deleteNode") {
          movedNodesAndEdges.push([createUA, deleteUA]);
        }
      } else if (createUA.action === "createEdge") {
        const deleteUA = deleteEdgeActions.find(ua =>
          // The first predicate will always be true, since we already filtered
          // for that, but we still need it here to satisfy flow :(
          ua.action === "deleteEdge" &&
          ua.value.source === createUA.value.source &&
          ua.value.target === createUA.value.target &&
          ua.value.treeId !== createUA.value.treeId);
        if (deleteUA != null && deleteUA.action === "deleteEdge") {
          movedNodesAndEdges.push([createUA, deleteUA]);
        }
      }
    }

    // Group moved nodes and edges by their old and new treeId using the cantor pairing function
    // to create a single unique id
    const groupedMovedNodesAndEdges = _.groupBy(movedNodesAndEdges, ([createUA, deleteUA]) =>
      cantor(createUA.value.treeId, deleteUA.value.treeId));

    // Create a moveTreeComponent update action for each of the groups and insert it at the right spot
    for (const movedPairings of _.values(groupedMovedNodesAndEdges)) {
      const oldTreeId = movedPairings[0][1].value.treeId;
      const newTreeId = movedPairings[0][0].value.treeId;
      const nodeIds = movedPairings
        .filter(([createUA]) => createUA.action === "createNode")
        .map(([createUA]) => createUA.value.id);
      // The moveTreeComponent update action needs to be placed:
      // BEFORE the possible deleteTree update action of the oldTreeId and
      // AFTER the possible createTree update action of the newTreeId
      const deleteTreeUAIndex = compactedBatch.findIndex(ua =>
        ua.action === "deleteTree" &&
        ua.value.id === oldTreeId);
      const createTreeUAIndex = compactedBatch.findIndex(ua =>
        ua.action === "createTree" &&
        ua.value.id === newTreeId);

      if (deleteTreeUAIndex > -1 && createTreeUAIndex > -1) {
        // This should not happen, but in case it does, the moveTreeComponent update action
        // cannot be inserted as the createTreeUA is after the deleteTreeUA
        // Skip the removal of the original create/delete update actions!
        continue;
      } else if (createTreeUAIndex > -1) {
        // Insert after the createTreeUA
        compactedBatch.splice(createTreeUAIndex + 1, 0, moveTreeComponent(oldTreeId, newTreeId, nodeIds));
      } else if (deleteTreeUAIndex > -1) {
        // Insert before the deleteTreeUA
        compactedBatch.splice(deleteTreeUAIndex, 0, moveTreeComponent(oldTreeId, newTreeId, nodeIds));
      } else {
        // Insert in front
        compactedBatch.unshift(moveTreeComponent(oldTreeId, newTreeId, nodeIds));
      }

      // Remove the original create/delete update actions of the moved nodes and edges
      compactedBatch = _.without(compactedBatch, ..._.flatten(movedPairings));
    }

    return compactedBatch;
  });

  // This part of the code removes all but the last updateTracing update actions
  let flatResult = _.flatten(result);
  const updateTracingUpdateActions = flatResult.filter(ua => ua.action === "updateTracing");
  if (updateTracingUpdateActions.length > 1) {
    flatResult = _.without(flatResult, ...updateTracingUpdateActions.slice(0, -1));
  }

  return flatResult;
}

export function performDiffTracing(
  prevTracing: TracingType,
  tracing: TracingType,
  flycam: FlycamType,
): Array<UpdateAction> {
  if (tracing.type === "skeleton" && prevTracing.type === "skeleton") {
    return Array.from(diffSkeletonTracing(prevTracing, tracing, flycam));
  } else if (tracing.type === "volume" && prevTracing.type === "volume") {
    return Array.from(diffVolumeTracing(prevTracing, tracing, flycam));
  } else {
    return [];
  }
}

export function* saveTracingAsync(): Generator<*, *, *> {
  const { initSkeleton } = yield race({
    initSkeleton: take("INITIALIZE_SKELETONTRACING"),
    initVolume: take("INITIALIZE_VOLUMETRACING"),
  });
  let prevTracing = yield select(state => state.tracing);
  if (initSkeleton) {
    if (yield select(state => state.tracing.activeTreeId == null)) {
      yield put(createTreeAction());
    }
  }
  yield take("WK_READY");
  const allowUpdate = yield select(state => state.tracing.restrictions.allowUpdate);
  if (!allowUpdate) return;

  while (true) {
    if (initSkeleton) {
      yield take([...SkeletonTracingActions, ...FlycamActions]);
    } else {
      yield take(FlycamActions);
    }
    const tracing = yield select(state => state.tracing);
    const flycam = yield select(state => state.flycam);
    const items = Array.from(yield call(performDiffTracing,
      prevTracing, tracing, flycam));
    if (items.length > 0) {
      yield put(pushSaveQueueAction(items));
    }
    prevTracing = tracing;
  }
}
<|endoftext|>


<commit_before>import domainSale from '@/assets/images/icons/domain-sale.svg';
import domainSaleHov from '@/assets/images/icons/domain-sale-hov.svg';
import manageEns from '@/assets/images/icons/domain.svg';
import manageEnsHov from '@/assets/images/icons/domain-hov.svg';
import secureTransaction from '@/assets/images/icons/button-key-hover.svg';
import secureTransactionHov from '@/assets/images/icons/button-key.svg';
import scheduleTransaction from '@/assets/images/icons/eac.svg';
import scheduleTransactionHov from '@/assets/images/icons/eac-hov.svg';
import makerdai from '@/assets/images/icons/dapps/makerdai.svg';
import { ETH, GOERLI, ROP, RIN, KOV } from '@/networks/types';

const dapps = {
  manageEns: {
    route: '/interface/dapps/manage-ens',
    icon: manageEns,
    iconDisabled: manageEnsHov,
    title: 'interface.ensManager',
    desc: 'interface.registerENSDescShort',
    supportedNetworks: [ETH.name, GOERLI.name, ROP.name, RIN.name]
  },
  domainSale: {
    route: '/interface/dapps/name-wallet',
    icon: domainSale,
    iconDisabled: domainSaleHov,
    title: 'interface.nameYourWallet',
    desc: 'interface.nameYourWalletDesc',
    supportedNetworks: [ETH.name]
  }
  scheduleTransaction: {
    route: '/interface/dapps/schedule-transaction',
    icon: scheduleTransaction,
    iconDisabled: scheduleTransactionHov,
    title: 'Schedule a transaction',
    desc:
      'Schedule a transaction using the decentralized Ethereum Alarm Clock protocol',
    supportedNetworks: [ETH.name, ROP.name, KOV.name]
  },
  maker: {
    route: '/interface/dapps/maker-dai',
    icon: makerdai,
    iconDisabled: secureTransactionHov,
    title: 'dappsMaker.maker_title',
    desc: 'dappsMaker.maker_desc',
    supportedNetworks: [ETH.name, KOV.name]
  }
};

export default dapps;
<commit_msg>Fix build<commit_after>import domainSale from '@/assets/images/icons/domain-sale.svg';
import domainSaleHov from '@/assets/images/icons/domain-sale-hov.svg';
import manageEns from '@/assets/images/icons/domain.svg';
import manageEnsHov from '@/assets/images/icons/domain-hov.svg';
import secureTransactionHov from '@/assets/images/icons/button-key.svg';
import scheduleTransaction from '@/assets/images/icons/eac.svg';
import scheduleTransactionHov from '@/assets/images/icons/eac-hov.svg';
import makerdai from '@/assets/images/icons/dapps/makerdai.svg';
import { ETH, GOERLI, ROP, RIN, KOV } from '@/networks/types';

const dapps = {
  manageEns: {
    route: '/interface/dapps/manage-ens',
    icon: manageEns,
    iconDisabled: manageEnsHov,
    title: 'interface.ensManager',
    desc: 'interface.registerENSDescShort',
    supportedNetworks: [ETH.name, GOERLI.name, ROP.name, RIN.name]
  },
  domainSale: {
    route: '/interface/dapps/name-wallet',
    icon: domainSale,
    iconDisabled: domainSaleHov,
    title: 'interface.nameYourWallet',
    desc: 'interface.nameYourWalletDesc',
    supportedNetworks: [ETH.name]
  },
  scheduleTransaction: {
    route: '/interface/dapps/schedule-transaction',
    icon: scheduleTransaction,
    iconDisabled: scheduleTransactionHov,
    title: 'Schedule a transaction',
    desc:
      'Schedule a transaction using the decentralized Ethereum Alarm Clock protocol',
    supportedNetworks: [ETH.name, ROP.name, KOV.name]
  },
  maker: {
    route: '/interface/dapps/maker-dai',
    icon: makerdai,
  iconDisabled: secureTransactionHov,
    title: 'dappsMaker.maker_title',
    desc: 'dappsMaker.maker_desc',
    supportedNetworks: [ETH.name, KOV.name]
  }
};

export default dapps;
<|endoftext|>


<commit_before>module.exports = {
	links: [
		{
			href: '/about',
			title: 'About Us',
			value: 'ABOUT US',
		},
		{
			href: '/appguides',
			title: 'Application Guides',
			value: 'APPLICATION GUIDES',
		},
		{
			href: '/becomedealer',
			title: 'Become a Dealer',
			value: 'BECOME A DEALER',
		},
		{
			href: '/buy',
			title: 'Where To Buy',
			value: 'WHERE TO BUY',
		},
		{
			href: 'http://dealers.ariesautomotive.com/',
			title: 'Dealer Login',
			value: 'DEALER LOGIN',
			target: '_blank',
		},
		{
			href: '/news',
			title: 'News',
			value: 'NEWS',
		},
		{
			href: 'http://survey.curtmfg.com/surveys/5',
			title: 'Online Warranties',
			value: 'ONLINE WARRANTIES',
			target: '_blank',
		},
		{
			href: '/contact',
			title: 'Contact Us',
			value: 'CONTACT US',
		},
		{
			href: '/category',
			title: 'View All Products',
			value: 'VIEW ALL PRODUCTS',
		},
		{
			href: 'http://www.curtmfg.com/careers',
			title: 'Careers',
			value: 'CAREERS',
			target: '_blank',
		},
	],
};
<commit_msg>Change careers link to point to curtgroup.com/careers<commit_after>module.exports = {
	links: [
		{
			href: '/about',
			title: 'About Us',
			value: 'ABOUT US',
		},
		{
			href: '/appguides',
			title: 'Application Guides',
			value: 'APPLICATION GUIDES',
		},
		{
			href: '/becomedealer',
			title: 'Become a Dealer',
			value: 'BECOME A DEALER',
		},
		{
			href: '/buy',
			title: 'Where To Buy',
			value: 'WHERE TO BUY',
		},
		{
			href: 'http://dealers.ariesautomotive.com/',
			title: 'Dealer Login',
			value: 'DEALER LOGIN',
			target: '_blank',
		},
		{
			href: '/news',
			title: 'News',
			value: 'NEWS',
		},
		{
			href: 'http://survey.curtmfg.com/surveys/5',
			title: 'Online Warranties',
			value: 'ONLINE WARRANTIES',
			target: '_blank',
		},
		{
			href: '/contact',
			title: 'Contact Us',
			value: 'CONTACT US',
		},
		{
			href: '/category',
			title: 'View All Products',
			value: 'VIEW ALL PRODUCTS',
		},
		{
			href: 'http://www.curtgroup.com/careers',
			title: 'Careers',
			value: 'CAREERS',
			target: '_blank',
		},
	],
};
<|endoftext|>


<commit_before>/*Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file.*/
var officeJsSnippetApp = angular.module("officeJsSnippetApp", ['ngRoute']);
var insideOffice = false;
var consoleErrorFunction;

var rootUrl = document.location;

var logComment = function(message) {
	var consoleElement;
	consoleElement = document.getElementById('console');
	consoleElement.innerHTML += message + '\n';
	consoleElement.scrollTop = consoleElement.scrollHeight;
}

Office.initialize = function (reason) {
	insideOffice = true;
	console.log('Add-in initialized, redirecting console.log() to console textArea');
	consoleErrorFunction = console.error;
	console.error = logComment;
	// get Angular scope from the known DOM element
    var e = document.getElementById('samplesContainer');
    var scope = angular.element(e).scope();
    // update the model with a wrap in $apply(fn) which will refresh the view for us
    scope.$apply(function() {
        scope.insideOffice = true;
    });
};

officeJsSnippetApp.config(['$routeProvider', function ($routeProvider) {
	$routeProvider
		.when('/snippets/:app',
			{
				controller: 'SamplesController',
				templateUrl: 'partials/snippet-browser.html'
			})
		.when('/add-in/:app',
			{
				controller: 'SamplesController',
				templateUrl: 'partials/add-in.html'
			})
		.when('/testAll',
			{
				controller: 'TestAllController',
				templateUrl: 'partials/testAll.html'
			})
		.otherwise({redirectTo: '/snippets/excel' });
}]);

officeJsSnippetApp.factory("snippetFactory", ['$http', function ($http) {
	var factory = {};

	factory.getSamples = function(app) {
		return $http.get(app + '-snippets/samples.json');
	};

	factory.getSampleCode = function(app, filename) {
		return $http.get(app + '-snippets/' + filename);
	};

	return factory;
}]);

officeJsSnippetApp.controller("SamplesController", function($scope, $routeParams, snippetFactory) {
	$scope.samples = [{ name: "Loading..." }];
	$scope.selectedSample = { description: "No snippet loaded" };
	$scope.insideOffice = insideOffice;

	CodeEditorIntegration.initializeJsEditor('TxtRichApiScript', [
			"/editorIntelliSense/ExcelLatest.txt",
			"/editorIntelliSense/WordLatest.txt",
			"/editorIntelliSense/Office.Runtime.txt"
	]);

	CodeEditorIntegration.setDirty = function() {
		if ($scope.selectedSample.code) {
			$scope.selectedSample = { description: $scope.selectedSample.description + " (modified)" };
			$scope.$apply();
		}
	}

	snippetFactory.getSamples($routeParams["app"]).then(function (response) {
		$scope.samples = response.data.values;
		$scope.groups = response.data.groups;
	});

	$scope.loadSampleCode = function() {
		appInsights.trackEvent("SampleLoaded", {name:$scope.selectedSample.name});
		snippetFactory.getSampleCode($routeParams["app"], $scope.selectedSample.filename).then(function (response) {
            $scope.selectedSample.code = addErrorHandlingIfNeeded(response.data);
			$scope.insideOffice = insideOffice;
			CodeEditorIntegration.setJavaScriptText($scope.selectedSample.code);
			CodeEditorIntegration.resizeEditor();
		});
	};

	$scope.runSelectedSample = function() {
		var script = CodeEditorIntegration.getJavaScriptToRun().replace(/console.log/g, "logComment");

		if (isTrulyJavaScript(script)) {
			try {
				eval(script);
			} catch (e) {
				logComment(e.name + ": " + e.message);
			}
		} else {
			CodeEditorIntegration.getEditorTextAsJavaScript().then(function (output) {
				if (output == null) {
					logComment("Invalid JavaScript / TypeScript. Please fix the errors shown in the code editor and try again.");
				} else {
					eval(output.content);
				}
			});
		}
	}
});

officeJsSnippetApp.controller("TestAllController", function($scope, $q, snippetFactory) {
	$scope.insideOffice = insideOffice;

	snippetFactory.getSamples().then(function (response) {
		$scope.samples = response.data.values;
		$scope.groups = response.data.groups;
	});

	$scope.loadSampleCode = function() {
		appInsights.trackEvent("SampleLoaded", {name:$scope.selectedSample.name});

	};

	$scope.runSamples = function() {

		var promiseProducingSampleFunctions = new Array();

		for (var i = 1; i < $scope.samples.length; i++) {
			promiseProducingSampleFunctions.push(createRunSample(i));
		}

		var result = createRunSample(0);
		result = result();
		promiseProducingSampleFunctions.forEach(function (f) {
			result = result.then(f);
		});

		function createRunSample(sampleIndex) {

			var sample = $scope.samples[sampleIndex];

			return function() {
				var deferred = $q.defer();
				//logComment("running next call");
				sample.runStatus = "Loading";
				snippetFactory.getSampleCode(sample.filename).then(function (response) {
					sample.code = addTestResults(addDeferredErrorHandling(response.data)).replace(/console.log/g, "logComment");
					sample.runStatus = "Running";
					try {
						//logComment(sample.code);
						eval(sample.code);
					} catch (e) {
						sample.runStatus = "Error: " + e.name + ": " + e.message;
						deferred.resolve();
					}
				});

				return deferred.promise;
			}
		}
	}

	$scope.refreshResults = function() {
		$scope.$apply();
	}

});

function addTestResults(sampleCode) {
	return sampleCode.replace("console.log(\"done\");", "sample.runStatus = \"Success\"; deferred.resolve();");
}

function addDeferredErrorHandling(sampleCode) {
	return sampleCode.replace("ctx.executeAsync().then();", "ctx.executeAsync().then(function() {\r\n    console.log(\"done\");\r\n}, function(error) {\r\n    sample.runStatus = \"Error: \" + error.errorCode + \":\" + error.errorMessage; deferred.resolve(); });");
}

function addErrorHandling(sampleCode) {
	return sampleCode.replace("ctx.executeAsync().then();", "ctx.executeAsync().then(function() {\r\n    console.log(\"done\");\r\n}, function(error) {\r\n    console.log(\"An error occurred: \" + error.errorCode + \":\" + error.errorMessage);\r\n});");
}

function addErrorHandlingIfNeeded(sampleCode) {
	if (!insideOffice) return sampleCode;
	return addErrorHandling(sampleCode);
}

/** returns whether the text is truly javascript (as opposed to typescript) */
function isTrulyJavaScript(text) {
	try {
		new Function(text);
		return true;
	} catch (syntaxError) {
		return false;
	}
}
/*
OfficeJS Snippet Explorer, https://github.com/OfficeDev/office-js-snippet-explorer

Copyright (c) Microsoft Corporation
All rights reserved.

MIT License:
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
<commit_msg>Added code to remove license text from UI.<commit_after>/*Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file.*/
var officeJsSnippetApp = angular.module("officeJsSnippetApp", ['ngRoute']);
var insideOffice = false;
var consoleErrorFunction;

var rootUrl = document.location;

var logComment = function(message) {
	var consoleElement;
	consoleElement = document.getElementById('console');
	consoleElement.innerHTML += message + '\n';
	consoleElement.scrollTop = consoleElement.scrollHeight;
}

Office.initialize = function (reason) {
	insideOffice = true;
	console.log('Add-in initialized, redirecting console.log() to console textArea');
	consoleErrorFunction = console.error;
	console.error = logComment;
	// get Angular scope from the known DOM element
    var e = document.getElementById('samplesContainer');
    var scope = angular.element(e).scope();
    // update the model with a wrap in $apply(fn) which will refresh the view for us
    scope.$apply(function() {
        scope.insideOffice = true;
    });
};

officeJsSnippetApp.config(['$routeProvider', function ($routeProvider) {
	$routeProvider
		.when('/snippets/:app',
			{
				controller: 'SamplesController',
				templateUrl: 'partials/snippet-browser.html'
			})
		.when('/add-in/:app',
			{
				controller: 'SamplesController',
				templateUrl: 'partials/add-in.html'
			})
		.when('/testAll',
			{
				controller: 'TestAllController',
				templateUrl: 'partials/testAll.html'
			})
		.otherwise({redirectTo: '/snippets/excel' });
}]);

officeJsSnippetApp.factory("snippetFactory", ['$http', function ($http) {
	var factory = {};

	factory.getSamples = function(app) {
		return $http.get(app + '-snippets/samples.json');
	};

	factory.getSampleCode = function(app, filename) {
		return $http.get(app + '-snippets/' + filename);
	};

	return factory;
}]);

officeJsSnippetApp.controller("SamplesController", function($scope, $routeParams, snippetFactory) {
	$scope.samples = [{ name: "Loading..." }];
	$scope.selectedSample = { description: "No snippet loaded" };
	$scope.insideOffice = insideOffice;

	CodeEditorIntegration.initializeJsEditor('TxtRichApiScript', [
			"/editorIntelliSense/ExcelLatest.txt",
			"/editorIntelliSense/WordLatest.txt",
			"/editorIntelliSense/Office.Runtime.txt"
	]);

	CodeEditorIntegration.setDirty = function() {
		if ($scope.selectedSample.code) {
			$scope.selectedSample = { description: $scope.selectedSample.description + " (modified)" };
			$scope.$apply();
		}
	}

	snippetFactory.getSamples($routeParams["app"]).then(function (response) {
		$scope.samples = response.data.values;
		$scope.groups = response.data.groups;
	});

	$scope.loadSampleCode = function() {
		appInsights.trackEvent("SampleLoaded", {name:$scope.selectedSample.name});
		snippetFactory.getSampleCode($routeParams["app"], $scope.selectedSample.filename).then(function (response) {
            var rawCodeSnippet = addErrorHandlingIfNeeded(response.data);
            $scope.selectedSample.code = removeLicenseText(rawCodeSnippet);
			$scope.insideOffice = insideOffice;
			CodeEditorIntegration.setJavaScriptText($scope.selectedSample.code);
			CodeEditorIntegration.resizeEditor();
		});
	};

    function removeLicenseText(rawCodeSnippet) {
        return rawCodeSnippet.replace(/(\/\*([\s\S]*?)\*\/)|(\/\/(.*)$)/g, '');
    }

	$scope.runSelectedSample = function() {
		var script = CodeEditorIntegration.getJavaScriptToRun().replace(/console.log/g, "logComment");

		if (isTrulyJavaScript(script)) {
			try {
				eval(script);
			} catch (e) {
				logComment(e.name + ": " + e.message);
			}
		} else {
			CodeEditorIntegration.getEditorTextAsJavaScript().then(function (output) {
				if (output == null) {
					logComment("Invalid JavaScript / TypeScript. Please fix the errors shown in the code editor and try again.");
				} else {
					eval(output.content);
				}
			});
		}
	}
});

officeJsSnippetApp.controller("TestAllController", function($scope, $q, snippetFactory) {
	$scope.insideOffice = insideOffice;

	snippetFactory.getSamples().then(function (response) {
		$scope.samples = response.data.values;
		$scope.groups = response.data.groups;
	});

	$scope.loadSampleCode = function() {
		appInsights.trackEvent("SampleLoaded", {name:$scope.selectedSample.name});

	};

	$scope.runSamples = function() {

		var promiseProducingSampleFunctions = new Array();

		for (var i = 1; i < $scope.samples.length; i++) {
			promiseProducingSampleFunctions.push(createRunSample(i));
		}

		var result = createRunSample(0);
		result = result();
		promiseProducingSampleFunctions.forEach(function (f) {
			result = result.then(f);
		});

		function createRunSample(sampleIndex) {

			var sample = $scope.samples[sampleIndex];

			return function() {
				var deferred = $q.defer();
				//logComment("running next call");
				sample.runStatus = "Loading";
				snippetFactory.getSampleCode(sample.filename).then(function (response) {
					sample.code = addTestResults(addDeferredErrorHandling(response.data)).replace(/console.log/g, "logComment");
					sample.runStatus = "Running";
					try {
						//logComment(sample.code);
						eval(sample.code);
					} catch (e) {
						sample.runStatus = "Error: " + e.name + ": " + e.message;
						deferred.resolve();
					}
				});

				return deferred.promise;
			}
		}
	}

	$scope.refreshResults = function() {
		$scope.$apply();
	}

});

function addTestResults(sampleCode) {
	return sampleCode.replace("console.log(\"done\");", "sample.runStatus = \"Success\"; deferred.resolve();");
}

function addDeferredErrorHandling(sampleCode) {
	return sampleCode.replace("ctx.executeAsync().then();", "ctx.executeAsync().then(function() {\r\n    console.log(\"done\");\r\n}, function(error) {\r\n    sample.runStatus = \"Error: \" + error.errorCode + \":\" + error.errorMessage; deferred.resolve(); });");
}

function addErrorHandling(sampleCode) {
	return sampleCode.replace("ctx.executeAsync().then();", "ctx.executeAsync().then(function() {\r\n    console.log(\"done\");\r\n}, function(error) {\r\n    console.log(\"An error occurred: \" + error.errorCode + \":\" + error.errorMessage);\r\n});");
}

function addErrorHandlingIfNeeded(sampleCode) {
	if (!insideOffice) return sampleCode;
	return addErrorHandling(sampleCode);
}

/** returns whether the text is truly javascript (as opposed to typescript) */
function isTrulyJavaScript(text) {
	try {
		new Function(text);
		return true;
	} catch (syntaxError) {
		return false;
	}
}
/*
OfficeJS Snippet Explorer, https://github.com/OfficeDev/office-js-snippet-explorer

Copyright (c) Microsoft Corporation
All rights reserved.

MIT License:
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
<|endoftext|>


<commit_before>(function(){
	var ad = function(parent){
		if (parent.tagName != 'CANVAS') {
			throw 'The parent element must be a canvas tag';
		}

		this.parent = parent;

		if (!this.parent.getContext) {
			throw 'This browser does not support the use of canvas';
		}

		this.ctx = this.parent.getContext('2d');
	};

	window.Ad = ad;
})();
<commit_msg>draft generate color<commit_after>(function(){
	var ad = function(parent){
		if (parent.tagName != 'CANVAS') {
			throw 'The parent element must be a canvas tag';
		}

		this.parent = parent;

		if (!this.parent.getContext) {
			throw 'This browser does not support the use of canvas';
		}

		this.ctx = this.parent.getContext('2d');
	};

	var generateColors = function(nb){
		// generate 1 random color
		// generate its opposite color
		// generate nb-2 color equaly dispatched between the boundaries
		// lerp?
	}

	window.Ad = ad;
})();
<|endoftext|>