<commit_before>/**
 * @author R.Akhtyamov
 * 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._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._point.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._point.width = width;
        this._point.height = height;
    };

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

    p.setComplexSize = function(width, height) {
        var widthSegments = this._size.width /  this._point.width;
        widthSegments = Math.ceil(widthSegments * 1000) / 1000; //TODO: Убрать этот костыль и найти красивое решение
        var heightSegments = this._size.height / this._point.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.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._point.width;
    };

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

    p.getValueByLocalY = function(localY) {
        return (this._size.height - localY) / this._point.height + this._axis.offset;
    };

    p.getLocalYByValue = function(value) {
        var localY = this._size.height - (this._applyOffset(value) * this._point.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._point.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._point.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._point.width * this._style.grid.width;
        var stepY = this._point.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._axis.offset * this._point.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._point.height;
        this._axisXShape.visible = this._isInsideBounds(0, this._axisXShape.y);
    };

    p._processExtreme = function() {
        if (!this._axis.isDynamic) return;
        var isAxisOffsetChanged = this._calculateAxisOffset();
        var isPointHeightChanged = this._calculatePointHeight();
        if (!isAxisOffsetChanged && !isPointHeightChanged) 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._calculateAxisOffset = function() {
        var tempOffset = this._extremeMin.value - this._axis.dynamicSpace.bottom;
        var result = this._axis.offset != tempOffset;
        this._axis.offset = tempOffset;
        return result;
    };

    p._calculatePointHeight = function() {
        var newPointHeight = this._point.height;
        newPointHeight = this._size.height / this._applyOffset(this._extremeMax.value + this._axis.dynamicSpace.top);
        newPointHeight = Math.min(newPointHeight, this._size.height);
        var result = this._point.height != newPointHeight;
        this._point.height = newPointHeight;
        this._calculateCapacity();
        return result;
    };

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

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

    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 getCapacity method<commit_after>/**
 * @author R.Akhtyamov
 * 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._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._point.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._point.width = width;
        this._point.height = height;
    };

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

    p.setComplexSize = function(width, height) {
        var widthSegments = this._size.width /  this._point.width;
        widthSegments = Math.ceil(widthSegments * 1000) / 1000; //TODO: Убрать этот костыль и найти красивое решение
        var heightSegments = this._size.height / this._point.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._point.width;
    };

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

    p.getValueByLocalY = function(localY) {
        return (this._size.height - localY) / this._point.height + this._axis.offset;
    };

    p.getLocalYByValue = function(value) {
        var localY = this._size.height - (this._applyOffset(value) * this._point.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._point.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._point.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._point.width * this._style.grid.width;
        var stepY = this._point.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._axis.offset * this._point.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._point.height;
        this._axisXShape.visible = this._isInsideBounds(0, this._axisXShape.y);
    };

    p._processExtreme = function() {
        if (!this._axis.isDynamic) return;
        var isAxisOffsetChanged = this._calculateAxisOffset();
        var isPointHeightChanged = this._calculatePointHeight();
        if (!isAxisOffsetChanged && !isPointHeightChanged) 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._calculateAxisOffset = function() {
        var tempOffset = this._extremeMin.value - this._axis.dynamicSpace.bottom;
        var result = this._axis.offset != tempOffset;
        this._axis.offset = tempOffset;
        return result;
    };

    p._calculatePointHeight = function() {
        var newPointHeight = this._point.height;
        newPointHeight = this._size.height / this._applyOffset(this._extremeMax.value + this._axis.dynamicSpace.top);
        newPointHeight = Math.min(newPointHeight, this._size.height);
        var result = this._point.height != newPointHeight;
        this._point.height = newPointHeight;
        this._calculateCapacity();
        return result;
    };

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

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

    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>// Copyright 2016, University of Colorado Boulder

/**
 * Copies all supporting PhET-iO files, including wrappers, indices, lib files, etc.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 * @author Matt Pennington (PhET Interactive Simulations)
 */
/* eslint-env node */
'use strict';

// modules
const ChipperStringUtils = require( '../../common/ChipperStringUtils' );
const copyDirectory = require( './copyDirectory' );
const grunt = require( 'grunt' );
const minify = require( '../minify' );
const execute = require( '../execute' );

// constants
const DEDICATED_REPO_WRAPPER_PREFIX = 'phet-io-wrapper-';
const WRAPPER_COMMON_FOLDER = 'phet-io-wrappers/common';
const PRODUCTION_SITE = 'phet-io.colorado.edu';
const WRAPPERS_FOLDER = 'wrappers/'; // The index wrapper assumes this constant, please see phet-io-wrappers/index/index.js before changing

// phet-io internal files to be consolidated into 1 file and publicly served as a minified phet-io library.
// Make sure to add new files to the jsdoc generation list below also
const LIB_FILES = [
  '../query-string-machine/js/QueryStringMachine.js', // must be first, other types use this
  '../' + WRAPPER_COMMON_FOLDER + '/js/assert.js',
  '../' + WRAPPER_COMMON_FOLDER + '/js/WrapperTypes.js',
  '../tandem/js/PhetioIDUtils.js',
  '../' + WRAPPER_COMMON_FOLDER + '/js/SimIFrameClient.js'
];

const LIB_OUTPUT_FILE = 'phet-io.js';
const LIB_COPYRIGHT_HEADER = '// Copyright 2002-2017, University of Colorado Boulder\n' +
                             '// This PhET-iO file requires a license\n' +
                             '// USE WITHOUT A LICENSE AGREEMENT IS STRICTLY PROHIBITED.\n' +
                             '// For licensing, please contact phethelp@colorado.edu';

// All libraries and third party files that are used by phet-io wrappers, and need to be copied over for a build
const CONTRIB_FILES = [
  '../sherpa/lib/lodash-4.17.4.min.js',
  '../sherpa/lib/ua-parser-0.7.12.min.js',
  '../sherpa/lib/bootstrap-2.2.2.js',
  '../sherpa/lib/font-awesome-4.5.0',
  '../sherpa/lib/jquery-2.1.0.min.js',
  '../sherpa/lib/jquery-ui-1.8.24.min.js',
  '../sherpa/lib/d3-4.2.2.js',
  '../sherpa/lib/highlight.js-9.1.0/styles/tomorrow-night-bright.css',
  '../sherpa/lib/highlight.js-9.1.0/highlight.js'
];

// list of files to run jsdoc generation with. Assume that anything in the public lib file needs documentation
const JSDOC_FILES = LIB_FILES.concat( [
  ' ../phet-io/js/phet-io-initialize-globals.js',
  ' ../chipper/js/initialize-globals.js'
] );
const JSDOC_README_FILE = '../phet-io/doc/wrapper/phet-io-documentation_README.md';

module.exports = async function( repo, version ) {

  const buildDir = `../${repo}/build/phet-io/`;
  const wrappersLocation = `${buildDir}${WRAPPERS_FOLDER}`;

  // The filter that we run every phet-io wrapper file through to transform dev content into built content. This mainly
  // involves lots of hard coded copy replace of template strings and marker values.
  const filterWrapper = function( abspath, contents ) {
    const originalContents = contents + '';

    const isIndexWrapper = abspath.indexOf( 'index/index.html' ) >= 0;

    if ( abspath.indexOf( '.html' ) >= 0 ) {

      // change the paths of sherpa files to point to the contrib/ folder
      CONTRIB_FILES.forEach( function( filePath ) {

        // No need to do this is this file doesn't have this contrib import in it.
        if ( contents.indexOf( filePath ) >= 0 ) {

          const filePathParts = filePath.split( '/' );

          // If the file is in a dedicated wrapper repo, then it is one level higher in the dir tree, and needs 1 less set of dots.
          // see https://github.com/phetsims/phet-io-wrappers/issues/17 for more info. This is hopefully a temporary workaround
          const needsExtraDots = abspath.indexOf( DEDICATED_REPO_WRAPPER_PREFIX ) >= 0;
          const fileName = filePathParts[ filePathParts.length - 1 ];
          const contribFileName = 'contrib/' + fileName;
          let pathToContrib = needsExtraDots ? '../../' + contribFileName : '../' + contribFileName;

          // The index "wrapper" is a different case because it is placed at the top level of the build dir.
          if ( isIndexWrapper ) {

            pathToContrib = contribFileName;
            filePath = '../' + filePath; // filePath has one less set of relative than are actually in the index.html file.
          }
          contents = ChipperStringUtils.replaceAll( contents, filePath, pathToContrib );
        }
      } );

      /*
       * Remove individual common phet-io code imports because they are all in phetio.js
       */

      // TODO: use LIB_FILES and/or factor this outs
      // This returns the whole line that contains this substring, so it can be removed
      const firstQueryStringLine = ChipperStringUtils.firstLineThatContains( contents, 'QueryStringMachine.js">' );

      // Don't remove the import if it is coming from the phet-io website, only if it is a relative path in requirejs mode.
      if ( firstQueryStringLine && firstQueryStringLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstQueryStringLine, '' ); // included in phetio.js
      }
      const firstAssertLine = ChipperStringUtils.firstLineThatContains( contents, 'assert.js">' );
      if ( firstAssertLine && firstAssertLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstAssertLine, '' ); // included in phetio.js
      }
      const firstIFrameClientLine = ChipperStringUtils.firstLineThatContains( contents, 'SimIFrameClient.js">' );
      if ( firstIFrameClientLine && firstIFrameClientLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstIFrameClientLine, '' ); // included in phetio.js
      }
      const firstWrapperTypeLine = ChipperStringUtils.firstLineThatContains( contents, 'WrapperTypes.js">' );
      if ( firstWrapperTypeLine && firstWrapperTypeLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstWrapperTypeLine, '' ); // included in phetio.js
      }
      const firstPhetioIDUtilsLine = ChipperStringUtils.firstLineThatContains( contents, 'PhetioIDUtils.js' );
      if ( firstPhetioIDUtilsLine && firstPhetioIDUtilsLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstPhetioIDUtilsLine, '' ); // included in phetio.js
      }

      // Support wrappers that use code from phet-io-wrappers
      contents = ChipperStringUtils.replaceAll( contents, '/phet-io-wrappers/', '/' );

      // For info about LIB_OUTPUT_FILE, see handleLib()
      let pathToLib = `lib/${LIB_OUTPUT_FILE}`;

      // index wrapper is moved to the top level of build dir, and needs no relative dots.
      pathToLib = isIndexWrapper ? pathToLib : `../../${pathToLib}`;
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{phet-io.js}}-->',
        `<script src="${pathToLib}"></script>`
      );

      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{GOOGLE_ANALYTICS.js}}-->',
        '<script src="/assets/js/phet-io-ga.js"></script>'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{FAVICON.ico}}-->',
        '<link rel="shortcut icon" href="/assets/favicon.ico"/>'
      );
    }
    if ( abspath.indexOf( '.js' ) >= 0 || abspath.indexOf( '.html' ) >= 0 ) {
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_NAME}}', repo );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_VERSION}}', version );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_IS_BUILT}}', 'true' );

      // phet-io-wrappers/common will be in the top level of wrappers/ in the build directory
      contents = ChipperStringUtils.replaceAll( contents, WRAPPER_COMMON_FOLDER + '/', 'common/' );
    }
    if ( contents !== originalContents ) {
      return contents;
    }
    else {
      return null; // signify no change (helps for images)
    }
  };

  // Load the master list of all wrappers
  // TODO: Should this be in a file? Would we be duplicating perennial? We want a reproducible build
  const wrappers = [
    'phet-io-wrappers/active',
    'phet-io-wrappers/event-log',
    'phet-io-wrappers/studio',
    'phet-io-wrappers/login',
    'phet-io-wrappers/mirror-inputs',
    'phet-io-wrappers/multi',
    'phet-io-wrappers/record',
    'phet-io-wrappers/playback',
    'phet-io-wrappers/screenshot',
    'phet-io-wrappers/state',
    'phet-io-wrappers/wrapper-template',
    'phet-io-wrapper-classroom-activity',
    'phet-io-wrapper-lab-book',
    'phet-io-wrapper-hookes-law-energy'
  ];

  // Files and directories from wrapper folders that we don't want to copy
  const wrappersBlacklist = [ '.git', 'README.md', '.gitignore', 'node_modules', 'package.json', 'build' ];

  const libFileNames = LIB_FILES.map( function( filePath ) {
    const parts = filePath.split( '/' );
    return parts[ parts.length - 1 ];
  } );

  // Don't copy over the files that are in the lib file, this way we can catch wrapper bugs that are not pointing to the lib.
  const fullBlacklist = wrappersBlacklist.concat( libFileNames );

  // wrapping function for copying the wrappers to the build dir
  const copyWrapper = function( src, dest ) {
    copyDirectory( src, dest, filterWrapper, {
      blacklist: fullBlacklist
    } );
  };

  // Make sure to copy the phet-io-wrappers common wrapper code too.
  wrappers.push( WRAPPER_COMMON_FOLDER );
  wrappers.forEach( function( wrapper ) {

    const wrapperParts = wrapper.split( '/' );

    // either take the last path part, or take the first (repo name) and remove the wrapper prefix
    const wrapperName = wrapperParts.length > 1 ? wrapperParts[ wrapperParts.length - 1 ] : wrapperParts[ 0 ].replace( DEDICATED_REPO_WRAPPER_PREFIX, '' );

    // Copy the wrapper into the build dir /wrappers/, exclude the blacklist
    copyWrapper( `../${wrapper}`, `${wrappersLocation}${wrapperName}` );
  } );

  // Copy the index wrapper into the top level of the build dir, exclude the blacklist
  copyWrapper( '../phet-io-wrappers/index', `${buildDir}` );

  // Create the lib file that is minified and publicly available under the /lib folder of the build
  handleLib( buildDir, filterWrapper );

  // Create the contrib folder and add to it third party libraries used by wrappers.
  handleContrib( buildDir );

  // Create the rendered jsdoc in the `doc` folder
  await handleJSDOC( buildDir );
};

/**
 * Given the list of lib files, apply a filter function to them. Then minify them and consolidate into a single string.
 * Finally write them to the build dir with a license prepended. See https://github.com/phetsims/phet-io/issues/353

 * @param {string} buildDir
 * @param {Function} filter - the filter function used when copying over wrapper files to fix relative paths and such.
 *                            Has arguments like "function(abspath, contents)"
 */
const handleLib = function( buildDir, filter ) {
  grunt.log.debug( 'Creating phet-io lib file from: ', LIB_FILES );

  grunt.file.mkdir( `${buildDir}lib` );

  let consolidated = '';
  LIB_FILES.forEach( function( libFile ) {
    const contents = grunt.file.read( libFile );

    const filteredContents = filter && filter( libFile, contents );

    // The filter should return null if nothing changes
    consolidated += filteredContents ? filteredContents : contents;
  } );

  const minified = minify( consolidated, {
    stripAssertions: false
  } );

  grunt.file.write( `${buildDir}lib/${LIB_OUTPUT_FILE}`, LIB_COPYRIGHT_HEADER + '\n\n' + minified );
};

/**
 * Copy all of the third party libraries from sherpa to the build directory under the 'contrib' folder.
 * @param {string} buildDir
 */
const handleContrib = function( buildDir ) {
  grunt.log.debug( 'Creating phet-io contrib folder' );

  CONTRIB_FILES.forEach( function( filePath ) {
    const filePathParts = filePath.split( '/' );

    const filename = filePathParts[ filePathParts.length - 1 ];

    grunt.file.copy( filePath, `${buildDir}contrib/${filename}` );

  } );
};

/**
 * Generate jsdoc and put it in "build/phet-io/doc"
 * @param {string} buildDir
 * @returns {Promise<void>}
 */
const handleJSDOC = async function( buildDir ) {
  grunt.log.debug( 'generating jsdoc for phet-io from: ', JSDOC_FILES );

  // First we tried to run the jsdoc binary as the cmd, but that wasn't working, and was quite finicky. Then @samreid
  // found https://stackoverflow.com/questions/33664843/how-to-use-jsdoc-with-gulp which recommends the following method
  // (node executable with jsdoc js file)
  await execute( 'node', [ '../chipper/node_modules/jsdoc/jsdoc.js' ].concat(
    JSDOC_FILES.concat( [ '-c', '../phet-io/doc/wrapper/jsdoc-config.json',
      '-d', `${buildDir}doc/`, '--readme', JSDOC_README_FILE ] ) ),
    { cwd: process.cwd(), shell: true } );
};<commit_msg>rename SimIFrameClient --> Client, https://github.com/phetsims/phet-io-wrappers/issues/127<commit_after>// Copyright 2016, University of Colorado Boulder

/**
 * Copies all supporting PhET-iO files, including wrappers, indices, lib files, etc.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 * @author Matt Pennington (PhET Interactive Simulations)
 */
/* eslint-env node */
'use strict';

// modules
const ChipperStringUtils = require( '../../common/ChipperStringUtils' );
const copyDirectory = require( './copyDirectory' );
const grunt = require( 'grunt' );
const minify = require( '../minify' );
const execute = require( '../execute' );

// constants
const DEDICATED_REPO_WRAPPER_PREFIX = 'phet-io-wrapper-';
const WRAPPER_COMMON_FOLDER = 'phet-io-wrappers/common';
const PRODUCTION_SITE = 'phet-io.colorado.edu';
const WRAPPERS_FOLDER = 'wrappers/'; // The index wrapper assumes this constant, please see phet-io-wrappers/index/index.js before changing

// phet-io internal files to be consolidated into 1 file and publicly served as a minified phet-io library.
// Make sure to add new files to the jsdoc generation list below also
const LIB_FILES = [
  '../query-string-machine/js/QueryStringMachine.js', // must be first, other types use this
  '../' + WRAPPER_COMMON_FOLDER + '/js/assert.js',
  '../' + WRAPPER_COMMON_FOLDER + '/js/WrapperTypes.js',
  '../tandem/js/PhetioIDUtils.js',
  '../' + WRAPPER_COMMON_FOLDER + '/js/Client.js'
];

const LIB_OUTPUT_FILE = 'phet-io.js';
const LIB_COPYRIGHT_HEADER = '// Copyright 2002-2017, University of Colorado Boulder\n' +
                             '// This PhET-iO file requires a license\n' +
                             '// USE WITHOUT A LICENSE AGREEMENT IS STRICTLY PROHIBITED.\n' +
                             '// For licensing, please contact phethelp@colorado.edu';

// All libraries and third party files that are used by phet-io wrappers, and need to be copied over for a build
const CONTRIB_FILES = [
  '../sherpa/lib/lodash-4.17.4.min.js',
  '../sherpa/lib/ua-parser-0.7.12.min.js',
  '../sherpa/lib/bootstrap-2.2.2.js',
  '../sherpa/lib/font-awesome-4.5.0',
  '../sherpa/lib/jquery-2.1.0.min.js',
  '../sherpa/lib/jquery-ui-1.8.24.min.js',
  '../sherpa/lib/d3-4.2.2.js',
  '../sherpa/lib/highlight.js-9.1.0/styles/tomorrow-night-bright.css',
  '../sherpa/lib/highlight.js-9.1.0/highlight.js'
];

// list of files to run jsdoc generation with. Assume that anything in the public lib file needs documentation
const JSDOC_FILES = LIB_FILES.concat( [
  ' ../phet-io/js/phet-io-initialize-globals.js',
  ' ../chipper/js/initialize-globals.js'
] );
const JSDOC_README_FILE = '../phet-io/doc/wrapper/phet-io-documentation_README.md';

module.exports = async function( repo, version ) {

  const buildDir = `../${repo}/build/phet-io/`;
  const wrappersLocation = `${buildDir}${WRAPPERS_FOLDER}`;

  // The filter that we run every phet-io wrapper file through to transform dev content into built content. This mainly
  // involves lots of hard coded copy replace of template strings and marker values.
  const filterWrapper = function( abspath, contents ) {
    const originalContents = contents + '';

    const isIndexWrapper = abspath.indexOf( 'index/index.html' ) >= 0;

    if ( abspath.indexOf( '.html' ) >= 0 ) {

      // change the paths of sherpa files to point to the contrib/ folder
      CONTRIB_FILES.forEach( function( filePath ) {

        // No need to do this is this file doesn't have this contrib import in it.
        if ( contents.indexOf( filePath ) >= 0 ) {

          const filePathParts = filePath.split( '/' );

          // If the file is in a dedicated wrapper repo, then it is one level higher in the dir tree, and needs 1 less set of dots.
          // see https://github.com/phetsims/phet-io-wrappers/issues/17 for more info. This is hopefully a temporary workaround
          const needsExtraDots = abspath.indexOf( DEDICATED_REPO_WRAPPER_PREFIX ) >= 0;
          const fileName = filePathParts[ filePathParts.length - 1 ];
          const contribFileName = 'contrib/' + fileName;
          let pathToContrib = needsExtraDots ? '../../' + contribFileName : '../' + contribFileName;

          // The index "wrapper" is a different case because it is placed at the top level of the build dir.
          if ( isIndexWrapper ) {

            pathToContrib = contribFileName;
            filePath = '../' + filePath; // filePath has one less set of relative than are actually in the index.html file.
          }
          contents = ChipperStringUtils.replaceAll( contents, filePath, pathToContrib );
        }
      } );

      /*
       * Remove individual common phet-io code imports because they are all in phetio.js
       */

      // TODO: use LIB_FILES and/or factor this outs
      // This returns the whole line that contains this substring, so it can be removed
      const firstQueryStringLine = ChipperStringUtils.firstLineThatContains( contents, 'QueryStringMachine.js">' );

      // Don't remove the import if it is coming from the phet-io website, only if it is a relative path in requirejs mode.
      if ( firstQueryStringLine && firstQueryStringLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstQueryStringLine, '' ); // included in phetio.js
      }
      const firstAssertLine = ChipperStringUtils.firstLineThatContains( contents, 'assert.js">' );
      if ( firstAssertLine && firstAssertLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstAssertLine, '' ); // included in phetio.js
      }
      const firstIFrameClientLine = ChipperStringUtils.firstLineThatContains( contents, 'Client.js">' );
      if ( firstIFrameClientLine && firstIFrameClientLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstIFrameClientLine, '' ); // included in phetio.js
      }
      const firstWrapperTypeLine = ChipperStringUtils.firstLineThatContains( contents, 'WrapperTypes.js">' );
      if ( firstWrapperTypeLine && firstWrapperTypeLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstWrapperTypeLine, '' ); // included in phetio.js
      }
      const firstPhetioIDUtilsLine = ChipperStringUtils.firstLineThatContains( contents, 'PhetioIDUtils.js' );
      if ( firstPhetioIDUtilsLine && firstPhetioIDUtilsLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstPhetioIDUtilsLine, '' ); // included in phetio.js
      }

      // Support wrappers that use code from phet-io-wrappers
      contents = ChipperStringUtils.replaceAll( contents, '/phet-io-wrappers/', '/' );

      // For info about LIB_OUTPUT_FILE, see handleLib()
      let pathToLib = `lib/${LIB_OUTPUT_FILE}`;

      // index wrapper is moved to the top level of build dir, and needs no relative dots.
      pathToLib = isIndexWrapper ? pathToLib : `../../${pathToLib}`;
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{phet-io.js}}-->',
        `<script src="${pathToLib}"></script>`
      );

      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{GOOGLE_ANALYTICS.js}}-->',
        '<script src="/assets/js/phet-io-ga.js"></script>'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{FAVICON.ico}}-->',
        '<link rel="shortcut icon" href="/assets/favicon.ico"/>'
      );
    }
    if ( abspath.indexOf( '.js' ) >= 0 || abspath.indexOf( '.html' ) >= 0 ) {
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_NAME}}', repo );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_VERSION}}', version );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_IS_BUILT}}', 'true' );

      // phet-io-wrappers/common will be in the top level of wrappers/ in the build directory
      contents = ChipperStringUtils.replaceAll( contents, WRAPPER_COMMON_FOLDER + '/', 'common/' );
    }
    if ( contents !== originalContents ) {
      return contents;
    }
    else {
      return null; // signify no change (helps for images)
    }
  };

  // Load the master list of all wrappers
  // TODO: Should this be in a file? Would we be duplicating perennial? We want a reproducible build
  const wrappers = [
    'phet-io-wrappers/active',
    'phet-io-wrappers/event-log',
    'phet-io-wrappers/studio',
    'phet-io-wrappers/login',
    'phet-io-wrappers/mirror-inputs',
    'phet-io-wrappers/multi',
    'phet-io-wrappers/record',
    'phet-io-wrappers/playback',
    'phet-io-wrappers/screenshot',
    'phet-io-wrappers/state',
    'phet-io-wrappers/wrapper-template',
    'phet-io-wrapper-classroom-activity',
    'phet-io-wrapper-lab-book',
    'phet-io-wrapper-hookes-law-energy'
  ];

  // Files and directories from wrapper folders that we don't want to copy
  const wrappersBlacklist = [ '.git', 'README.md', '.gitignore', 'node_modules', 'package.json', 'build' ];

  const libFileNames = LIB_FILES.map( function( filePath ) {
    const parts = filePath.split( '/' );
    return parts[ parts.length - 1 ];
  } );

  // Don't copy over the files that are in the lib file, this way we can catch wrapper bugs that are not pointing to the lib.
  const fullBlacklist = wrappersBlacklist.concat( libFileNames );

  // wrapping function for copying the wrappers to the build dir
  const copyWrapper = function( src, dest ) {
    copyDirectory( src, dest, filterWrapper, {
      blacklist: fullBlacklist
    } );
  };

  // Make sure to copy the phet-io-wrappers common wrapper code too.
  wrappers.push( WRAPPER_COMMON_FOLDER );
  wrappers.forEach( function( wrapper ) {

    const wrapperParts = wrapper.split( '/' );

    // either take the last path part, or take the first (repo name) and remove the wrapper prefix
    const wrapperName = wrapperParts.length > 1 ? wrapperParts[ wrapperParts.length - 1 ] : wrapperParts[ 0 ].replace( DEDICATED_REPO_WRAPPER_PREFIX, '' );

    // Copy the wrapper into the build dir /wrappers/, exclude the blacklist
    copyWrapper( `../${wrapper}`, `${wrappersLocation}${wrapperName}` );
  } );

  // Copy the index wrapper into the top level of the build dir, exclude the blacklist
  copyWrapper( '../phet-io-wrappers/index', `${buildDir}` );

  // Create the lib file that is minified and publicly available under the /lib folder of the build
  handleLib( buildDir, filterWrapper );

  // Create the contrib folder and add to it third party libraries used by wrappers.
  handleContrib( buildDir );

  // Create the rendered jsdoc in the `doc` folder
  await handleJSDOC( buildDir );
};

/**
 * Given the list of lib files, apply a filter function to them. Then minify them and consolidate into a single string.
 * Finally write them to the build dir with a license prepended. See https://github.com/phetsims/phet-io/issues/353

 * @param {string} buildDir
 * @param {Function} filter - the filter function used when copying over wrapper files to fix relative paths and such.
 *                            Has arguments like "function(abspath, contents)"
 */
const handleLib = function( buildDir, filter ) {
  grunt.log.debug( 'Creating phet-io lib file from: ', LIB_FILES );

  grunt.file.mkdir( `${buildDir}lib` );

  let consolidated = '';
  LIB_FILES.forEach( function( libFile ) {
    const contents = grunt.file.read( libFile );

    const filteredContents = filter && filter( libFile, contents );

    // The filter should return null if nothing changes
    consolidated += filteredContents ? filteredContents : contents;
  } );

  const minified = minify( consolidated, {
    stripAssertions: false
  } );

  grunt.file.write( `${buildDir}lib/${LIB_OUTPUT_FILE}`, LIB_COPYRIGHT_HEADER + '\n\n' + minified );
};

/**
 * Copy all of the third party libraries from sherpa to the build directory under the 'contrib' folder.
 * @param {string} buildDir
 */
const handleContrib = function( buildDir ) {
  grunt.log.debug( 'Creating phet-io contrib folder' );

  CONTRIB_FILES.forEach( function( filePath ) {
    const filePathParts = filePath.split( '/' );

    const filename = filePathParts[ filePathParts.length - 1 ];

    grunt.file.copy( filePath, `${buildDir}contrib/${filename}` );

  } );
};

/**
 * Generate jsdoc and put it in "build/phet-io/doc"
 * @param {string} buildDir
 * @returns {Promise<void>}
 */
const handleJSDOC = async function( buildDir ) {
  grunt.log.debug( 'generating jsdoc for phet-io from: ', JSDOC_FILES );

  // First we tried to run the jsdoc binary as the cmd, but that wasn't working, and was quite finicky. Then @samreid
  // found https://stackoverflow.com/questions/33664843/how-to-use-jsdoc-with-gulp which recommends the following method
  // (node executable with jsdoc js file)
  await execute( 'node', [ '../chipper/node_modules/jsdoc/jsdoc.js' ].concat(
    JSDOC_FILES.concat( [ '-c', '../phet-io/doc/wrapper/jsdoc-config.json',
      '-d', `${buildDir}doc/`, '--readme', JSDOC_README_FILE ] ) ),
    { cwd: process.cwd(), shell: true } );
};<|endoftext|>


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

module.exports = React.createClass({

  handleClick: function() {
    if(this.props.onSelectDate){
      this.props.onSelectDate(this.props.date);
    }
  },

  render: function() {

    var isCurrentMonth = this.props.date.month() === this.props.month.month();
    var isToday = this.props.date.isSame(new Date(), "day");

    return (

          {this.props.date.date()}

    );
  }
});
<commit_msg>display selected day<commit_after>import React, { Component } from 'react';

module.exports = React.createClass({

  handleClick: function() {
    if(this.props.onSelectDate){
      this.props.onSelectDate(this.props.date);
    }
  },

  render: function() {

    var isCurrentMonth = this.props.date.month() === this.props.month.month();
    var isToday = this.props.date.isSame(new Date(), "day");

    return (

          {this.props.date.date()}

    );
  }
});
<|endoftext|>


<commit_before>module.exports = require("handlebars/runtime");
<commit_msg>Use the default runtime<commit_after>module.exports = require("handlebars/runtime")["default"];
<|endoftext|>


<commit_before>/*
 * Plantalytics
 *     Copyright (c) 2016 Sapphire Becker, Katy Brimm, Scott Ewing,
 *       Matt Fraser, Kelly Ledford, Michael Limb, Steven Ngo, Eric Turley.
 *     This project is licensed under the MIT License.
 *     Please see the file LICENSE in this distribution for license terms.
 * Contact: plantalytics.capstone@gmail.com
 */

$(function() {
    /*
     * Listener for 'enter' keypress on #loginUsername field.
     */
    $('#loginUsername').on('keypress', function(evt) {
        if (evt.which == 13 && !evt.shiftKey) {
            $('#loginButton').trigger('click');
        }
    });

    /*
     * Listener for 'enter' keypress on #loginPassword field.
     */
    $('#loginPassword').on('keypress', function(evt) {
        if (evt.which == 13 && !evt.shiftKey) {
            $('#loginButton').trigger('click');
        }
    });

    $('#loginButton').click(function() {
        /* This function to handle user login
         * once backend support allows
         */
        $.ajax({
            url: backendIpAddress + 'login',
            data: JSON.stringify({
                "username": $('#loginUsername').val(),
                "password": $('#loginPassword').val()
            }),
            type: "POST"
        }).done(function(json) {
            if (json.token) {
                localStorage.accessToken = json.token;
                window.location.href = "dashboard.html";
            } else {
                // Show error message.
                $("#loginError").text("Error logging in.");
            }
        }).fail(function() {
            // Show error message
            $("#loginError").text("Error logging in.");
        });
    });
});
<commit_msg>FE#51: Normalize ajax call for login to come inline with other ajax calls in frontend.<commit_after>/*
 * Plantalytics
 *     Copyright (c) 2016 Sapphire Becker, Katy Brimm, Scott Ewing,
 *       Matt Fraser, Kelly Ledford, Michael Limb, Steven Ngo, Eric Turley.
 *     This project is licensed under the MIT License.
 *     Please see the file LICENSE in this distribution for license terms.
 * Contact: plantalytics.capstone@gmail.com
 */

$(function() {
    /*
     * Listener for 'enter' keypress on #loginUsername field.
     */
    $('#loginUsername').on('keypress', function(evt) {
        if (evt.which == 13 && !evt.shiftKey) {
            $('#loginButton').trigger('click');
        }
    });

    /*
     * Listener for 'enter' keypress on #loginPassword field.
     */
    $('#loginPassword').on('keypress', function(evt) {
        if (evt.which == 13 && !evt.shiftKey) {
            $('#loginButton').trigger('click');
        }
    });

    $('#loginButton').click(function() {
        /* This function to handle user login
         * once backend support allows
         */
        $.ajax({
            "url": backendIpAddress + "login",
            "data": JSON.stringify({
                "username": $('#loginUsername').val(),
                "password": $('#loginPassword').val()
            }),
            "type": "POST"
        }).done(function(json) {
            if (json.token) {
                localStorage.accessToken = json.token;
                window.location.href = "dashboard.html";
            } else {
                // Show error message.
                $("#loginError").text("Error logging in.");
            }
        }).fail(function() {
            // Show error message
            $("#loginError").text("Error logging in.");
        });
    });
});
<|endoftext|>


<commit_before>
var $ = require('jquery');
var ko = require('knockout');
var bootbox = require('bootbox');
var signIn = require('js/signIn');

/**
    * The NavbarViewModel, for OSF wide navigation.
    * @param {Object} ...
    */
var NavbarViewModel = function() {
    var self = this;

    self.showSearch = ko.observable(false);
    self.searchCSS = ko.observable('');
    self.query = ko.observable('');
    self.showClose = true;

    self.onSearchPage = ko.computed(function() {
        return window.contextVars.search
    });

    // signIn viewmodel component
    self.signIn = new signIn.ViewModel();

    self.toggleSearch = function(){
        if(self.showSearch()){
            self.showSearch(false);
            self.searchCSS('');
        } else {
            self.showSearch(true);
            self.searchCSS('active');
            $('#searchPageFullBar').focus();
        }
    };

    self.submit = function() {
        $('#searchPageFullBar').blur().focus();
       if(self.query() !== ''){
           window.location.href = '/search/?q=' + self.query();
       }
    };

};

function NavbarControl (selector, data, options) {
    var self = this;
    self.selector = selector;
    self.$element = $(self.selector);
    self.data = data;
    self.viewModel = new NavbarViewModel(self.data);
    self.options = $.extend({}, {}, options);
    self.init();
}

/** Only for Search Bar Placeholder -- Allow IE and other browsers to work as the same */
function placeholder(inputDom, inputLabel) {
    inputDom.on('input', function () {
        if (inputDom.val() === '') {
            inputLabel.css( "visibility", "visible" );
        } else {
            inputLabel.css( "visibility", "hidden" );
        }
    });
}

function searchBarPlaceHolderInit() {
    var inputDom =  $("#searchPageFullBar");
    var inputLabel =  $("#searchBarLabel");
    inputDom.attr("placeholder", ""); //Clear the original placeholder
    inputLabel.css( "visibility", "visible" );
    placeholder(inputDom, inputLabel);
    inputDom.focus();

    //Make sure IE cursor is located at the end of text
    var $inputVal = inputDom.val();
    inputDom.val('').val($inputVal);

    //For search page with existing input, make sure placeholder is hidden.
    if(inputDom.val() !== '' ){
         inputLabel.css( "visibility", "hidden" );
    }
}

function isIE(userAgent) {
  userAgent = userAgent || navigator.userAgent;
  return userAgent.indexOf("MSIE ") > -1 || userAgent.indexOf("Trident/") > -1;
}

NavbarControl.prototype.init = function() {
    var self = this;
    ko.applyBindings(self.viewModel, self.$element[0]);
    if(isIE()){
        searchBarPlaceHolderInit();
    }

};

module.exports = NavbarControl;
<commit_msg>Revert "Werid space"<commit_after>
var $ = require('jquery');
var ko = require('knockout');
var bootbox = require('bootbox');
var signIn = require('js/signIn');

/**
    * The NavbarViewModel, for OSF wide navigation.
    * @param {Object} ...
    */
var NavbarViewModel = function() {
    var self = this;

    self.showSearch = ko.observable(false);
    self.searchCSS = ko.observable('');
    self.query = ko.observable('');
    self.showClose = true;

    self.onSearchPage = ko.computed(function() {
        return window.contextVars.search
    });

    // signIn viewmodel component
    self.signIn = new signIn.ViewModel();

    self.toggleSearch = function(){
        if(self.showSearch()){
            self.showSearch(false);
            self.searchCSS('');
        } else {
            self.showSearch(true);
            self.searchCSS('active');
            $('#searchPageFullBar').focus();
        }
    };

    self.submit = function() {
        $('#searchPageFullBar').blur().focus();
       if(self.query() !== ''){
           window.location.href = '/search/?q=' + self.query();
       }
    };

};

function NavbarControl (selector, data, options) {
    var self = this;
    self.selector = selector;
    self.$element = $(self.selector);
    self.data = data;
    self.viewModel = new NavbarViewModel(self.data);
    self.options = $.extend({}, {}, options);
    self.init();
}

/** Only for Search Bar Placeholder -- Allow IE and other browsers to work as the same */
function placeholder(inputDom, inputLabel) {
    inputDom.on('input', function () {
        if (inputDom.val() === '') {
            inputLabel.css( "visibility", "visible" );
        } else {
            inputLabel.css( "visibility", "hidden" );
        }
    });
}

function searchBarPlaceHolderInit() {
    var inputDom =  $("#searchPageFullBar");
    var inputLabel =  $("#searchBarLabel");
    inputDom.attr("placeholder", ""); //Clear the original placeholder
    inputLabel.css( "visibility", "visible" );
    placeholder(inputDom, inputLabel);
    inputDom.focus();

    //Make sure IE cursor is located at the end of text
    var $inputVal = inputDom.val();
    inputDom.val('').val($inputVal);

    //For search page with existing input, make sure placeholder is hidden.
    if(inputDom.val() !== '' ){
         inputLabel.css( "visibility", "hidden" );
    }
}

function isIE(userAgent) {
  userAgent = userAgent || navigator.userAgent;
  return userAgent.indexOf("MSIE ") > -1 || userAgent.indexOf("Trident/") > -1;
}

NavbarControl.prototype.init = function() {
    var self = this;
    ko.applyBindings(self.viewModel, self.$element[0]);
    if(isIE()){
        searchBarPlaceHolderInit();
    }

};

module.exports = NavbarControl;
<|endoftext|>


<commit_before>const SimpleIcons = require('./');

console.log(SimpleIcons['500px'].svg);

/*
{ title: '500px',
  hex: '0099E5',
  source: 'https://about.500px.com/press',
  name: '500px',
  svg: '<svg aria-labelledby="simpleicons-500px-icon" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">...</svg>' }
*/
<commit_msg>Remove example.js<commit_after><|endoftext|>


<commit_before>/*
 * tex_highlight_rules.js
 *
 * Copyright (C) 2009-11 by RStudio, Inc.
 *
 * The Initial Developer of the Original Code is
 * Ajax.org B.V.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * This program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */
define("mode/tex_highlight_rules", function(require, exports, module) {

var oop = require("pilot/oop");
var lang = require("pilot/lang");
var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;

var TexHighlightRules = function() {

    // regexp must not have capturing parentheses. Use (?:) instead.
    // regexps are ordered -> the first match is used

    this.$rules = {
        "start" : [
	        {
	            token : "comment",
	            regex : "%.*$"
	        }, {
	            token : "text", // non-command
	            regex : "\\\\[$&%#\\{\\}]"
	        }, {
	            token : "keyword", // command
	            regex : "\\\\(?:[a-zA-z0-9]+|[^a-zA-z0-9])"
	        }, {
	            token : "string", // double quoted string
	            regex : "``",
				next : "qqstring"
	        }, {
	            token : "string", // single quoted string
	            regex : "`",
	            next : "qstring"
	        }, {
               // Obviously these are neither keywords nor operators, but
               // labelling them as such was the easiest way to get them
               // to be colored distinctly from regular text
               token : "paren.keyword.operator",
	            regex : "[[({]"
	        }, {
               // Obviously these are neither keywords nor operators, but
               // labelling them as such was the easiest way to get them
               // to be colored distinctly from regular text
               token : "paren.keyword.operator",
	            regex : "[\\])}]"
	        }, {
	            token : "text",
	            regex : "\\s+"
	        }
        ],
        "qqstring" : [
	        {
	            token : "string",
	            regex : "['][']",
	            next : "start"
	        }, {
	            token : "comment",
	            regex : "%.*$"
	        }, {
	            token : "string", // non-command
	            regex : "\\\\[$&%#\\{\\}]"
	        }, {
	            token : "keyword", // command
	            regex : "\\\\(?:[a-zA-z0-9]+|[^a-zA-z0-9])"
	        }, {
               // Obviously these are neither keywords nor operators, but
               // labelling them as such was the easiest way to get them
               // to be colored distinctly from regular text
               token : "paren.keyword.operator",
	            regex : "[[({]"
	        }, {
               // Obviously these are neither keywords nor operators, but
               // labelling them as such was the easiest way to get them
               // to be colored distinctly from regular text
               token : "paren.keyword.operator",
	            regex : "[\\])}]"
	        }, {
	            token : "string",
	            regex : "[^\\\\'[({\\])}%]+"
	        }, {
		        token : "string",
				regex : "."
			}
        ],
        "qstring" : [
	        {
	            token : "string",
	            regex : "['](?!['])",
	            next : "start"
	        }, {
	            token : "comment",
	            regex : "%.*$"
	        }, {
	            token : "string", // non-command
	            regex : "\\\\[$&%#\\{\\}]"
	        }, {
	            token : "keyword", // command
	            regex : "\\\\(?:[a-zA-z0-9]+|[^a-zA-z0-9])"
	        }, {
               // Obviously these are neither keywords nor operators, but
               // labelling them as such was the easiest way to get them
               // to be colored distinctly from regular text
               token : "paren.keyword.operator",
	            regex : "[[({]"
	        }, {
               // Obviously these are neither keywords nor operators, but
               // labelling them as such was the easiest way to get them
               // to be colored distinctly from regular text
               token : "paren.keyword.operator",
	            regex : "[\\])}]"
	        }, {
	            token : "string",
	            regex : "[^\\\\'[({\\])}%]+"
	        }, {
		        token : "string",
				regex : "."
			}
        ]
    };
};

oop.inherits(TexHighlightRules, TextHighlightRules);

exports.TexHighlightRules = TexHighlightRules;
});
<commit_msg>1964: Improved TeX coloring rules for quotes<commit_after>/*
 * tex_highlight_rules.js
 *
 * Copyright (C) 2009-11 by RStudio, Inc.
 *
 * The Initial Developer of the Original Code is
 * Ajax.org B.V.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * This program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */
define("mode/tex_highlight_rules", function(require, exports, module) {

var oop = require("pilot/oop");
var lang = require("pilot/lang");
var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;

var TexHighlightRules = function() {

    // regexp must not have capturing parentheses. Use (?:) instead.
    // regexps are ordered -> the first match is used

    this.$rules = {
        "start" : [
	        {
	            token : "comment",
	            regex : "%.*$"
	        }, {
	            token : "text", // non-command
	            regex : "\\\\[$&%#\\{\\}]"
	        }, {
	            token : "keyword", // command
	            regex : "\\\\(?:[a-zA-z0-9]+|[^a-zA-z0-9])"
	        }, {
	            token : "string", // double quoted string
	            regex : "``",
               next : "qqstring"
	        }, {
	            token : "string", // double quoted string
	            regex : ",,",
               next : "qqstring"
	        }, {
	            token : "string", // single quoted string
	            regex : "`",
	            next : "qstring"
	        }, {
               // Obviously these are neither keywords nor operators, but
               // labelling them as such was the easiest way to get them
               // to be colored distinctly from regular text
               token : "paren.keyword.operator",
	            regex : "[[({]"
	        }, {
               // Obviously these are neither keywords nor operators, but
               // labelling them as such was the easiest way to get them
               // to be colored distinctly from regular text
               token : "paren.keyword.operator",
	            regex : "[\\])}]"
	        }, {
	            token : "text",
	            regex : "\\s+"
	        }
        ],
        "qqstring" : [
	        {
	            token : "string",
	            regex : "['][']",
	            next : "start"
	        }, {
	            token : "string",
	            regex : '"',
	            next : "start"
	        }, {
	            token : "comment",
	            regex : "%.*$"
	        }, {
	            token : "string", // non-command
	            regex : "\\\\[$&%#\\{\\}]"
	        }, {
	            token : "keyword", // command
	            regex : "\\\\(?:[a-zA-z0-9]+|[^a-zA-z0-9])"
	        }, {
               // Obviously these are neither keywords nor operators, but
               // labelling them as such was the easiest way to get them
               // to be colored distinctly from regular text
               token : "paren.keyword.operator",
	            regex : "[[({]"
	        }, {
               // Obviously these are neither keywords nor operators, but
               // labelling them as such was the easiest way to get them
               // to be colored distinctly from regular text
               token : "paren.keyword.operator",
	            regex : "[\\])}]"
	        }, {
	            token : "string",
	            regex : "[^\\\\'\"[({\\])}%]+"
	        }, {
               token : "string",
               regex : "."
           }
        ],
        "qstring" : [
	        {
	            token : "string",
	            regex : "['](?!['])",
	            next : "start"
	        }, {
	            token : "comment",
	            regex : "%.*$"
	        }, {
	            token : "string", // non-command
	            regex : "\\\\[$&%#\\{\\}]"
	        }, {
	            token : "keyword", // command
	            regex : "\\\\(?:[a-zA-z0-9]+|[^a-zA-z0-9])"
	        }, {
               // Obviously these are neither keywords nor operators, but
               // labelling them as such was the easiest way to get them
               // to be colored distinctly from regular text
               token : "paren.keyword.operator",
	            regex : "[[({]"
	        }, {
               // Obviously these are neither keywords nor operators, but
               // labelling them as such was the easiest way to get them
               // to be colored distinctly from regular text
               token : "paren.keyword.operator",
	            regex : "[\\])}]"
	        }, {
	            token : "string",
	            regex : "[^\\\\'[({\\])}%]+"
	        }, {
		        token : "string",
				regex : "."
			}
        ]
    };
};

oop.inherits(TexHighlightRules, TextHighlightRules);

exports.TexHighlightRules = TexHighlightRules;
});
<|endoftext|>


<commit_before>/**
 * Brackets Themes Copyright (c) 2014 Miguel Castillo.
 *
 * Licensed under MIT
 */

define(function (require) {
    "use strict";

    var _ = brackets.getModule("thirdparty/lodash");
    var EditorManager  = brackets.getModule("editor/EditorManager"),
        CommandManager = brackets.getModule("command/CommandManager"),
        FileSystem     = brackets.getModule("filesystem/FileSystem");

    var settings            = require("settings"),
        Theme               = require("theme"),
        themeFiles          = require("themeFiles"),
        themeApply          = require("themeApply"),
        scrollbarsApply     = require("scrollbarsApply"),
        generalSettings     = require("generalSettings"),
        viewCommandsManager = require("viewCommandsManager"),
        menu                = require("menu");

    var themeManager = {
        selected: settings.getValue("theme"),
        docMode: "",
        themes: {}
    };

    var _initted = false;

    if ( themeManager.selected === undefined ) {
        settings.setValue("theme",  ["default"]);
        themeManager.selected = ["default"];
    }

    /**
    * Process theme meta deta to create theme instances
    */
    function loadThemesFiles(themes) {
        // Iterate through each name in the themes and make them theme objects
        return _.map(themes.files, function (themeFile) {
            var theme = new Theme({fileName: themeFile, path: themes.path});
            return (themeManager.themes[theme.name] = theme);
        });
    }

    /**
    *  Create theme objects and add them to the global themes container.
    */
    function loadThemesMenu(themes, lastItem) {
        //
        // Iterate through each name in the themes and make them theme objects
        //
        _.each(themes, function (theme) {
            // Create the command id used by the menu
            var COMMAND_ID = "theme." + theme.name;

            // Register menu event...
            CommandManager.register(theme.displayName, COMMAND_ID, function () {
                syncMenuSelection(false);
                setThemesClass([theme.name]);
                syncMenuSelection(true);
                themeManager.update(true);
            });

            // Add theme menu item
            menu.addMenuItem(COMMAND_ID);
        });

        if (themes.length !== 0 && !lastItem) {
            menu.addMenuDivider();
        }
    }

    function loadSettingsMenu() {
        // Create the command id used by the menu
        var COMMAND_ID = "theme.settings";

        // Register menu event...
        CommandManager.register("Settings", COMMAND_ID, settings.open);

        // Add theme menu item
        menu.addMenuItem(COMMAND_ID);
        menu.addMenuDivider();
    }

    function syncMenuSelection(val) {
        _.each(themeManager.selected, function (item) {
            var command = CommandManager.get("theme." + item);
            if (command) {
                command.setChecked(val);
            }
        });
    }

    function setThemesClass(newThemes) {
        var oldThemes = themeManager.selected;
        themeManager.selected = (newThemes = newThemes || []);

        // We gotta prefix theme names with "theme" because themes that start with a number
        // will not render correctly.  Class names that start with a number are invalid
        newThemes = _.map(newThemes, function(theme){ return "theme-" + theme; }).join(" ");
        oldThemes = _.map(oldThemes, function(theme){ return "theme-" + theme; }).join(" ");
        $("html").removeClass(oldThemes.replace(' ', ',')).addClass(newThemes.replace(' ', ','));
    }

    function setDocumentMode(cm) {
        var mode = cm.getDoc().getMode();
        var docMode = mode && (mode.helperType || mode.name);
        $("html").removeClass("doctype-" + themeManager.docMode).addClass("doctype-" + docMode);
        themeManager.docMode = docMode;
    }

    /**
    * Loads theme style files
    */
    function loadThemes(themes, refresh) {
        var pending = _.map(themes, function (theme) {
            if ( theme ) {
                return theme.load(refresh);
            }
        });

        return $.when.apply((void 0), pending);
    }

    function refresh(cm) {
        setTimeout(function(){
            cm.refresh();
            EditorManager.resizeEditor();
        }, 100);
    }

    function getCM() {
        var editor = EditorManager.getActiveEditor();
        if (!editor || !editor._codeMirror) {
            return;
        }
        return editor._codeMirror;
    }

    FileSystem.on("change", function(evt, file) {
        var name = (file.name || "").substring(0, file.name.lastIndexOf('.')),
            theme = themeManager.themes[name];

        if ( theme && theme.getFile().parentPath === file.parentPath ) {
            themeManager.update(true);
        }
    });

    $(settings).on("change:fontSize", function() {
        themeManager.update();
    });

    themeManager.update = function(refreshThemes) {
        var cm = getCM();
        if ( cm ) {
            setDocumentMode(cm);
            themeApply(themeManager, cm);
            refresh(cm);
        }

        if ( refreshThemes === true ) {
            settings.setValue("theme", themeManager.selected);

            loadThemes(themeManager.getThemes(), refreshThemes === true).done(function() {
                setThemesClass(themeManager.selected);
                generalSettings(themeManager);
                scrollbarsApply(themeManager);

                if ( cm ) {
                    refresh(cm);
                }
            });
        }
    };

    themeManager.getThemes = function() {
        return _.map(themeManager.selected.slice(0), function (item) {
            return themeManager.themes[item];
        });
    };

    themeManager.init = function() {
        if ( _initted ) {
            return;
        }

        _initted = true;

        // Init themes when files meta data have all been loaded.  By the time
        // themeManager.init has been called, this is generally ready
        themeFiles.ready(function() {
            viewCommandsManager();
            loadSettingsMenu();

            function returnTrue() {return true;}
            var i, length, themes;
            var args = arguments;

            for ( i = 0, length = args.length; i < length; i++ ) {
                if ( args[i].error ) {
                    console.log("=============> Themes error", args[i], args[i].error);
                    continue;
                }

                themes = loadThemesFiles(args[i]);
                loadThemesMenu( themes, i + 1 === length );

                try {
                    FileSystem.watch(FileSystem.getDirectoryForPath(args[i].path), returnTrue, returnTrue);
                }
                catch(ex) {
                    console.log("=============> Themes file watch", ex);
                }
            }

            syncMenuSelection(true);
            themeManager.update(true);
            $(EditorManager).on("activeEditorChange", function() {
                themeManager.update();
            });
        });
    };

    return themeManager;
});
<commit_msg>Moved the doctype to the body of the document so that themes can easily construct rules based on document types<commit_after>/**
 * Brackets Themes Copyright (c) 2014 Miguel Castillo.
 *
 * Licensed under MIT
 */

define(function (require) {
    "use strict";

    var _ = brackets.getModule("thirdparty/lodash");
    var EditorManager  = brackets.getModule("editor/EditorManager"),
        CommandManager = brackets.getModule("command/CommandManager"),
        FileSystem     = brackets.getModule("filesystem/FileSystem");

    var settings            = require("settings"),
        Theme               = require("theme"),
        themeFiles          = require("themeFiles"),
        themeApply          = require("themeApply"),
        scrollbarsApply     = require("scrollbarsApply"),
        generalSettings     = require("generalSettings"),
        viewCommandsManager = require("viewCommandsManager"),
        menu                = require("menu");

    var themeManager = {
        selected: settings.getValue("theme"),
        docMode: "",
        themes: {}
    };

    var _initted = false;

    if ( themeManager.selected === undefined ) {
        settings.setValue("theme",  ["default"]);
        themeManager.selected = ["default"];
    }

    /**
    * Process theme meta deta to create theme instances
    */
    function loadThemesFiles(themes) {
        // Iterate through each name in the themes and make them theme objects
        return _.map(themes.files, function (themeFile) {
            var theme = new Theme({fileName: themeFile, path: themes.path});
            return (themeManager.themes[theme.name] = theme);
        });
    }

    /**
    *  Create theme objects and add them to the global themes container.
    */
    function loadThemesMenu(themes, lastItem) {
        //
        // Iterate through each name in the themes and make them theme objects
        //
        _.each(themes, function (theme) {
            // Create the command id used by the menu
            var COMMAND_ID = "theme." + theme.name;

            // Register menu event...
            CommandManager.register(theme.displayName, COMMAND_ID, function () {
                syncMenuSelection(false);
                setThemesClass([theme.name]);
                syncMenuSelection(true);
                themeManager.update(true);
            });

            // Add theme menu item
            menu.addMenuItem(COMMAND_ID);
        });

        if (themes.length !== 0 && !lastItem) {
            menu.addMenuDivider();
        }
    }

    function loadSettingsMenu() {
        // Create the command id used by the menu
        var COMMAND_ID = "theme.settings";

        // Register menu event...
        CommandManager.register("Settings", COMMAND_ID, settings.open);

        // Add theme menu item
        menu.addMenuItem(COMMAND_ID);
        menu.addMenuDivider();
    }

    function syncMenuSelection(val) {
        _.each(themeManager.selected, function (item) {
            var command = CommandManager.get("theme." + item);
            if (command) {
                command.setChecked(val);
            }
        });
    }

    function setThemesClass(newThemes) {
        var oldThemes = themeManager.selected;
        themeManager.selected = (newThemes = newThemes || []);

        // We gotta prefix theme names with "theme" because themes that start with a number
        // will not render correctly.  Class names that start with a number are invalid
        newThemes = _.map(newThemes, function(theme){ return "theme-" + theme; }).join(" ");
        oldThemes = _.map(oldThemes, function(theme){ return "theme-" + theme; }).join(" ");
        $("html").removeClass(oldThemes.replace(' ', ',')).addClass(newThemes.replace(' ', ','));
    }

    function setDocumentMode(cm) {
        var mode = cm.getDoc().getMode();
        var docMode = mode && (mode.helperType || mode.name);
        $("body").removeClass("doctype-" + themeManager.docMode).addClass("doctype-" + docMode);
        themeManager.docMode = docMode;
    }

    /**
    * Loads theme style files
    */
    function loadThemes(themes, refresh) {
        var pending = _.map(themes, function (theme) {
            if ( theme ) {
                return theme.load(refresh);
            }
        });

        return $.when.apply((void 0), pending);
    }

    function refresh(cm) {
        setTimeout(function(){
            cm.refresh();
            EditorManager.resizeEditor();
        }, 100);
    }

    function getCM() {
        var editor = EditorManager.getActiveEditor();
        if (!editor || !editor._codeMirror) {
            return;
        }
        return editor._codeMirror;
    }

    FileSystem.on("change", function(evt, file) {
        var name = (file.name || "").substring(0, file.name.lastIndexOf('.')),
            theme = themeManager.themes[name];

        if ( theme && theme.getFile().parentPath === file.parentPath ) {
            themeManager.update(true);
        }
    });

    $(settings).on("change:fontSize", function() {
        themeManager.update();
    });

    themeManager.update = function(refreshThemes) {
        var cm = getCM();
        if ( cm ) {
            setDocumentMode(cm);
            themeApply(themeManager, cm);
            refresh(cm);
        }

        if ( refreshThemes === true ) {
            settings.setValue("theme", themeManager.selected);

            loadThemes(themeManager.getThemes(), refreshThemes === true).done(function() {
                setThemesClass(themeManager.selected);
                generalSettings(themeManager);
                scrollbarsApply(themeManager);

                if ( cm ) {
                    refresh(cm);
                }
            });
        }
    };

    themeManager.getThemes = function() {
        return _.map(themeManager.selected.slice(0), function (item) {
            return themeManager.themes[item];
        });
    };

    themeManager.init = function() {
        if ( _initted ) {
            return;
        }

        _initted = true;

        // Init themes when files meta data have all been loaded.  By the time
        // themeManager.init has been called, this is generally ready
        themeFiles.ready(function() {
            viewCommandsManager();
            loadSettingsMenu();

            function returnTrue() {return true;}
            var i, length, themes;
            var args = arguments;

            for ( i = 0, length = args.length; i < length; i++ ) {
                if ( args[i].error ) {
                    console.log("=============> Themes error", args[i], args[i].error);
                    continue;
                }

                themes = loadThemesFiles(args[i]);
                loadThemesMenu( themes, i + 1 === length );

                try {
                    FileSystem.watch(FileSystem.getDirectoryForPath(args[i].path), returnTrue, returnTrue);
                }
                catch(ex) {
                    console.log("=============> Themes file watch", ex);
                }
            }

            syncMenuSelection(true);
            themeManager.update(true);
            $(EditorManager).on("activeEditorChange", function() {
                themeManager.update();
            });
        });
    };

    return themeManager;
});
<|endoftext|>


<commit_before>var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var shareBox = document.getElementById('shareBox');

var player = null;
var entities = [];
var mobBrush = "player-knight";

var tileScale = 8
var tileSize = tileScale * 9;
var tileOffset = tileScale / 2;

var backgroundImg = new Image();
backgroundImg.src = "arena-7x14.png";

var gapImg = new Image();
gapImg.src = "gap.png";

var Gap = function(x, y) {
    this.x = x;
    this.y = y;
}
gaps = [];

var updateShareLink = function() {
    var baseLink = "http://astudyinpixels.com/ld37/index.html";

    if (player == null) {
        shareBox.innerHTML = "you must add a player to generate the share url";
        return;
    };

    var link = "?p=" + player.x + "," + player.y;
    link += "&m=";

    if (entities.length <= 1) {
        shareBox.innerHTML = "you must add at least 1 enemy to generate the share url";
        return;
    }

    // add entities to url
    for (let i = 0; i < entities.length; ++i) {
        // player is handled differently, for probably unnecessary reasons
        let ent = entities[i];
        if (ent.type != "player-knight") {
            link += ent.shortName + "," + ent.x + "," + ent.y + ",";
        }
    }
    link = link.slice(0, -1);

    // add gaps to url
    if (gaps.length > 0) {
        link += "&g=";
        for (let gap of gaps) {
            link += gap.x + "," + gap.y + ",";
        }
    }
    link = link.slice(0, -1);

    var finalLink = baseLink + link;
    shareBox.innerHTML = finalLink;

    var testMapDiv = document.getElementById("testMap");
    testMapDiv.innerHTML = "test your map by clicking this";
}
updateShareLink();

var realPosFromTilePos = function(x, y) {
    return {
        x: (x * tileSize),
        y: (y * tileSize),
        s: (tileSize + (tileOffset * 2)),
    };
}

var arenaSizeX = 14;
var arenaSizeY = 7;
var inBounds = function(x, y) {
    return (
        x >= 0 && x < arenaSizeX &&
        y >= 0 && y < arenaSizeY
    );
}

var Mob = function(type, x, y) {
    this.type = type;
    this.x = x;
    this.y = y;
    this.img = new Image();
    this.img.src = type + ".png";
    this.idleImg = new Image();
    this.idleImg.src = type + "2.png";
    this.shortName = validMobShortNames[validBrushTypes.indexOf(this.type)];

    var realPos = realPosFromTilePos(x, y);
    this.realPos = { x: realPos.x, y: realPos.y };
}

// all support entity types
var validBrushTypes = [
    "player-knight",
    "skeleton",
    "ghoul",
    "demon-spear",
    "demon-mage",
    "orc-knight",
    "orc-mage",
    "black-knight",
    "TILE-GAP",
    "ERASE"
];

var validMobShortNames = [
    "pk",
    "s",
    "g",
    "ds",
    "dm",
    "ok",
    "om",
    "bk"
]

var getMousePos = function() {
    var rect = canvas.getBoundingClientRect();

    return {
        x: Math.floor((event.clientX - (rect.left + (tileScale / 2))) / tileSize),
        y: Math.floor((event.clientY - (rect.top + (tileScale / 2))) / tileSize)
    };
}

var removeEntityOrGapAt = function(x, y) {
    // if entity exists in this spot, replace it
    var entIdx = -1;
    for (var i = 0; i < entities.length; ++i) {
        if (entities[i].x == x && entities[i].y == y) {
            entIdx = i;
            break;
        }
    }

    var gapIdx = -1;
    for (var k = 0; k < gaps.length; ++k) {
        if (gaps[k].x == x && gaps[k].y == y) {
            gapIdx = k;
            break;
        }
    }

    if (entIdx != -1) {
        entities.splice(entIdx, 1);
    }
    if (gapIdx != -1) {
        gaps.splice(gapIdx, 1);
    }
}

canvas.addEventListener('mouseup', function(event) {
    mousePos = getMousePos();

    // is this even possible?
    if (!inBounds(mousePos.x, mousePos.y)) { return; }

    removeEntityOrGapAt(mousePos.x, mousePos.y);

    // if brush is eraser, update the share link and leave
    if (mobBrush == "ERASE") {
        updateShareLink();
        return;
    }

    if (mobBrush == "TILE-GAP") {
        gaps.push(new Gap(mousePos.x, mousePos.y));
        updateShareLink();
        return;
    }

    // if brush is player-knight remove any old entity info so there's only one player
    if (mobBrush == "player-knight") {
        if (player != null) {
            removeEntityOrGapAt(player.x, player.y);
        }
        player = { x: mousePos.x, y: mousePos.y };
    }

    entities.push(new Mob(mobBrush, mousePos.x, mousePos.y));
    updateShareLink();
});

var generateBrushButtons = function() {
    var brushList = document.getElementById('brushes');
    var count = 0;

    for (let type of validBrushTypes) {
        let btn = document.createElement("button");
        let text = document.createTextNode(type);

        btn.addEventListener("click", function() {
            var sel = document.getElementById("selected-brush");
            sel.innerHTML = "selected brush: " + type;
            mobBrush = type;
        });

        btn.appendChild(text);
        brushList.appendChild(btn);
        count++;
        if (count % 5 == 0) {
            brushList.appendChild(document.createElement("br"));
        }
    }
}

var drawEntities = function(time) {
    for (var ent of entities) {
        if (time % 1000 < 500) {
            ctx.drawImage(ent.img, ent.realPos.x, ent.realPos.y);
        } else {
            ctx.drawImage(ent.idleImg, ent.realPos.x, ent.realPos.y);
        }
    }
}

var drawGaps = function() {
    for (var gap of gaps) {
        let realPos = realPosFromTilePos(gap.x, gap.y)
        ctx.drawImage(gapImg, realPos.x, realPos.y);
    }
}

var drawLoop = function(time) {
    ctx.drawImage(backgroundImg, 0, 0);

    drawGaps();
    drawEntities(time);

    requestAnimationFrame(drawLoop);
}

generateBrushButtons();
requestAnimationFrame(drawLoop);<commit_msg>fix share link creation bug<commit_after>var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var shareBox = document.getElementById('shareBox');

var player = null;
var entities = [];
var mobBrush = "player-knight";

var tileScale = 8
var tileSize = tileScale * 9;
var tileOffset = tileScale / 2;

var backgroundImg = new Image();
backgroundImg.src = "arena-7x14.png";

var gapImg = new Image();
gapImg.src = "gap.png";

var Gap = function(x, y) {
    this.x = x;
    this.y = y;
}
gaps = [];

var updateShareLink = function() {
    var baseLink = "http://astudyinpixels.com/ld37/index.html";

    if (player == null) {
        shareBox.innerHTML = "you must add a player to generate the share url";
        return;
    };

    var link = "?p=" + player.x + "," + player.y;
    link += "&m=";

    if (entities.length <= 1) {
        shareBox.innerHTML = "you must add at least 1 enemy to generate the share url";
        return;
    }

    // add entities to url
    for (let i = 0; i < entities.length; ++i) {
        // player is handled differently, for probably unnecessary reasons
        let ent = entities[i];
        if (ent.type != "player-knight") {
            link += ent.shortName + "," + ent.x + "," + ent.y + ",";
        }
    }
    link = link.slice(0, -1);

    // add gaps to url
    if (gaps.length > 0) {
        link += "&g=";
        for (let gap of gaps) {
            link += gap.x + "," + gap.y + ",";
        }
        link = link.slice(0, -1);
    }

    var finalLink = baseLink + link;
    shareBox.innerHTML = finalLink;

    var testMapDiv = document.getElementById("testMap");
    testMapDiv.innerHTML = "test your map by clicking this";
}
updateShareLink();

var realPosFromTilePos = function(x, y) {
    return {
        x: (x * tileSize),
        y: (y * tileSize),
        s: (tileSize + (tileOffset * 2)),
    };
}

var arenaSizeX = 14;
var arenaSizeY = 7;
var inBounds = function(x, y) {
    return (
        x >= 0 && x < arenaSizeX &&
        y >= 0 && y < arenaSizeY
    );
}

var Mob = function(type, x, y) {
    this.type = type;
    this.x = x;
    this.y = y;
    this.img = new Image();
    this.img.src = type + ".png";
    this.idleImg = new Image();
    this.idleImg.src = type + "2.png";
    this.shortName = validMobShortNames[validBrushTypes.indexOf(this.type)];

    var realPos = realPosFromTilePos(x, y);
    this.realPos = { x: realPos.x, y: realPos.y };
}

// all support entity types
var validBrushTypes = [
    "player-knight",
    "skeleton",
    "ghoul",
    "demon-spear",
    "demon-mage",
    "orc-knight",
    "orc-mage",
    "black-knight",
    "TILE-GAP",
    "ERASE"
];

var validMobShortNames = [
    "pk",
    "s",
    "g",
    "ds",
    "dm",
    "ok",
    "om",
    "bk"
]

var getMousePos = function() {
    var rect = canvas.getBoundingClientRect();

    return {
        x: Math.floor((event.clientX - (rect.left + (tileScale / 2))) / tileSize),
        y: Math.floor((event.clientY - (rect.top + (tileScale / 2))) / tileSize)
    };
}

var removeEntityOrGapAt = function(x, y) {
    // if entity exists in this spot, replace it
    var entIdx = -1;
    for (var i = 0; i < entities.length; ++i) {
        if (entities[i].x == x && entities[i].y == y) {
            entIdx = i;
            break;
        }
    }

    var gapIdx = -1;
    for (var k = 0; k < gaps.length; ++k) {
        if (gaps[k].x == x && gaps[k].y == y) {
            gapIdx = k;
            break;
        }
    }

    if (entIdx != -1) {
        entities.splice(entIdx, 1);
    }
    if (gapIdx != -1) {
        gaps.splice(gapIdx, 1);
    }
}

canvas.addEventListener('mouseup', function(event) {
    mousePos = getMousePos();

    // is this even possible?
    if (!inBounds(mousePos.x, mousePos.y)) { return; }

    removeEntityOrGapAt(mousePos.x, mousePos.y);

    // if brush is eraser, update the share link and leave
    if (mobBrush == "ERASE") {
        updateShareLink();
        return;
    }

    if (mobBrush == "TILE-GAP") {
        gaps.push(new Gap(mousePos.x, mousePos.y));
        updateShareLink();
        return;
    }

    // if brush is player-knight remove any old entity info so there's only one player
    if (mobBrush == "player-knight") {
        if (player != null) {
            removeEntityOrGapAt(player.x, player.y);
        }
        player = { x: mousePos.x, y: mousePos.y };
    }

    entities.push(new Mob(mobBrush, mousePos.x, mousePos.y));
    updateShareLink();
});

var generateBrushButtons = function() {
    var brushList = document.getElementById('brushes');
    var count = 0;

    for (let type of validBrushTypes) {
        let btn = document.createElement("button");
        let text = document.createTextNode(type);

        btn.addEventListener("click", function() {
            var sel = document.getElementById("selected-brush");
            sel.innerHTML = "selected brush: " + type;
            mobBrush = type;
        });

        btn.appendChild(text);
        brushList.appendChild(btn);
        count++;
        if (count % 5 == 0) {
            brushList.appendChild(document.createElement("br"));
        }
    }
}

var drawEntities = function(time) {
    for (var ent of entities) {
        if (time % 1000 < 500) {
            ctx.drawImage(ent.img, ent.realPos.x, ent.realPos.y);
        } else {
            ctx.drawImage(ent.idleImg, ent.realPos.x, ent.realPos.y);
        }
    }
}

var drawGaps = function() {
    for (var gap of gaps) {
        let realPos = realPosFromTilePos(gap.x, gap.y)
        ctx.drawImage(gapImg, realPos.x, realPos.y);
    }
}

var drawLoop = function(time) {
    ctx.drawImage(backgroundImg, 0, 0);

    drawGaps();
    drawEntities(time);

    requestAnimationFrame(drawLoop);
}

generateBrushButtons();
requestAnimationFrame(drawLoop);<|endoftext|>


<commit_before>
module.exports = {
  dbPath: './credentials.sqlite3',
  slack: {
    dbTable: 'SlackCredentials'
  }
};
<commit_msg>Update db path according to env<commit_after>
const path = (process.env.NODE_ENV !== 'production') ? 'credentials.sqlite3' : '~/.credentials.sqlite3';

module.exports = {
  dbPath: path,
  slack: {
    dbTable: 'SlackCredentials'
  }
};
<|endoftext|>