<commit_before>/**
 * @author R.Akhtyamov
 * https://github.com/Heterotroph/StreamingChart
 */

var charts = {};

(function() {

    /**
     * Example of arguments:
     *  size = {width: 1000, height: 400}
     *  point = {width: 50, height: 1}
     *  axis = {offset: 0, dynamicSpace: {top: 10, bottom: 0}, isDynamic: false}
     *  style = {
     *      background: {color: "#000000", alpha: 0.5},
     *      grid: {thickness: 1, color: "#00FFFF", alpha: 1, width: 1, height: 20, dash: [1, 0]},
     *      axisX:  {thickness: 1, color: "#000000", alpha: 1, offset: 0},
     *      chart: {
     *          lines: {thickness: 3, color: "#000000", alpha: 0.8, bounds: true},
     *          points:  {thickness: 3, radius: 2, lineColor: "#0000FF", fillColor: "#FF0000", alpha: 0.8, bounds: true}
     *      }
     *  }
     */
    function StreamingChart(size, point, axis, style) {
        this.Container_constructor();

        //chart points data
        this._data = [];

        //configuration
        this._size = size;
        this._point = point;
        this._axis = axis;
        this._style = style;

        //dynamic values
        this._widthCapacity = 0;
        this._heightCapacity = 0;
        this._dynamicOffset = axis.offset;
        this._dynamicPoint = {width: point.width, height: point.height};
        this._extremeMax = {value: -Number.MAX_VALUE, age: Number.MAX_VALUE};
        this._extremeMin = {value: Number.MAX_VALUE, age: Number.MAX_VALUE};

        //views
        this._backgroundShape = this.addChild(new createjs.Shape());
        this._gridShape = this.addChild(new createjs.Shape());
        this._axisXShape = this.addChild(new createjs.Shape());
        this._chartShape = this.addChild(new createjs.Shape());
        this._pointShape = this.addChild(new createjs.Shape());

        //apply style configuration
        this.redraw();
    }

    var p = createjs.extend(StreamingChart, createjs.Container);

    //
    //  PUBLIC METHODS
    //

    p.append = function(data) {
        if (data.length === 0) return;
        var totalData = this._data.concat(data);
        this._data = totalData.slice(-this._widthCapacity);

        this._searchExtreme(data);
        this._processExtreme();

        this._clearChartAndPoints();
        this._drawChart(0, this._dynamicPoint.width, this._data, this._style.chart);
    };

    p.clear = function() {
        this._data = [];
        this._clearChartAndPoints();
        this._extremeMax = {value: -Number.MAX_VALUE, age: this._widthCapacity};
        this._extremeMin = {value: Number.MAX_VALUE, age: this._widthCapacity};
        this._moveAxisX(this._style.axisX.offset);
    };

    p.setStyle = function(style) {
        this._style = style;
        this.redraw();
    };

    p.redraw = function() {
        this._calculateCapacity();
        this._drawBackgroundShape(this._size, this._style.background);
        this._updateGrid(this._style.grid);
        this._drawAxisX(this._style.axisX);
        this._updateMask(this._style.chart.lines.bounds, this._size.width, this._size.height);
        this.append(this._data.splice(0, this._data.length));
    };

    p.setPoint = function(width, height) {
        this._dynamicPoint.width = width;
        this._dynamicPoint.height = height;
    };

    p.getPoint = function() {
        return {width: this._dynamicPoint.width, height: this._dynamicPoint.height};
    };

    p.setComplexSize = function(width, height) {
        var widthSegments = this._size.width /  this._dynamicPoint.width;
        widthSegments = Math.ceil(widthSegments * 1000) / 1000; //TODO: Убрать этот костыль и найти красивое решение
        var heightSegments = this._size.height / this._dynamicPoint.height;
        heightSegments = Math.ceil(heightSegments * 1000) / 1000; //TODO: ...
        this.setSize(width, height);
        this.setPoint(this._size.width / widthSegments, this._size.height / heightSegments);
    };

    p.setSize = function(width, height) {
        this._size.width = width;
        this._size.height = height;
    };

    p.getSize = function() {
        return {width: this._size.width, height: this._size.height};
    };

    p.setGrid = function(width, height) {
        this._style.grid.width = width;
        this._style.grid.height = height;
        this._updateGrid(this._style.grid);
    };

    p.getGrid = function() {
        return {width: this._style.grid.width, height: this._style.grid.height};
    };

    p.getData = function() {
        return this._data.slice();
    };

    p.getCapacity = function() {
        return this._widthCapacity;
    };

    p.getInterpolatedValue = function(index) {
        index = Math.round(index * 100) / 100;
        index = Math.min(index, this._data.length - 1);
        index = Math.max(index, 0);
        var intIndex = Math.floor(index);
        if (intIndex == index) return this._data[index];
        var delta = this._data[intIndex + 1] - this._data[intIndex];
        return this._data[intIndex] + delta * (index - intIndex);
    };

    p.getInterpolatedValueByLocalX = function(localX) {
        var index = this.getIndexByLocalX(localX);
        return this.getInterpolatedValue(index);
    };

    p.getExtreme = function() {
        var indexCapacity = Math.min(this._data.length, this._widthCapacity) - 1;
        var min = {
            value: this._extremeMin.value,
            index: indexCapacity - this._extremeMin.age
        };
        var max = {
            value: this._extremeMax.value,
            index: indexCapacity - this._extremeMax.age
        };
        return {min: min, max: max};
    };

    p.getIndexByLocalX = function(localX) {
        return localX / this._dynamicPoint.width;
    };

    p.getLocalXByIndex = function(index) {
        var localX = index * this._dynamicPoint.width;
        return Math.round(localX);
    };

    p.getValueByLocalY = function(localY) {
        return (this._size.height - localY) / this._dynamicPoint.height + this._dynamicOffset;
    };

    p.getLocalYByValue = function(value) {
        var localY = this._size.height - (this._applyOffset(value) * this._dynamicPoint.height);
        return Math.round(localY);
    };

    //
    //  PRIVATE METHODS (VIEW)
    //

    p._drawChart = function(offsetX, stepX, data, style) {
        var aX, aY, bX, bY;
        aX = offsetX;
        aY = this._applyOffset(data[0]) * this._dynamicPoint.height;
        this._chartShape.graphics.setStrokeStyle(style.lines.thickness).beginStroke(style.lines.color);
        this._pointShape.graphics.setStrokeStyle(style.points.thickness).beginStroke(style.points.lineColor);
        if (offsetX === 0) this._drawPoint(0, Math.round(aY), style.points);
        for (var i = 0; i < data.length - 1; i++) {
            bX = Math.round(offsetX + stepX * (i + 1));
            bY = Math.round(this._applyOffset(data[i + 1]) * this._dynamicPoint.height);
            this._drawSegment(aX, aY, bX, bY, style.lines);
            this._drawPoint(bX, bY, style.points);
            aX = bX;
            aY = bY;
        }
        this._chartShape.graphics.endStroke();
        this._pointShape.graphics.endStroke();
    };

    p._drawSegment = function(aX, aY, bX, bY, style) {
        var graphics = this._chartShape.graphics;
        graphics.moveTo(aX, this._size.height - aY).lineTo(bX, this._size.height - bY);
    };

    p._drawPoint = function(x, y, style) {
        var graphics = this._pointShape.graphics;
        if (style.radius === 0) return;
        if (style.bounds && !this._isInsideBounds(x, y)) return;
        graphics.beginFill(style.fillColor);
        graphics.drawCircle(x, this._size.height - y, style.radius);
        graphics.endFill();
    };

    p._drawBackgroundShape = function(size, style) {
        var graphics = this._backgroundShape.graphics.clear();
        if (style.alpha === 0) return;
        graphics.beginFill(style.color);
        graphics.drawRoundRect(0, 0, size.width, size.height, 3);
        this._backgroundShape.alpha = style.alpha;
    };

    p._updateGrid = function(style) {
        var stepX = this._dynamicPoint.width * this._style.grid.width;
        var stepY = this._dynamicPoint.height * this._style.grid.height;
        this._drawGridShape(stepX, stepY, style);
    };

    p._drawGridShape = function(stepX, stepY, style) {
        var graphics = this._gridShape.graphics.clear();
        if (style.alpha === 0) return;
        graphics.setStrokeDash(style.dash);
        graphics.setStrokeStyle(style.thickness).beginStroke(style.color);
        if (stepX) {
            for (var x = stepX; x < this._size.width; x += stepX) {
                graphics.moveTo(x, 0).lineTo(x, this._size.height);
            }
        }
        var gridOffset = (-this._dynamicOffset * this._dynamicPoint.height) % stepY;
        gridOffset = gridOffset < 0 ? gridOffset + stepY : gridOffset;
        if (stepY) {
            for (var y = this._size.height - gridOffset; y >= 0; y -= stepY) {
                graphics.moveTo(0, y).lineTo(this._size.width, y);
            }
        }
        graphics.endStroke();
        this._gridShape.alpha = style.alpha;
    };

    p._updateMask = function(bounds, width, height) {
        if (bounds) {
            this._drawMaskShape(0, 0, width, height);
        } else {
            this._chartShape.mask = null;
        }
    };

    p._drawMaskShape = function(x, y, width, height) {
        this._chartShape.mask = new createjs.Shape();
        this._chartShape.mask.graphics.beginFill("#000000");
        this._chartShape.mask.graphics.drawRect(x, y, width, height);
    };

    p._drawLevelLine = function(shape, thickness, color) {
        var graphics = shape.graphics.clear();
        graphics.setStrokeStyle(thickness).beginStroke(color);
        graphics.moveTo(0, 0).lineTo(this._size.width, 0).endStroke();
    };

    p._drawAxisX = function(style) {
        if (style.alpha === 0) return;
        this._axisXShape.alpha = style.alpha;
        this._drawLevelLine(this._axisXShape, style.thickness, style.color);
        this._moveAxisX(style.offset);
    };

    p._moveAxisX = function(offset) {
        this._axisXShape.y = this._size.height - this._applyOffset(offset) * this._dynamicPoint.height;
        this._axisXShape.visible = this._isInsideBounds(0, this._axisXShape.y);
    };

    p._processExtreme = function() {
        if (!this._axis.isDynamic) return;
        var isDynamicChanged = this._calculateDynamic();
        if (!isDynamicChanged) return;
        this._updateGrid(this._style.grid);
        this._moveAxisX(this._style.axisX.offset);
    };

    p._clearChartAndPoints = function() {
        this._chartShape.graphics.clear();
        this._pointShape.graphics.clear();
    };

    //
    //  PRIVATE METHODS (UTILS)
    //

    p._calculateDynamic = function() {
        var workHeight = this._size.height - this._axis.dynamicSpace.top - this._axis.dynamicSpace.bottom;
        var workDelta = this._extremeMax.value - this._extremeMin.value  - this._axis.offset;
        var tempPointHeight = workHeight / workDelta;
        tempPointHeight = Math.min(tempPointHeight, workHeight);

        var tempAxisOffset = this._extremeMin.value - this._axis.dynamicSpace.bottom / tempPointHeight;

        var result = this._dynamicPoint.height != tempPointHeight || this._dynamicOffset != tempAxisOffset;
        this._dynamicOffset = tempAxisOffset;
        this._dynamicPoint.height = tempPointHeight;
        if (result) this._calculateCapacity();

        return result;
    };

    p._calculateCapacity = function() {
        this._widthCapacity = Math.floor(this._size.width /  this._dynamicPoint.width) + 1;
        this._heightCapacity = Math.floor(this._size.height / this._dynamicPoint.height) + 1;
    };

    p._applyOffset = function(y) {
        return y - this._dynamicOffset;
    };

    p._isInsideBounds = function(x, y) {
        return x >= 0 && x <= this._size.width
            && y >= 0 && y <= this._size.height;
    };

    p._searchExtreme = function(data) {
        var newExtreme;
        this._extremeMax.age += data.length;
        this._extremeMin.age += data.length;
        var isOld = this._extremeMax.age >= this._widthCapacity;
        isOld = isOld || this._extremeMin.age >= this._widthCapacity;
        if (isOld || !data.length) {
            newExtreme = this._getExtreme(Number.MAX_VALUE, -Number.MAX_VALUE, this._data);
        } else {
            newExtreme = this._getExtreme(this._extremeMin.value, this._extremeMax.value, data);
        }
        this._extremeMin = newExtreme.min;
        this._extremeMax = newExtreme.max;
    };

    p._getExtreme = function(min, max, data) {
        var currentMax = {value: max, age: this._widthCapacity};
        var currentMin = {value: min, age: this._widthCapacity};
        for (var i = 0; i < data.length; i++) {
            currentMax.value = Math.max(currentMax.value, data[i]);
            if (currentMax.value == data[i]) currentMax.age = data.length - i - 1;
            currentMin.value = Math.min(currentMin.value, data[i]);
            if (currentMin.value == data[i]) currentMin.age = data.length - i - 1;
        }
        return {min: currentMin, max: currentMax};
    };

    //  ---
    charts.StreamingChart = createjs.promote(StreamingChart, "Container");
}());<commit_msg>Added setOffset and getOffset method<commit_after>/**
 * @author R.Akhtyamov
 * https://github.com/Heterotroph/StreamingChart
 */

var charts = {};

(function() {

    /**
     * Example of arguments:
     *  size = {width: 1000, height: 400}
     *  point = {width: 50, height: 1}
     *  axis = {offset: 0, dynamicSpace: {top: 10, bottom: 0}, isDynamic: false}
     *  style = {
     *      background: {color: "#000000", alpha: 0.5},
     *      grid: {thickness: 1, color: "#00FFFF", alpha: 1, width: 1, height: 20, dash: [1, 0]},
     *      axisX:  {thickness: 1, color: "#000000", alpha: 1, offset: 0},
     *      chart: {
     *          lines: {thickness: 3, color: "#000000", alpha: 0.8, bounds: true},
     *          points:  {thickness: 3, radius: 2, lineColor: "#0000FF", fillColor: "#FF0000", alpha: 0.8, bounds: true}
     *      }
     *  }
     */
    function StreamingChart(size, point, axis, style) {
        this.Container_constructor();

        //chart points data
        this._data = [];

        //configuration
        this._size = size;
        this._point = point;
        this._axis = axis;
        this._style = style;

        //dynamic values
        this._widthCapacity = 0;
        this._heightCapacity = 0;
        this._dynamicOffset = axis.offset;
        this._dynamicPoint = {width: point.width, height: point.height};
        this._extremeMax = {value: -Number.MAX_VALUE, age: Number.MAX_VALUE};
        this._extremeMin = {value: Number.MAX_VALUE, age: Number.MAX_VALUE};

        //views
        this._backgroundShape = this.addChild(new createjs.Shape());
        this._gridShape = this.addChild(new createjs.Shape());
        this._axisXShape = this.addChild(new createjs.Shape());
        this._chartShape = this.addChild(new createjs.Shape());
        this._pointShape = this.addChild(new createjs.Shape());

        //apply style configuration
        this.redraw();
    }

    var p = createjs.extend(StreamingChart, createjs.Container);

    //
    //  PUBLIC METHODS
    //

    p.append = function(data) {
        if (data.length === 0) return;
        var totalData = this._data.concat(data);
        this._data = totalData.slice(-this._widthCapacity);

        this._searchExtreme(data);
        this._processExtreme();

        this._clearChartAndPoints();
        this._drawChart(0, this._dynamicPoint.width, this._data, this._style.chart);
    };

    p.clear = function() {
        this._data = [];
        this._clearChartAndPoints();
        this._extremeMax = {value: -Number.MAX_VALUE, age: this._widthCapacity};
        this._extremeMin = {value: Number.MAX_VALUE, age: this._widthCapacity};
        this._moveAxisX(this._style.axisX.offset);
    };

    p.setStyle = function(style) {
        this._style = style;
        this.redraw();
    };

    p.redraw = function() {
        this.append(this._data.splice(0, this._data.length));
        this._drawBackgroundShape(this._size, this._style.background);
        this._updateGrid(this._style.grid);
        this._drawAxisX(this._style.axisX);
        this._updateMask(this._style.chart.lines.bounds, this._size.width, this._size.height);
    };

    p.setComplexSize = function(width, height) {
        var widthSegments = this._size.width /  this._dynamicPoint.width;
        widthSegments = Math.ceil(widthSegments * 1000) / 1000; //TODO: Убрать этот костыль и найти красивое решение
        var heightSegments = this._size.height / this._dynamicPoint.height;
        heightSegments = Math.ceil(heightSegments * 1000) / 1000; //TODO: ...
        this.setSize(width, height);
        this.setPoint(this._size.width / widthSegments, this._size.height / heightSegments);
    };

    p.setSize = function(width, height) {
        this._size.width = width;
        this._size.height = height;
    };

    p.getSize = function() {
        return {width: this._size.width, height: this._size.height};
    };

    p.setPoint = function(width, height) {
        this._point.width = width;
        this._point.height = height;
        this._dynamicPoint = {width: this._point.width, height: this._point.height};
    };

    p.getPoint = function() {
        return {width: this._dynamicPoint.width, height: this._dynamicPoint.height};
    };

    p.setOffset = function(value) {
        this._axis.offset = value;
        this._dynamicOffset = value;
    };

    p.getOffset = function() {
        return this._dynamicOffset;
    };

    p.setGrid = function(width, height) {
        this._style.grid.width = width;
        this._style.grid.height = height;
        this._updateGrid(this._style.grid);
    };

    p.getGrid = function() {
        return {width: this._style.grid.width, height: this._style.grid.height};
    };

    p.getData = function() {
        return this._data.slice();
    };

    p.getCapacity = function() {
        return this._widthCapacity;
    };

    p.getInterpolatedValue = function(index) {
        index = Math.round(index * 100) / 100;
        index = Math.min(index, this._data.length - 1);
        index = Math.max(index, 0);
        var intIndex = Math.floor(index);
        if (intIndex == index) return this._data[index];
        var delta = this._data[intIndex + 1] - this._data[intIndex];
        return this._data[intIndex] + delta * (index - intIndex);
    };

    p.getInterpolatedValueByLocalX = function(localX) {
        var index = this.getIndexByLocalX(localX);
        return this.getInterpolatedValue(index);
    };

    p.getExtreme = function() {
        var indexCapacity = Math.min(this._data.length, this._widthCapacity) - 1;
        var min = {
            value: this._extremeMin.value,
            index: indexCapacity - this._extremeMin.age
        };
        var max = {
            value: this._extremeMax.value,
            index: indexCapacity - this._extremeMax.age
        };
        return {min: min, max: max};
    };

    p.getIndexByLocalX = function(localX) {
        return localX / this._dynamicPoint.width;
    };

    p.getLocalXByIndex = function(index) {
        var localX = index * this._dynamicPoint.width;
        return Math.round(localX);
    };

    p.getValueByLocalY = function(localY) {
        return (this._size.height - localY) / this._dynamicPoint.height + this._dynamicOffset;
    };

    p.getLocalYByValue = function(value) {
        var localY = this._size.height - (this._applyOffset(value) * this._dynamicPoint.height);
        return Math.round(localY);
    };

    //
    //  PRIVATE METHODS (VIEW)
    //

    p._drawChart = function(offsetX, stepX, data, style) {
        var aX, aY, bX, bY;
        aX = offsetX;
        aY = this._applyOffset(data[0]) * this._dynamicPoint.height;
        this._chartShape.graphics.setStrokeStyle(style.lines.thickness).beginStroke(style.lines.color);
        this._pointShape.graphics.setStrokeStyle(style.points.thickness).beginStroke(style.points.lineColor);
        if (offsetX === 0) this._drawPoint(0, Math.round(aY), style.points);
        for (var i = 0; i < data.length - 1; i++) {
            bX = Math.round(offsetX + stepX * (i + 1));
            bY = Math.round(this._applyOffset(data[i + 1]) * this._dynamicPoint.height);
            this._drawSegment(aX, aY, bX, bY, style.lines);
            this._drawPoint(bX, bY, style.points);
            aX = bX;
            aY = bY;
        }
        this._chartShape.graphics.endStroke();
        this._pointShape.graphics.endStroke();
    };

    p._drawSegment = function(aX, aY, bX, bY, style) {
        var graphics = this._chartShape.graphics;
        graphics.moveTo(aX, this._size.height - aY).lineTo(bX, this._size.height - bY);
    };

    p._drawPoint = function(x, y, style) {
        var graphics = this._pointShape.graphics;
        if (style.radius === 0) return;
        if (style.bounds && !this._isInsideBounds(x, y)) return;
        graphics.beginFill(style.fillColor);
        graphics.drawCircle(x, this._size.height - y, style.radius);
        graphics.endFill();
    };

    p._drawBackgroundShape = function(size, style) {
        var graphics = this._backgroundShape.graphics.clear();
        if (style.alpha === 0) return;
        graphics.beginFill(style.color);
        graphics.drawRoundRect(0, 0, size.width, size.height, 3);
        this._backgroundShape.alpha = style.alpha;
    };

    p._updateGrid = function(style) {
        var stepX = this._dynamicPoint.width * this._style.grid.width;
        var stepY = this._dynamicPoint.height * this._style.grid.height;
        this._drawGridShape(stepX, stepY, style);
    };

    p._drawGridShape = function(stepX, stepY, style) {
        var graphics = this._gridShape.graphics.clear();
        if (style.alpha === 0) return;
        graphics.setStrokeDash(style.dash);
        graphics.setStrokeStyle(style.thickness).beginStroke(style.color);
        if (stepX) {
            for (var x = stepX; x < this._size.width; x += stepX) {
                graphics.moveTo(x, 0).lineTo(x, this._size.height);
            }
        }
        var gridOffset = (-this._dynamicOffset * this._dynamicPoint.height) % stepY;
        gridOffset = gridOffset < 0 ? gridOffset + stepY : gridOffset;
        if (stepY) {
            for (var y = this._size.height - gridOffset; y >= 0; y -= stepY) {
                graphics.moveTo(0, y).lineTo(this._size.width, y);
            }
        }
        graphics.endStroke();
        this._gridShape.alpha = style.alpha;
    };

    p._updateMask = function(bounds, width, height) {
        if (bounds) {
            this._drawMaskShape(0, 0, width, height);
        } else {
            this._chartShape.mask = null;
        }
    };

    p._drawMaskShape = function(x, y, width, height) {
        this._chartShape.mask = new createjs.Shape();
        this._chartShape.mask.graphics.beginFill("#000000");
        this._chartShape.mask.graphics.drawRect(x, y, width, height);
    };

    p._drawLevelLine = function(shape, thickness, color) {
        var graphics = shape.graphics.clear();
        graphics.setStrokeStyle(thickness).beginStroke(color);
        graphics.moveTo(0, 0).lineTo(this._size.width, 0).endStroke();
    };

    p._drawAxisX = function(style) {
        if (style.alpha === 0) return;
        this._axisXShape.alpha = style.alpha;
        this._drawLevelLine(this._axisXShape, style.thickness, style.color);
        this._moveAxisX(style.offset);
    };

    p._moveAxisX = function(offset) {
        this._axisXShape.y = this._size.height - this._applyOffset(offset) * this._dynamicPoint.height;
        this._axisXShape.visible = this._isInsideBounds(0, this._axisXShape.y);
    };

    p._processExtreme = function() {
        if (!this._axis.isDynamic) return;
        var isDynamicChanged = this._calculateDynamic();
        if (!isDynamicChanged) return;
        this._updateGrid(this._style.grid);
        this._moveAxisX(this._style.axisX.offset);
    };

    p._clearChartAndPoints = function() {
        this._chartShape.graphics.clear();
        this._pointShape.graphics.clear();
    };

    //
    //  PRIVATE METHODS (UTILS)
    //

    p._calculateDynamic = function() {
        var workHeight = this._size.height - this._axis.dynamicSpace.top - this._axis.dynamicSpace.bottom;
        var workDelta = this._extremeMax.value - this._extremeMin.value  - this._axis.offset;
        var tempPointHeight = workHeight / workDelta;
        tempPointHeight = Math.min(tempPointHeight, workHeight);

        var tempAxisOffset = this._extremeMin.value - this._axis.dynamicSpace.bottom / tempPointHeight;

        var result = this._dynamicPoint.height != tempPointHeight || this._dynamicOffset != tempAxisOffset;
        this._dynamicOffset = tempAxisOffset;
        this._dynamicPoint.height = tempPointHeight;
        if (result) this._calculateCapacity();

        return result;
    };

    p._calculateCapacity = function() {
        this._widthCapacity = Math.floor(this._size.width /  this._dynamicPoint.width) + 1;
        this._heightCapacity = Math.floor(this._size.height / this._dynamicPoint.height) + 1;
    };

    p._applyOffset = function(y) {
        return y - this._dynamicOffset;
    };

    p._isInsideBounds = function(x, y) {
        return x >= 0 && x <= this._size.width
            && y >= 0 && y <= this._size.height;
    };

    p._searchExtreme = function(data) {
        var newExtreme;
        this._extremeMax.age += data.length;
        this._extremeMin.age += data.length;
        var isOld = this._extremeMax.age >= this._widthCapacity;
        isOld = isOld || this._extremeMin.age >= this._widthCapacity;
        if (isOld || !data.length) {
            newExtreme = this._getExtreme(Number.MAX_VALUE, -Number.MAX_VALUE, this._data);
        } else {
            newExtreme = this._getExtreme(this._extremeMin.value, this._extremeMax.value, data);
        }
        this._extremeMin = newExtreme.min;
        this._extremeMax = newExtreme.max;
    };

    p._getExtreme = function(min, max, data) {
        var currentMax = {value: max, age: this._widthCapacity};
        var currentMin = {value: min, age: this._widthCapacity};
        for (var i = 0; i < data.length; i++) {
            currentMax.value = Math.max(currentMax.value, data[i]);
            if (currentMax.value == data[i]) currentMax.age = data.length - i - 1;
            currentMin.value = Math.min(currentMin.value, data[i]);
            if (currentMin.value == data[i]) currentMin.age = data.length - i - 1;
        }
        return {min: currentMin, max: currentMax};
    };

    //  ---
    charts.StreamingChart = createjs.promote(StreamingChart, "Container");
}());<|endoftext|>


<commit_before>'use strict';

angular.module('OpenTMIControllers')
  .controller('TestcaseListController',
             ['$scope', 'Testcase', '$stateParams', '$log',
    function ($scope,   Testcase,   $stateParams,    $log) {

    $log.info('init TestcaseListController')

    var linkCellTemplate = '
' +
                       '{{ row.entity[col.field] }}' +
                       '
';
    var defaultCellTemplate = '
{{COL_FIELD}}
';
    $scope.columns = [
      { field: 'tcid', /*width:200,*/ enableCellEdit: true, cellTemplate: linkCellTemplate, displayName: 'TC'  },
      { field: 'status.value', width:100, enableCellEdit: true, displayName: 'Status',
        editableCellTemplate: 'ui-grid/dropdownEditor',
        //cellFilter: 'mapStatus',
        editDropdownValueLabel: 'status', editDropdownOptionsArray: [
          { id: 'released', status: 'released' },
          { id: 'development', status: 'development'   },
          { id: 'maintenance', status: 'maintenance' },
          { id: 'unknown', status: 'unknown' }
        ]
      },
      //{ field: 'cre.user', width:200, enableCellEdit: true, displayName: 'Creator' },
      { field: 'owner.name', width:100,
        enableCellEdit: true, displayName: 'Owner' },
      { field: 'other_info.type', width:100,
        enableCellEdit: true, displayName: 'Type' },
      { field: 'other_info.components', width: 100,
        enableCellEdit: true, displayName: 'Components' },
      //{ field: 'other_info.layer', width:50, enableCellEdit: true, displayName: 'Layer',
      // editableCellTemplate: 'ui-grid/dropdownEditor',
      // //cellFilter: 'mapStatus',
      // editDropdownValueLabel: 'status', editDropdownOptionsArray: [
      //   { id: 'L1', status: 'L1' },
      //   { id: 'L2', status: 'L2'   },
      //   { id: 'L12', status: 'L12' },
      //   { id: 'L3', status: 'L3' },
      //   { id: 'unknown', status: 'unknown' },
      // ] },

      //{ field: 'specs', enableCellEdit: true },
      //{ field: 'history.durationAvg', width:100, enableCellEdit: true, cellTemplate: defaultCellTemplate },
    ];
    $scope.gridOptions = {
      columnDefs: $scope.columns,
      enableColumnResizing: true,
      enableFiltering: true,
      //enableRowSelection: true,
      enableFullRowSelection: true,
      multiSelect: false,
      showFooter: true,
      exporterMenuCsv: true,
      enableGridMenu: true
    };

    Object.resolve = function(path, obj, safe) {
        return path.split('.').reduce(function(prev, curr) {
            return !safe ? prev[curr] : (prev ? prev[curr] : undefined)
        }, obj || self)
    }
    var status = {
              dataLength: 0,
              totalDuration: 0 };

    var updateListStatus = function(){
      $scope.$root.$broadcast('tcListStatus', status);
    }

    function doUpdateList(q)
    {
      Testcase.query({q: JSON.stringify(q), f: "tcid execution.estimation.duration status owner other_info.type other_info.components"}).$promise.then(
        function(testcases){
          status.dataLength = testcases.length;
          status.totalDuration = 0;
          var TotalDuration = 0;
          testcases.forEach( function(tc){
            if( Object.resolve('execution.estimation.duration', tc) ) {
                status.totalDuration += Object.resolve('execution.estimation.duration', tc, 0);
            }
          });
          $scope.dataTestcases = testcases;
          updateListStatus();
      });
    }

    $scope.gridOptions.data = 'dataTestcases';
    doUpdateList({});

    $scope.$on('tcFilter', function(event, data) {

      var q = {}
      data.tags.forEach( function(tag){
        if( !q.$and ) q.$and = [];

        var or = [
          {tcid: {"$regex": ("/"+tag+"/"), "$options":"i"}},
          {'other_info.components': {"$regex": ("/"+tag+"/"), "$options":"i"}},
          {'other_info.features': {"$regex": ("/"+tag+"/"), "$options":"i"}},
          {'other_info.type': {"$regex": ("/"+tag+"/"), "$options":"i"}},
          {'status.value': {"$regex": ("/"+tag+"/"), "$options":"i"}},

        ]
        q.$and.push( {$or: or} );
      });
      doUpdateList(q);

    });

    $scope.gridOptions.onRegisterApi = function(gridApi){
      //set gridApi on scope
      $scope.gridApi = gridApi;
      console.log(gridApi.selection)
      gridApi.selection.on.rowSelectionChanged($scope,function(row){
        var msg = 'row selected ' + row.isSelected;
        if( row.isSelected ) {
          $scope.$root.$broadcast('tcStatus', row.entity);
        } else updateListStatus();
      });
      gridApi.edit.on.afterCellEdit($scope,
        function(rowEntity, colDef, newValue, oldValue){

        $log.info('edited tc: ' + rowEntity.tcid + ' Column:' + colDef.name + ' newValue:' + newValue + ' oldValue:' + oldValue);
        //Somewhy this not working property!
        $scope.$root.$broadcast('tcListStatus',
            {lastCellEdited: 'edited tc: ' + rowEntity.tcid + ' Column:' + colDef.name + ' newValue:' + newValue + ' oldValue:' + oldValue});

        rowEntity.$update( function( response ) {
          //$scope.$root.$broadcast('tcListStatus', {error: null});
          $log.info("Update success");
        }, function( error ) {
          $scope.$root.$broadcast('tcListStatus', {error: error});
          $log.info("Update fails");
        });
      });
    };

  }])
  /*
  .filter('mapStatus', function() {
    var statusHash = {
      'released': 'released',
      'develop': 'develop',
      'maintenance': 'maintenance',
      'unknown': 'unknown'
    };

    return function(input) {
      if (!input){
        return '';
      } else {
        return statusHash[input];
      }
    };
  })*/

  ;<commit_msg>Update testcases.list-controller.js<commit_after>'use strict';

angular.module('OpenTMIControllers')
  .controller('TestcaseListController',
             ['$scope', 'Testcase', '$stateParams', '$log',
    function ($scope,   Testcase,   $stateParams,    $log) {

    $log.info('init TestcaseListController')

    var linkCellTemplate = '
' +
                       '{{ row.entity[col.field] }}' +
                       '
';
    var defaultCellTemplate = '
{{COL_FIELD}}
';
    $scope.columns = [
      { field: 'tcid', /*width:200,*/ enableCellEdit: true, cellTemplate: linkCellTemplate, displayName: 'TC'  },
      { field: 'status.value', width:100, enableCellEdit: true, displayName: 'Status',
        editableCellTemplate: 'ui-grid/dropdownEditor',
        //cellFilter: 'mapStatus',
        editDropdownValueLabel: 'status', editDropdownOptionsArray: [
          { id: 'released', status: 'released' },
          { id: 'development', status: 'development'   },
          { id: 'maintenance', status: 'maintenance' },
          { id: 'unknown', status: 'unknown' }
        ]
      },
      //{ field: 'cre.user', width:200, enableCellEdit: true, displayName: 'Creator' },
      { field: 'owner.name', width:100,
        enableCellEdit: true, displayName: 'Owner' },
      { field: 'other_info.type', width:100,
        enableCellEdit: true, displayName: 'Type' },
      { field: 'other_info.components', width: 100,
        enableCellEdit: true, displayName: 'Components' },
      //{ field: 'other_info.layer', width:50, enableCellEdit: true, displayName: 'Layer',
      // editableCellTemplate: 'ui-grid/dropdownEditor',
      // //cellFilter: 'mapStatus',
      // editDropdownValueLabel: 'status', editDropdownOptionsArray: [
      //   { id: 'L1', status: 'L1' },
      //   { id: 'L2', status: 'L2'   },
      //   { id: 'L12', status: 'L12' },
      //   { id: 'L3', status: 'L3' },
      //   { id: 'unknown', status: 'unknown' },
      // ] },

      //{ field: 'specs', enableCellEdit: true },
      //{ field: 'history.durationAvg', width:100, enableCellEdit: true, cellTemplate: defaultCellTemplate },
    ];
    $scope.gridOptions = {
      columnDefs: $scope.columns,
      enableColumnResizing: true,
      enableFiltering: true,
      //enableRowSelection: true,
      enableFullRowSelection: true,
      multiSelect: false,
      showFooter: true,
      exporterMenuCsv: true,
      enableGridMenu: true
    };

    Object.resolve = function(path, obj, safe) {
        return path.split('.').reduce(function(prev, curr) {
            return !safe ? prev[curr] : (prev ? prev[curr] : undefined)
        }, obj || self)
    }
    var status = {
              dataLength: 0,
              totalDuration: 0 };

    var updateListStatus = function(){
      $scope.$root.$broadcast('tcListStatus', status);
    }

    function doUpdateList(q)
    {
      Testcase.query({
          q: JSON.stringify(q),
          f: "tcid execution.estimation.duration status owner other_info.type other_info.components",
          l: 5000
      }).$promise.then(
        function(testcases){
          status.dataLength = testcases.length;
          status.totalDuration = 0;
          var TotalDuration = 0;
          testcases.forEach( function(tc){
            if( Object.resolve('execution.estimation.duration', tc) ) {
                status.totalDuration += Object.resolve('execution.estimation.duration', tc, 0);
            }
          });
          $scope.dataTestcases = testcases;
          updateListStatus();
      });
    }

    $scope.gridOptions.data = 'dataTestcases';
    doUpdateList({});

    $scope.$on('tcFilter', function(event, data) {

      var q = {}
      data.tags.forEach( function(tag){
        if( !q.$and ) q.$and = [];

        var or = [
          {tcid: {"$regex": ("/"+tag+"/"), "$options":"i"}},
          {'other_info.components': {"$regex": ("/"+tag+"/"), "$options":"i"}},
          {'other_info.features': {"$regex": ("/"+tag+"/"), "$options":"i"}},
          {'other_info.type': {"$regex": ("/"+tag+"/"), "$options":"i"}},
          {'status.value': {"$regex": ("/"+tag+"/"), "$options":"i"}},

        ]
        q.$and.push( {$or: or} );
      });
      doUpdateList(q);

    });

    $scope.gridOptions.onRegisterApi = function(gridApi){
      //set gridApi on scope
      $scope.gridApi = gridApi;
      console.log(gridApi.selection)
      gridApi.selection.on.rowSelectionChanged($scope,function(row){
        var msg = 'row selected ' + row.isSelected;
        if( row.isSelected ) {
          $scope.$root.$broadcast('tcStatus', row.entity);
        } else updateListStatus();
      });
      gridApi.edit.on.afterCellEdit($scope,
        function(rowEntity, colDef, newValue, oldValue){

        $log.info('edited tc: ' + rowEntity.tcid + ' Column:' + colDef.name + ' newValue:' + newValue + ' oldValue:' + oldValue);
        //Somewhy this not working property!
        $scope.$root.$broadcast('tcListStatus',
            {lastCellEdited: 'edited tc: ' + rowEntity.tcid + ' Column:' + colDef.name + ' newValue:' + newValue + ' oldValue:' + oldValue});

        rowEntity.$update( function( response ) {
          //$scope.$root.$broadcast('tcListStatus', {error: null});
          $log.info("Update success");
        }, function( error ) {
          $scope.$root.$broadcast('tcListStatus', {error: error});
          $log.info("Update fails");
        });
      });
    };

  }])
  /*
  .filter('mapStatus', function() {
    var statusHash = {
      'released': 'released',
      'develop': 'develop',
      'maintenance': 'maintenance',
      'unknown': 'unknown'
    };

    return function(input) {
      if (!input){
        return '';
      } else {
        return statusHash[input];
      }
    };
  })*/

  ;
<|endoftext|>


<commit_before>'use strict';

const P = require('bluebird');
const HyperSwitch = require('hyperswitch');
const uuid = require('cassandra-uuid').TimeUuid;
const mwUtil = require('../lib/mwUtil');
const URI = HyperSwitch.URI;
const HTTPError = HyperSwitch.HTTPError;

const spec = HyperSwitch.utils.loadSpec(`${__dirname}/feed.yaml`);

const DEFAULT_TTL = 3600;

const FEED_URIS = {
    tfa: { uri: ['v1', 'page', 'featured'], date: true },
    mostread: { uri: ['v1', 'page', 'most-read'], date: true },
    image: { uri: ['v1', 'media', 'image', 'featured'], date: true },
    news: { uri: ['v1', 'page', 'news'], date: false }
};

// TODO: need a way to dynamically derive this
const CONTENT_TYPE = 'application/json; charset=utf-8; ' +
    'profile="https://www.mediawiki.org/wiki/Specs/aggregated-feed/0.5.0"';

/**
 * Checks whether the date is today or in the past in UTC-0 timezone
 *
 * @param {Date} date a date to check
 * @return {boolean} true if the date is in the past
 */
function isHistoric(date) {
    const now = new Date();
    const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
    return date < today;
}

/**
 * Safely builds the Date from request parameters
 *
 * @param {Object} rp request parameters object
 * @return {Date} the requested date.
 */
function getDateSafe(rp) {
    try {
        return new Date(Date.UTC(rp.yyyy, rp.mm - 1, rp.dd));
    } catch (err) {
        throw new HTTPError({
            status: 400,
            body: {
                type: 'bad_request',
                description: 'wrong date format specified'
            }
        });
    }
}

/**
 * Converts the date to the bucket key, key the records in the format YYYY-MM-DD
 *
 * @param {Date} date the date to convert
 * @return {string}
 */
function toKey(date) {
    return date.toISOString().split('T').shift();
}

class Feed {
    constructor(options) {
        this.options = options;
    }

    _makeFeedRequests(parts, hyper, rp, dateArr) {
        const props = {};
        parts.forEach((part) => {
            const def = FEED_URIS[part];
            const uriArray = [this.options.host, rp.domain].concat(def.uri);
            if (def.date) {
                Array.prototype.push.apply(uriArray, dateArr);
            }
            props[part] = hyper.get({
                uri: uriArray.join('/'),
                query: { aggregated: true }
            });
        });
        return P.props(props);
    }

    _assembleResult(result, dateArr) {
        // assemble the final response to be returned
        const finalResult = {
            status: 200,
            headers: {
                'cache-control': this.options.feed_cache_control,
                // mimic MCS' ETag value
                etag: `${dateArr.join('')}/${uuid.now().toString()}`,
                'content-type': CONTENT_TYPE
            },
            body: {}
        };
        // hydrateResponse its body
        Object.keys(result).forEach((key) => {
            if (result[key].body && Object.keys(result[key].body).length) {
                finalResult.body[key] = result[key].body;
            }
        });
        return finalResult;
    }

    aggregated(hyper, req) {
        const rp = req.params;
        const date = getDateSafe(rp);
        const dateKey = toKey(date);
        const dateArr = dateKey.split('-');
        const populateSummaries = (res) => {
            function fetchSummary(uri) {
                return hyper.get({ uri })
                .then((res) => {
                    res.body.normalizedtitle = res.body.title.replace(/_/g, ' ');
                    delete res.body.title; // MSC expects title to be the db-key
                    return res.body;
                })
                // Swallow the error, no need to fail the whole feed
                // request because of one failed summary fetch
                .catchReturn(undefined);
            }
            return mwUtil.hydrateResponse(res, fetchSummary, '$merge');
        };
        // TODO: TEMP CODE: add '$merge' key until the MCS implements it
        const replaceTitleWith$merge = (response) => {
            function _traverse(node) {
                if (Array.isArray(node)) {
                    for (let i = 0; i < node.length; i++) {
                        _traverse(node[i]);
                    }
                } else if (typeof node === 'object') {
                    if (node.title) {
                        node.$merge = [
                            `https://${rp.domain}/api/rest_v1/page/summary/`
                                + `${encodeURIComponent(node.title)}`
                        ];
                    } else {
                        Object.keys(node).forEach((key) => _traverse(node[key]));
                    }
                }
            }
            _traverse(response);
            return response;
        };
        const storeContent = (res, bucket) => {
            return hyper.put({
                uri: new URI([rp.domain, 'sys', 'key_value', bucket, date]),
                headers: res.headers,
                body: res.body
            });
        };
        const getCurrentContent = () => {
            return hyper.get({
                uri: new URI([rp.domain, 'sys', 'key_value', 'feed.aggregated', dateKey])
            })
            .catch({ status: 404 }, () =>
                // it's a cache miss, so we need to request all
                // of the components and store them
                this._makeFeedRequests(Object.keys(FEED_URIS), hyper, rp, dateArr)
                .then((result) => this._assembleResult(result, dateArr))
                .then(replaceTitleWith$merge)
                .tap((res) => {
                    // Store async
                    P.join(
                        storeContent(res, 'feed.aggregated'),
                        storeContent(res, 'feed.aggregated.historic')
                    );
                }));
        };
        const getHistoricContent = () => {
            return hyper.get({
                uri: new URI([rp.domain, 'sys', 'key_value', 'feed.aggregated.historic', dateKey])
            })
            .catch({ status: 404 }, () =>
                // it's a cache miss, so we need to request all
                // of the components and store them (but don't request news)
                this._makeFeedRequests([ 'tfa', 'mostread', 'image' ], hyper, rp, dateArr)
                .then((result) => this._assembleResult(result, dateArr))
                .then(replaceTitleWith$merge)
                .tap((res) => {
                    // Store async
                    storeContent(res, 'feed.aggregated.historic');
                })
            );
        };

        let contentRequest;
        if (isHistoric(date)) {
            contentRequest = getHistoricContent();
        } else {
            contentRequest = getCurrentContent();
        }
        return contentRequest.then(populateSummaries);
    }
}

module.exports = (options) => {
    options.ttl = options.ttl || DEFAULT_TTL;
    options.feed_cache_control = options.feed_cache_control || 's-maxage=30, max-age=15';
    if (!options.host) {
        throw new Error('feed module: host option missing');
    }

    const feed = new Feed(options);

    return {
        spec,
        operations: {
            aggregatedFeed: feed.aggregated.bind(feed)
        },
        resources: [
            {
                uri: '/{domain}/sys/key_value/feed.aggregated',
                body: {
                    version: 2,
                    valueType: 'json',
                    retention_policy: {
                        type: 'ttl',
                        ttl: options.ttl
                    }
                }
            },
            {
                uri: '/{domain}/sys/key_value/feed.aggregated.historic',
                body: {
                    version: 1,
                    valueType: 'json'
                }
            }
        ]
    };
};
<commit_msg>Improved code reuse<commit_after>'use strict';

const P = require('bluebird');
const HyperSwitch = require('hyperswitch');
const uuid = require('cassandra-uuid').TimeUuid;
const mwUtil = require('../lib/mwUtil');
const URI = HyperSwitch.URI;
const HTTPError = HyperSwitch.HTTPError;

const spec = HyperSwitch.utils.loadSpec(`${__dirname}/feed.yaml`);

const DEFAULT_TTL = 3600;

const FEED_URIS = {
    tfa: { uri: ['v1', 'page', 'featured'], date: true },
    mostread: { uri: ['v1', 'page', 'most-read'], date: true },
    image: { uri: ['v1', 'media', 'image', 'featured'], date: true },
    news: { uri: ['v1', 'page', 'news'], date: false }
};

// TODO: need a way to dynamically derive this
const CONTENT_TYPE = 'application/json; charset=utf-8; ' +
    'profile="https://www.mediawiki.org/wiki/Specs/aggregated-feed/0.5.0"';

/**
 * Checks whether the date is today or in the past in UTC-0 timezone
 *
 * @param {Date} date a date to check
 * @return {boolean} true if the date is in the past
 */
function isHistoric(date) {
    const now = new Date();
    const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
    return date < today;
}

/**
 * Safely builds the Date from request parameters
 *
 * @param {Object} rp request parameters object
 * @return {Date} the requested date.
 */
function getDateSafe(rp) {
    try {
        return new Date(Date.UTC(rp.yyyy, rp.mm - 1, rp.dd));
    } catch (err) {
        throw new HTTPError({
            status: 400,
            body: {
                type: 'bad_request',
                description: 'wrong date format specified'
            }
        });
    }
}

/**
 * Converts the date to the bucket key, key the records in the format YYYY-MM-DD
 *
 * @param {Date} date the date to convert
 * @return {string}
 */
function toKey(date) {
    return date.toISOString().split('T').shift();
}

class Feed {
    constructor(options) {
        this.options = options;
    }

    _makeFeedRequests(parts, hyper, rp, dateArr) {
        const props = {};
        parts.forEach((part) => {
            const def = FEED_URIS[part];
            const uriArray = [this.options.host, rp.domain].concat(def.uri);
            if (def.date) {
                Array.prototype.push.apply(uriArray, dateArr);
            }
            props[part] = hyper.get({
                uri: uriArray.join('/'),
                query: { aggregated: true }
            });
        });
        return P.props(props);
    }

    _assembleResult(result, dateArr) {
        // assemble the final response to be returned
        const finalResult = {
            status: 200,
            headers: {
                'cache-control': this.options.feed_cache_control,
                // mimic MCS' ETag value
                etag: `${dateArr.join('')}/${uuid.now().toString()}`,
                'content-type': CONTENT_TYPE
            },
            body: {}
        };
        // hydrateResponse its body
        Object.keys(result).forEach((key) => {
            if (result[key].body && Object.keys(result[key].body).length) {
                finalResult.body[key] = result[key].body;
            }
        });
        return finalResult;
    }

    aggregated(hyper, req) {
        const rp = req.params;
        const date = getDateSafe(rp);
        const dateKey = toKey(date);
        const dateArr = dateKey.split('-');
        const populateSummaries = (res) => {
            function fetchSummary(uri) {
                return hyper.get({ uri })
                .then((res) => {
                    res.body.normalizedtitle = res.body.title.replace(/_/g, ' ');
                    delete res.body.title; // MSC expects title to be the db-key
                    return res.body;
                })
                // Swallow the error, no need to fail the whole feed
                // request because of one failed summary fetch
                .catchReturn(undefined);
            }
            return mwUtil.hydrateResponse(res, fetchSummary, '$merge');
        };
        // TODO: TEMP CODE: add '$merge' key until the MCS implements it
        const replaceTitleWith$merge = (response) => {
            function _traverse(node) {
                if (Array.isArray(node)) {
                    for (let i = 0; i < node.length; i++) {
                        _traverse(node[i]);
                    }
                } else if (typeof node === 'object') {
                    if (node.title) {
                        node.$merge = [
                            `https://${rp.domain}/api/rest_v1/page/summary/`
                                + `${encodeURIComponent(node.title)}`
                        ];
                    } else {
                        Object.keys(node).forEach((key) => _traverse(node[key]));
                    }
                }
            }
            _traverse(response);
            return response;
        };
        const getContent = (bucket) => hyper.get({
            uri: new URI([rp.domain, 'sys', 'key_value', 'feed.aggregated', dateKey])
        });
        const storeContent = (res, bucket) => {
            return hyper.put({
                uri: new URI([rp.domain, 'sys', 'key_value', bucket, date]),
                headers: res.headers,
                body: res.body
            });
        };
        const getCurrentContent = () => {
            return getContent('feed.aggregated')
            .catch({ status: 404 }, () =>
                // it's a cache miss, so we need to request all
                // of the components and store them
                this._makeFeedRequests(Object.keys(FEED_URIS), hyper, rp, dateArr)
                .then((result) => this._assembleResult(result, dateArr))
                .then(replaceTitleWith$merge)
                .tap((res) => {
                    // Store async
                    P.join(
                        storeContent(res, 'feed.aggregated'),
                        storeContent(res, 'feed.aggregated.historic')
                    );
                }));
        };
        const getHistoricContent = () => {
            return getContent('feed.aggregated.historic')
            .catch({ status: 404 }, () =>
                // it's a cache miss, so we need to request all
                // of the components and store them (but don't request news)
                this._makeFeedRequests([ 'tfa', 'mostread', 'image' ], hyper, rp, dateArr)
                .then((result) => this._assembleResult(result, dateArr))
                .then(replaceTitleWith$merge)
                .tap((res) => {
                    // Store async
                    storeContent(res, 'feed.aggregated.historic');
                })
            );
        };

        const contentRequest = isHistoric(date) ? getHistoricContent() : getCurrentContent();
        return contentRequest.then(populateSummaries);
    }
}

module.exports = (options) => {
    options.ttl = options.ttl || DEFAULT_TTL;
    options.feed_cache_control = options.feed_cache_control || 's-maxage=30, max-age=15';
    if (!options.host) {
        throw new Error('feed module: host option missing');
    }

    const feed = new Feed(options);

    return {
        spec,
        operations: {
            aggregatedFeed: feed.aggregated.bind(feed)
        },
        resources: [
            {
                uri: '/{domain}/sys/key_value/feed.aggregated',
                body: {
                    version: 2,
                    valueType: 'json',
                    retention_policy: {
                        type: 'ttl',
                        ttl: options.ttl
                    }
                }
            },
            {
                uri: '/{domain}/sys/key_value/feed.aggregated.historic',
                body: {
                    version: 1,
                    valueType: 'json'
                }
            }
        ]
    };
};
<|endoftext|>


<commit_before>export { Base };

//import * as FloatValue from "../../Unpacker/FloatValue.js";
//import * as ValueDesc from "../../Unpacker/ValueDesc.js";
//import * as Weights from "../../Unpacker/Weights.js";
import * as BoundsArraySet from "../BoundsArraySet.js";
import { Base } from "./Pointwise_Base.js";

//!!! ...unfinished... (2022/05/08)

/**
 *
 */
class Pointwise_SqueezeExcitation extends Pointwise_Base {

}
<commit_msg>Update Pointwise_SqueezeExcitation.js<commit_after>export { Base };

//import * as FloatValue from "../../Unpacker/FloatValue.js";
//import * as ValueDesc from "../../Unpacker/ValueDesc.js";
//import * as Weights from "../../Unpacker/Weights.js";
import * as BoundsArraySet from "../BoundsArraySet.js";
import { Base } from "./Pointwise_Base.js";

//!!! ...unfinished... (2022/05/08) Add squeeze and excitation before pointiwse.
// globale avg pooling - pointwise - pointwise - multiplyToInput
// And the, the original pointwise

/**
 *
 */
class Pointwise_SqueezeExcitation extends Pointwise_Base {

//!!! ...unfinished... (2022/05/08)

  /**
   */
  constructor(
    inputChannelCount, outputChannelCount, bBias, nActivationId,
    nHigherHalfDifferent, inputChannelCount_lowerHalf, outputChannelCount_lowerHalf, channelShuffler_outputGroupCount,

//!!! ...unfinished... (2022/05/08) squeeze channel count? ratio?

    ) {

    super(
      inputChannelCount, outputChannelCount, bBias, nActivationId,
      nHigherHalfDifferent, inputChannelCount_lowerHalf, outputChannelCount_lowerHalf, channelShuffler_outputGroupCount );
  }

  /**
   * @param {Float32Array} inputFloat32Array
   *   A Float32Array whose values will be interpreted as weights.
   *
   * @param {ActivationEscaping.ScaleBoundsArray} inputScaleBoundsArray
   *   The element value bounds (per channel) of input. Usually, it is The .output of the previous convolution-bias-activation value bounds
   * set of this pointwise convolution. It will be kept (not cloned) directly. So caller should not modify them.
   *
   * @param {Array} arrayTemp_forInterleave_asGrouptTwo
   *   A temporary array for placing the original elements temporarily. Provide this array could reduce memory re-allocation
   * and improve performance when doing Interleave_asGrouptTwo.
   *
   * @return {boolean} Return true, if succeeded.
   */
  init( inputFloat32Array, byteOffsetBegin, inputScaleBoundsArray, arrayTemp_forInterleave_asGrouptTwo ) {

//!!! ...unfinished... (2022/05/08)

  }

}
<|endoftext|>


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

	var BadgeComponent = Vue.extend({
		template: "<img :src='url' />",

		props: {
			key: String,
			value: String,
			color: String,
			style: {
				type: String,
				default: "flat",
				// validator: function(value) {
				// 	var validValues = ["flat","plastic","flat-square","social"];
				// 	return (validValues.indexOf(value) > -1);
				// }
			}
		},
		computed: {
			url: function() {
				var url = "https://img.shields.io/badge/" + this.key + "-" + this.value + "-" + this.color + ".svg?style=" + this.style;
				console.log(url);
				return url;
			},
		}
	});
})();

<commit_msg>Remove console logging<commit_after>(function(){
	"use strict";

	var BadgeComponent = Vue.extend({
		template: "<img :src='url' />",

		props: {
			key: String,
			value: String,
			color: String,
			style: {
				type: String,
				default: "flat",
				// validator: function(value) {
				// 	var validValues = ["flat","plastic","flat-square","social"];
				// 	return (validValues.indexOf(value) > -1);
				// }
			}
		},
		computed: {
			url: function() {
				var url = "https://img.shields.io/badge/" + this.key + "-" + this.value + "-" + this.color + ".svg?style=" + this.style;
				return url;
			},
		}
	});
})();

<|endoftext|>


<commit_before>$(function() {

    $("#contactForm input,#contactForm textarea").jqBootstrapValidation({
        preventSubmit: true,
        submitError: function($form, event, errors) {
            // additional error messages or events
        },
        submitSuccess: function($form, event) {
            // Prevent spam click and default submit behaviour
            $("#btnSubmit").attr("disabled", true);
            event.preventDefault();

            // get values from FORM
            var name = $("input#name").val();
            var email = $("input#email").val();
            var phone = $("input#phone").val();
            var message = $("textarea#message").val();
            var firstName = name; // For Success/Failure Message
            // Check for white space in name for Success/Fail message
            if (firstName.indexOf(' ') >= 0) {
                firstName = name.split(' ').slice(0, -1).join(' ');
            }
            $.ajax({
                url: "././mail/contact_me.php",
                type: "POST",
                data: {
                    name: name,
                    phone: phone,
                    email: email,
                    message: message
                },
                cache: false,
                success: function() {
                    // Enable button & show success message
                    $("#btnSubmit").attr("disabled", false);
                    $('#success').html("
");
                    $('#success > .alert-success').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-success')
                        .append("Your message has been sent. ");
                    $('#success > .alert-success')
                        .append('
');

                    //clear all fields
                    $('#contactForm').trigger("reset");
                },
                error: function() {
                    // Fail message
                    $('#success').html("
");
                    $('#success > .alert-danger').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-danger').append("Sorry " + firstName + ", it seems that my mail server is not responding. Please try again later!");
                    $('#success > .alert-danger').append('
');
                    //clear all fields
                    $('#contactForm').trigger("reset");
                },
            })
        },
        filter: function() {
            return $(this).is(":visible");
        },
    });

    $("a[data-toggle=\"tab\"]").click(function(e) {
        e.preventDefault();
        $(this).tab("show");
    });
});

// When clicking on Full hide fail/success boxes
$('#name').focus(function() {
    $('#success').html('');
});
<commit_msg>Delete contact_me.js<commit_after><|endoftext|>


<commit_before>/**
* The quoter tool controller provides a number of functionality around generating quotes
* --------------------------------------
*
* GENERATE A NEW QUOTE, given a totalCost, description, and company information
*
* VIEW EXISTING QUOTE, given a quote ID
*
* EDIT AN EXISTING QUOTE, if user is still in the generate quote process. This means re-visitng the
*     quote url will not allow for editing the quote, only viewing and printing.
*
* PRINT EXISTING QUOTE AS PDF, given a quote ID
*
* START A NEW APPLICATION, by choosing a payment from an existing quote
*
*
*/
angular
    .module('app')
    .controller('quoterToolController', [
        '$rootScope',
        '$scope',
        '$location',
        '$routeParams',
        'quoteService',
        'programService',
        'vendorService',
        'stateService',
        'applicationService',
        '$anchorScroll',
        'resolvedVendor',
        function($rootScope, $scope, $location, $routeParams, Quote, Program, Vendor, States, Application, $anchorScroll, resolvedVendor) {

            // define empty objects
            $scope.quote = {};
            $scope.vendor = {};
            $scope.vendors = [];

            // define variables
            $scope.didQuote = false;
            $scope.buttonText = 'Get Quote';
            $scope.canEdit = true;
            $scope.quoteCost = null;

            // define local store for quote
            var quote = {};

            // Get quoter version
            // @todo this should be replaced with "version directive"
            $scope.version = $rootScope.version;

            // get route params
            var quoteId = $routeParams.id;
            var vendorId = $routeParams.vendor_id;

            // if we don't already have a quote, we need to figure out the vendor
            // lets do some logic to figure this out
            if (!quoteId && vendorId && !resolvedVendor) {

                // we have a vendor, so hide the dropdown
                // if its not valid, we'll handle that later
                $scope.haveVendor = true;

                // get the vendor from API
                getInitialVendor(vendorId);

            } else if(resolvedVendor) {

                $scope.vendor = resolvedVendor;

            } else if (!quoteId) {

                // gets all the vendors so our user can select one!
                getAllVendors();

            }

            // get a single vendor at set as global vendor

            function getInitialVendor(getId) {
                Vendor.getById(getId).then(function(response) {

                    $scope.vendor = response;

                    // not a valid vendor, redirect
                    if (!$scope.vendor) redirectAndClear();

                }, function() {

                    // API returned failure, redirect
                    redirectAndClear();
                });
            }

            // redirect and clear the params using .search() method

            function redirectAndClear() {
                $location.url('tools/quoter');
                $location.search('vendor_id', null);
            }

            // get all the vendors from API
            // @todo this sould only get vendors with quoter enabled

            function getAllVendors() {
                // get the vendors
                Vendor.getAll().then(function(response) {
                    $scope.vendors = response;
                });
            }

            // utility function to go back to the quote list
            // @todo this function is used in many places, find a way to streamline it
            $scope.cancel = function() {
                $location.url('/tools/quoter');
            };

            // get and store the quote
            if (quoteId) {
                // get the quote
                Quote.getById(quoteId).then(function(response) {

                    $scope.quote = response;

                    // get the vendor
                    Vendor.getById($scope.quote.vendorId).then(function(response) {
                        $scope.vendor = response;
                    });

                }, function(error) {
                    $location.path('/tools/quoter');
                });

                $scope.didQuote = true;
                $scope.buttonText = 'Re-calculate Quote';

                // we create a preview link, removing the print param if present
                $scope.permalink = $location.absUrl().replace('/print', '');

                if ($rootScope.previewQuote !== true) $scope.canEdit = false;

            } else {

                //List states in dropdown menu
                // get states list and set default
                $scope.quote.company = {};
                $scope.quote.company.businessAddress = {};
                $scope.states = States.states();
                $scope.quote.company.businessAddress.state = $scope.states[0].abbreviation;

                if ($rootScope.previewQuote === true) $rootScope.previewQuote = false;
            }

            // uncomment to test
            // provides dummy valid quote info
            //
            if(!$scope.quote._id) {
                //$scope.quote = {"company":{"businessAddress":{"state":"NJ","address1":"111 Tree Road","address2":"Apartment 3","city":"Absecon","zip":"19223"},"contactPerson":{"contactMethod":"email","name":"Matt Miller","email":"matt@facultycreative.com","phone":"6093354417"},"fullLegalBusinessName":"Company Name"},"description":"This is some equiptment","totalCost":"3000","vendorId":"51e71518ed32080ffc000023"};
            }

            /**
            * GENERATE A NEW QUOTE
            * --------------------------------------
            * Generates a new quote for given:
            * - totalCost
            * - description
            * - company (not required by api but we provide it with quoter tool)
            *
            * This function works by passing the required info the to API, on success
            * we then redirect user to a page which loads their quote by _id, ie: /tools/quoter/12345
            * This page loads the quote a second time but provides in context editing and viewing
            * for thier quote as they review, print, and update it.
            *
            */
            $scope.generateQuote = function() {

                // check if form is valid
                //   this will trigger any in-valid form items to show
                //   their validation messages
                // if form is valid, then we call a function to save quote
                //
                if ($scope.QuoterToolForm.$valid) {
                    successCallback();
                } else {
                    $rootScope.Validator.validateForm($scope.QuoterToolForm);
                }

                function successCallback() {

                    $scope.didQuote = false;

                    // save the custom Field with the quote
                    // this allows the custom field to live on with the quote
                    // even if that gets changed later for this vendor
                    if ($scope.vendor && $scope.vendor.customField.enabled) {

                        // add empty customField object if not present
                        if(!$scope.quote.customField) $scope.quote.customField = {};

                        // set customField value and displayName
                        $scope.quote.customField = {
                            displayName: $scope.vendor.customField.displayName,
                            value: $scope.quote.customField.value
                        };
                    }

                    if (!quoteId) {

                        $rootScope.previewQuote = true;
                        $scope.quote.vendorId = $scope.vendor._id;

                        // create new quote
                        Quote.add($scope.quote).then(handleReponse);

                    } else {

                        $rootScope.previewQuote = true;

                        // update existing quote
                        Quote.update($scope.quote).then(handleReponse);

                    }
                }

            };

            // function to set error or quote on response from Quote api call
            var handleReponse = function(response) {
                // if a quote was generated
                if(response._id) {

                    $scope.quote = response;

                    $location.url('/tools/quoter/' + response._id);
                    $scope.didQuote = true;

                // no quote was genereated
                } else {

                    $scope.quoteError = response.meta.message;
                    $scope.didQuote = true;
                    $anchorScroll();

                }
            };

            /**
            * START A NEW APPLICATION
            * --------------------------------------
            * Function which allows user to start application by selectiong a payment
            * when viewing an existing quote.
            *
            * Works by generating a new application, saving specific data from the quote, and then
            * redirecting user to this new application page.
            *
            */
            $scope.chooseRate = function(rateObject) {

                /*
                payment: 516.5
                paymentDisplay: "$516.50"
                rate: 0.1033
                term: "12 Months"
                totalCost: 5000
                totalCostDisplay: "$5,000.00"
                */

                var application = _.clone($scope.quote);

                // clear the application id, which will be the quote id because of clone
                delete application._id;

                // save reference to quote id
                application.quoteId = $scope.quote._id;

                // save the rate object as the selected "payment"
                application.payment = rateObject;

                // quotes have an 'Open' status by default while apps have 'new'
                // @todo refactor to make consistant
                application.status = 'new';

                // flag user as coming from a quote
                // if this variable is not true, the application tool currecntly redirects
                // users back home
                // @todo remove this if we open the application tool with "Marlin" as vendor
                //
                $rootScope.fromQuote = true;

                // create new application and redirect on success
                //
                Application.add(application).then(function(response) {

                    // redirec to application tool, which will handle loading the application
                    $location.url('/tools/application/' + response._id);

                });

            };

            /**
             * PRINT EXISTING QUOTE AS PDF
             * --------------------------------------
             * Given a quote id, makes a call to the API which generates a pdf, saves it locally,
             * returns the url of the pdf, and prompts a file download in a cross browser way.
             *
             * @todo If we decide to send the pdf by email, we'd need to refactor this
             *       so that a pdf is generated on quote creation, rather then in this instance.
             *
             */

            // default message
            $scope.downloadMessage = "Download as a PDF";

            // function called on ng-click
            $scope.download = function(id) {

                // why are we providing this?
                id = id || quoteId;

                // message user
                $scope.downloading = true;
                $scope.downloadMessage = "Please wait while we generate your PDF";

                // once the generation is complete, we'll put the download url
                // here. Users can click to download the file
                $scope.downloadURL = null;

                //window.open('api/v1/quote/'+quoteId+'/download');

                Quote.generatePDF(quoteId).then(function(response) {

                    // message user
                    $scope.downloading = false;
                    $scope.downloadMessage = "Download Complete";

                    // handle success and failure
                    if (response.status === 'OK') {
                        SaveToDisk(response.file, 'Quote');
                    } else {
                        $scope.downloadMessage = "Something went wrong. Please try again later.";
                    }

                });

            };

            // cross browser open file in new window prompting download
            //
            // @see http://muaz-khan.blogspot.com/2012/10/save-files-on-disk-using-javascript-or.html
            //
            function SaveToDisk(fileURL, fileName) {
                // for non-IE
                if (!window.ActiveXObject) {
                    var save = document.createElement('a');
                    save.href = fileURL;
                    save.target = '_blank';
                    save.download = fileName || 'unknown';

                    var event = document.createEvent('Event');
                    event.initEvent('click', true, true);
                    save.dispatchEvent(event);
                    (window.URL || window.webkitURL).revokeObjectURL(save.href);
                }

                // for IE
                else if ( !! window.ActiveXObject && document.execCommand) {
                    var _window = window.open(fileURL, '_self');
                    //_window.document.close();
                    //_window.document.execCommand('SaveAs', true, fileName || fileURL);
                    //_window.close();
                }
            }
        }
    ]);
<commit_msg>Refactor quoter tool to ensure things are working fine.<commit_after>/**
* The quoter tool controller provides a number of functionality around generating quotes
* --------------------------------------
*
* GENERATE A NEW QUOTE, given a totalCost, description, and company information
*
* VIEW EXISTING QUOTE, given a quote ID
*
* EDIT AN EXISTING QUOTE, if user is still in the generate quote process. This means re-visitng the
*     quote url will not allow for editing the quote, only viewing and printing.
*
* PRINT EXISTING QUOTE AS PDF, given a quote ID
*
* START A NEW APPLICATION, by choosing a payment from an existing quote
*
*
*/
angular
    .module('app')
    .controller('quoterToolController', [
        '$rootScope',
        '$scope',
        '$location',
        '$routeParams',
        'quoteService',
        'programService',
        'vendorService',
        'stateService',
        'applicationService',
        '$anchorScroll',
        'resolvedVendor',
        function($rootScope, $scope, $location, $routeParams, Quote, Program, Vendor, States, Application, $anchorScroll, resolvedVendor) {

            // define empty objects
            $scope.quote = {};
            $scope.vendor = {};
            $scope.vendors = [];

            // define variables
            $scope.didQuote = false;
            $scope.buttonText = 'Get Quote';
            $scope.canEdit = true;
            $scope.quoteCost = null;

            // define local store for quote
            var quote = {};

            // Get quoter version
            // @todo this should be replaced with "version directive"
            $scope.version = $rootScope.version;

            // get route params
            var quoteId = $routeParams.id;
            var vendorId = $routeParams.vendor_id;

            // if we don't already have a quote, we need to figure out the vendor
            // lets do some logic to figure this out
            if (!quoteId && vendorId && !resolvedVendor) {

                // we have a vendor, so hide the dropdown
                // if its not valid, we'll handle that later
                $scope.haveVendor = true;

                // get the vendor from API
                getInitialVendor(vendorId);

            } else if(resolvedVendor) {

                $scope.vendor = resolvedVendor;

            } else if (!quoteId) {

                // gets all the vendors so our user can select one!
                getAllVendors();

            }

            // get a single vendor at set as global vendor

            function getInitialVendor(getId) {
                Vendor.getById(getId).then(function(response) {

                    $scope.vendor = response;

                    // not a valid vendor, redirect
                    if (!$scope.vendor) redirectAndClear();

                }, function() {

                    // API returned failure, redirect
                    redirectAndClear();
                });
            }

            // redirect and clear the params using .search() method

            function redirectAndClear() {
                $location.url('tools/quoter');
                $location.search('vendor_id', null);
            }

            // get all the vendors from API
            // @todo this sould only get vendors with quoter enabled

            function getAllVendors() {
                // get the vendors
                Vendor.getAll().then(function(response) {
                    $scope.vendors = response;
                });
            }

            // utility function to go back to the quote list
            // @todo this function is used in many places, find a way to streamline it
            $scope.cancel = function() {
                $location.url('/tools/quoter');
            };

            // get and store the quote
            if (quoteId) {
                // get the quote
                Quote.getById(quoteId).then(function(response) {

                    $scope.quote = response;

                }, function(error) {
                    $location.path('/tools/quoter');
                });

                $scope.didQuote = true;
                $scope.buttonText = 'Re-calculate Quote';

                // we create a preview link, removing the print param if present
                $scope.permalink = $location.absUrl().replace('/print', '');

                if ($rootScope.previewQuote !== true) $scope.canEdit = false;

            } else {

                //List states in dropdown menu
                // get states list and set default
                $scope.quote.company = {};
                $scope.quote.company.businessAddress = {};
                $scope.states = States.states();
                $scope.quote.company.businessAddress.state = $scope.states[0].abbreviation;

                if ($rootScope.previewQuote === true) $rootScope.previewQuote = false;
            }

            // uncomment to test
            // provides dummy valid quote info
            //
            if(!$scope.quote._id) {
                //$scope.quote = {"company":{"businessAddress":{"state":"NJ","address1":"111 Tree Road","address2":"Apartment 3","city":"Absecon","zip":"19223"},"contactPerson":{"contactMethod":"email","name":"Matt Miller","email":"matt@facultycreative.com","phone":"6093354417"},"fullLegalBusinessName":"Company Name"},"description":"This is some equiptment","totalCost":"3000","vendorId":"51e71518ed32080ffc000023"};
            }

            /**
            * GENERATE A NEW QUOTE
            * --------------------------------------
            * Generates a new quote for given:
            * - totalCost
            * - description
            * - company (not required by api but we provide it with quoter tool)
            *
            * This function works by passing the required info the to API, on success
            * we then redirect user to a page which loads their quote by _id, ie: /tools/quoter/12345
            * This page loads the quote a second time but provides in context editing and viewing
            * for thier quote as they review, print, and update it.
            *
            */
            $scope.generateQuote = function() {

                // check if form is valid
                //   this will trigger any in-valid form items to show
                //   their validation messages
                // if form is valid, then we call a function to save quote
                //
                if ($scope.QuoterToolForm.$valid) {
                    successCallback();
                } else {
                    $rootScope.Validator.validateForm($scope.QuoterToolForm);
                }

                function successCallback() {

                    $scope.didQuote = false;

                    // save the custom Field with the quote
                    // this allows the custom field to live on with the quote
                    // even if that gets changed later for this vendor
                    if ($scope.vendor && $scope.vendor.customField.enabled) {

                        // add empty customField object if not present
                        if(!$scope.quote.customField) $scope.quote.customField = {};

                        // set customField value and displayName
                        $scope.quote.customField = {
                            displayName: $scope.vendor.customField.displayName,
                            value: $scope.quote.customField.value
                        };
                    }

                    if (!quoteId) {

                        $rootScope.previewQuote = true;

                        // clear vendorId because its an object and reset
                        $scope.quote.vendorId = null;
                        $scope.quote.vendorId = $scope.vendor._id;

                        // create new quote
                        Quote.add($scope.quote).then(handleReponse);

                    } else {

                        $rootScope.previewQuote = true;

                        // clear out the vendor id
                        $scope.quote.vendorId = null;
                        $scope.quote.vendorId = $scope.vendor._id;

                        // update existing quote
                        Quote.update($scope.quote).then(handleReponse);

                    }
                }

            };

            // function to set error or quote on response from Quote api call
            var handleReponse = function(response) {
                // if a quote was generated
                if(response._id) {

                    $scope.quote = response;

                    $location.url('/tools/quoter/' + response._id);
                    $scope.didQuote = true;

                // no quote was genereated
                } else {

                    $scope.quoteError = response.meta.message;
                    $scope.didQuote = true;
                    $anchorScroll();

                }
            };

            /**
            * START A NEW APPLICATION
            * --------------------------------------
            * Function which allows user to start application by selectiong a payment
            * when viewing an existing quote.
            *
            * Works by generating a new application, saving specific data from the quote, and then
            * redirecting user to this new application page.
            *
            */
            $scope.chooseRate = function(rateObject) {

                /*
                payment: 516.5
                paymentDisplay: "$516.50"
                rate: 0.1033
                term: "12 Months"
                totalCost: 5000
                totalCostDisplay: "$5,000.00"
                */

                var application = _.clone($scope.quote);

                // clear the application id, which will be the quote id because of clone
                delete application._id;

                // save reference to quote id
                application.quoteId = $scope.quote._id;

                // save the rate object as the selected "payment"
                application.payment = rateObject;

                // quotes have an 'Open' status by default while apps have 'new'
                // @todo refactor to make consistant
                application.status = 'new';

                // clear out the vendor id
                application.vendorId = null;
                application.vendorId = $scope.vendor._id;

                // flag user as coming from a quote
                // if this variable is not true, the application tool currecntly redirects
                // users back home
                // @todo remove this if we open the application tool with "Marlin" as vendor
                //
                $rootScope.fromQuote = true;

                // create new application and redirect on success
                //
                Application.add(application).then(function(response) {

                    // redirec to application tool, which will handle loading the application
                    $location.url('/tools/application/' + response._id);

                });

            };

            /**
             * PRINT EXISTING QUOTE AS PDF
             * --------------------------------------
             * Given a quote id, makes a call to the API which generates a pdf, saves it locally,
             * returns the url of the pdf, and prompts a file download in a cross browser way.
             *
             * @todo If we decide to send the pdf by email, we'd need to refactor this
             *       so that a pdf is generated on quote creation, rather then in this instance.
             *
             */

            // default message
            $scope.downloadMessage = "Download as a PDF";

            // function called on ng-click
            $scope.download = function(id) {

                // why are we providing this?
                id = id || quoteId;

                // message user
                $scope.downloading = true;
                $scope.downloadMessage = "Please wait while we generate your PDF";

                // once the generation is complete, we'll put the download url
                // here. Users can click to download the file
                $scope.downloadURL = null;

                //window.open('api/v1/quote/'+quoteId+'/download');

                Quote.generatePDF(quoteId).then(function(response) {

                    // message user
                    $scope.downloading = false;
                    $scope.downloadMessage = "Download Complete";

                    // handle success and failure
                    if (response.status === 'OK') {
                        SaveToDisk(response.file, 'Quote');
                    } else {
                        $scope.downloadMessage = "Something went wrong. Please try again later.";
                    }

                });

            };

            // cross browser open file in new window prompting download
            //
            // @see http://muaz-khan.blogspot.com/2012/10/save-files-on-disk-using-javascript-or.html
            //
            function SaveToDisk(fileURL, fileName) {
                // for non-IE
                if (!window.ActiveXObject) {
                    var save = document.createElement('a');
                    save.href = fileURL;
                    save.target = '_blank';
                    save.download = fileName || 'unknown';

                    var event = document.createEvent('Event');
                    event.initEvent('click', true, true);
                    save.dispatchEvent(event);
                    (window.URL || window.webkitURL).revokeObjectURL(save.href);
                }

                // for IE
                else if ( !! window.ActiveXObject && document.execCommand) {
                    var _window = window.open(fileURL, '_self');
                    //_window.document.close();
                    //_window.document.execCommand('SaveAs', true, fileName || fileURL);
                    //_window.close();
                }
            }
        }
    ]);
<|endoftext|>