<commit_before>function Map(_game) {
	this.game = _game;
	this.canvas = new Array();
	this.context = new Array();
	this.name = null;
    this.visible = true;
    this.image = new Array();
    this.path = '';
    this.json = null;
    this.loaded = false;

    return this;
};

Map.prototype.load = function(_name) {
	var self = this;
	this.name = _name;
	var t = _name.split('/');
	for(var i = 0; i < t.length - 1; i++) {
		this.path += t[i] + '/';
	}
	this.ajaxJsonReq(this.name);
};

Map.prototype.ajaxJsonReq = function(_name) {
	var self = this;
    var ajaxReq = new XMLHttpRequest();
    ajaxReq.open("GET", _name, true);
    ajaxReq.setRequestHeader("Content-type", "application/json");
	ajaxReq.addEventListener('readystatechange', function(){self.jsonLoaded(ajaxReq)});
    ajaxReq.send();
};

Map.prototype.jsonLoaded = function(_ajaxReq) {
	if(_ajaxReq.readyState == 4 && _ajaxReq.status == 200) {
        var response = JSON.parse(_ajaxReq.responseText);
        this.json = response;
        this.addProperties();
        this.loadImages();
    }
};

Map.prototype.loadImages = function() {
	var self = this;
	for(var i = 0; i < this.json.tilesets.length; i++) {
		var image = this.game.sprite.loadMap(this.path + this.json.tilesets[i].image);
		this.image.push(image);
	}
	var interval = setInterval(function(){self.loadResources(interval)}, 100);
};

Map.prototype.loadResources = function(_interval) {
	if(this.game.sprite.isLoaded()) {
		clearInterval(_interval);
		this.createContext();
		this.loaded = true;
    }
};

Map.prototype.addProperties = function() {
	for(var i = 0; i < this.json.layers.length; i++) {
		if(this.json.layers[i].type === 'tilelayer') {
			if(this.json.layers[i].properties !== undefined) {
				var main = this.json.layers[i].properties['main'] === 'true'? true : false || false;
				var scrollable = this.json.layers[i].properties['scrollable'] === 'false'? false : true || true;
				var collidable = this.json.layers[i].properties['collidable'] === 'true'? true : false || false;
				var overlap = this.json.layers[i].properties['overlap'] === 'true'? true : false || false;
				var speed = parseFloat(this.json.layers[i].properties['scroll.speed']).toFixed(3) || 1;
				this.json.layers[i].properties = {scroll: {x: 0, y: 0, speed: speed}, main: main, scrollable: scrollable, collidable: collidable, overlap: overlap};
			} else {
				this.json.layers[i]['properties'] = {scroll: {x: 0, y: 0, speed: 1}, main: false, scrollable: true, collidable: false, overlap: false};
			}
		}
	}
};

Map.prototype.createContext = function() {
	for(var i = 0; i < this.json.layers.length; i++) {
		if(this.json.layers[i].type === 'tilelayer') {
			this.canvas.push(document.createElement('canvas'));
			this.context.push(this.canvas[i].getContext('2d'));
	    	this.canvas[i].width = this.json.width * this.json.tilewidth;
	    	this.canvas[i].height = this.json.height * this.json.tileheight;
			for(j = 0; j < this.json.layers[i].data.length; j++) {
				var data = this.json.layers[i].data[j];
				if(data > 0) {
				var tileset = this.getTileset(data);
				this.context[i].save();
				this.context[i].globalAlpha = this.json.layers[i].opacity;
				this.context[i].drawImage(this.image[tileset], Math.floor((data - this.json.tilesets[tileset].firstgid) % this.json.layers[i].width) * this.json.tilesets[tileset].tilewidth, Math.floor((data - this.json.tilesets[tileset].firstgid) / this.json.layers[i].width) * this.json.tilesets[tileset].tilewidth, this.json.tilesets[tileset].tilewidth, this.json.tilesets[tileset].tileheight, Math.floor(j % this.json.layers[i].width) * this.json.tilewidth, Math.floor(j / this.json.layers[i].width) * this.json.tilewidth, this.json.tilewidth, this.json.tileheight);
				this.context[i].restore();
				}
			}
		}
	}
};

Map.prototype.getTileset = function(_data) {
	for(var i = 0; i < this.json.tilesets.length; i++) {
		if(this.json.tilesets.length === 1 || this.json.tilesets.length === i || this.json.tilesets[i].firstgid === _data) {
			return i;
		} else if(this.json.tilesets[i].firstgid > _data) {
			return i - 1;
		}
	}
};

Map.prototype.getMainLayer = function() {
	if(this.json !== null) {
	    for(var i = 0; i < this.json.layers.length; i++) {
	        if(this.game.map.json.layers[i].type === 'tilelayer' && this.json.layers[i].properties.main) {
	            return i;
	        }
	    }
    }
    return -1;
};

Map.prototype.getLayerIdByName = function(_name) {
    for(var i = 0; i < this.json.layers.length; i++) {
        if(this.json.layers[i].name === _name) {
            return i;
        }
    }
    return -1;
};

Map.prototype.getTilesetIdByName = function(_name) {
    for(var i = 0; i < this.json.tilesets.length; i++) {
        if(this.json.tilesets[i].name === _name) {
            return i;
        }
    }
    return -1;
};

Map.prototype.getTile = function(_name, _x, _y, _width, _height) {
	_width = _width || 0;
	_height = _height || 0;
	var layer = this.getLayerIdByName(_name);
	if(this.json.layers[layer].type === 'tilelayer') {
		var tile = (Math.floor(_y / this.json.tileheight) * this.json.layers[layer].width) + Math.floor(_x / this.json.tilewidth);
		if((tile >= this.json.layers[layer].data.length || tile < 0) || (_x > this.json.layers[layer].width * this.json.tilewidth || _x + _width < 0) || (_y > this.json.layers[layer].height * this.json.tileheight || _y + _height < 0)) {
			return null;
		} else {
			return tile;
		}
	} else {
		return null;
	}
};

Map.prototype.getTileData = function(_name, _x, _y) {
	var layer = this.getLayerIdByName(_name);
	var tile = this.getTile(_name, _x, _y);
	if(tile === null) {
		return null;
	} else {
		return this.json.layers[layer].data[tile];
	}
};

Map.prototype.clearTile = function(_name, _x, _y) {
	var id = this.getLayerIdByName(_name);
	var layer = this.json.layers[id];
	var tile = this.getTile(_name, _x, _y);
	if(tile !== null) {
		layer.data[tile] = 0;
		this.context[id].save();
		this.context[id].globalAlpha = layer.opacity;
		this.context[id].clearRect(Math.floor(tile % this.json.layers[id].width) * this.json.tilewidth, Math.floor(tile / this.json.layers[id].width) * this.json.tilewidth, this.json.tilewidth, this.json.tileheight);
		this.context[id].restore();
	}
};

Map.prototype.setTile = function(_name, _x, _y, _tileset, _tile) {
	var id = this.getLayerIdByName(_name);
	var layer = this.json.layers[id];
	var tile = this.getTile(_name, _x, _y);
	var tileset = this.getTilesetIdByName(_tileset);
	var data = _tile + this.json.tilesets[tileset].firstgid;
	if(tile !== null) {
		layer.data[tile] = data;
		this.context[id].save();
		this.context[id].globalAlpha = this.json.layers[id].opacity;
		this.context[id].drawImage(this.image[tileset], Math.floor((data - this.json.tilesets[tileset].firstgid) % this.json.layers[id].width) * this.json.tilesets[tileset].tilewidth, Math.floor((data - this.json.tilesets[tileset].firstgid) / this.json.layers[id].width) * this.json.tilesets[tileset].tilewidth, this.json.tilesets[tileset].tilewidth, this.json.tilesets[tileset].tileheight, Math.floor(tile % this.json.layers[id].width) * this.json.tilewidth, Math.floor(tile / this.json.layers[id].width) * this.json.tilewidth, this.json.tilewidth, this.json.tileheight);
		this.context[id].restore();
	}
};

Map.prototype.update = function() {
	if(this.json !== null) {
		for(var i = 0; i < this.json.layers.length; i++) {
			if(this.json.layers[i].type === 'tilelayer') {
				this.json.layers[i].x += this.json.layers[i].properties.scroll.x;
				this.json.layers[i].y += this.json.layers[i].properties.scroll.y;
				this.json.layers[i].x = parseFloat(this.json.layers[i].x.toFixed(3));
				this.json.layers[i].y = parseFloat(this.json.layers[i].y.toFixed(3));
			}
		}
	}
};

Map.prototype.resetScroll = function() {
	if(this.json !== null) {
		for(var i = 0; i < this.json.layers.length; i++) {
			if(this.json.layers[i].type === 'tilelayer') {
				this.json.layers[i].properties.scroll.x = 0;
				this.json.layers[i].properties.scroll.y = 0;
			}
		}
	}
};

Map.prototype.resetPosition = function() {
	for(var i = 0; i < this.json.layers.length; i++) {
		if(this.json.layers[i].type === 'tilelayer') {
			this.json.layers[i].x = 0;
			this.json.layers[i].y = 0;
		}
	}
};

Map.prototype.draw = function(_overlap) {
	for(var i = 0; i < this.canvas.length; i++) {
		if(this.json.layers[i].type === 'tilelayer' && this.json.layers[i].visible && this.json.layers[i].properties.overlap === _overlap) {
			var w = this.game.canvas.width > this.canvas[i].width ? this.canvas[i].width : this.game.canvas.width;
			var h = this.game.canvas.height > this.canvas[i].height ? this.canvas[i].height : this.game.canvas.height;
			this.game.context.save();
			this.game.context.drawImage(this.canvas[i], Math.round(-this.json.layers[i].x), Math.round(-this.json.layers[i].y), w, h, 0, 0, w, h);
			this.game.context.restore();
		}
	}
};<commit_msg>Fixed handling of map sprite image size relative to map size<commit_after>function Map(_game) {
	this.game = _game;
	this.canvas = new Array();
	this.context = new Array();
	this.name = null;
    this.visible = true;
    this.image = new Array();
    this.path = '';
    this.json = null;
    this.loaded = false;

    return this;
};

Map.prototype.load = function(_name) {
	var self = this;
	this.name = _name;
	var t = _name.split('/');
	for(var i = 0; i < t.length - 1; i++) {
		this.path += t[i] + '/';
	}
	this.ajaxJsonReq(this.name);
};

Map.prototype.ajaxJsonReq = function(_name) {
	var self = this;
    var ajaxReq = new XMLHttpRequest();
    ajaxReq.open("GET", _name, true);
    ajaxReq.setRequestHeader("Content-type", "application/json");
	ajaxReq.addEventListener('readystatechange', function(){self.jsonLoaded(ajaxReq)});
    ajaxReq.send();
};

Map.prototype.jsonLoaded = function(_ajaxReq) {
	if(_ajaxReq.readyState == 4 && _ajaxReq.status == 200) {
        var response = JSON.parse(_ajaxReq.responseText);
        this.json = response;
        this.addProperties();
        this.loadImages();
    }
};

Map.prototype.loadImages = function() {
	var self = this;
	for(var i = 0; i < this.json.tilesets.length; i++) {
		var image = this.game.sprite.loadMap(this.path + this.json.tilesets[i].image);
		this.image.push(image);
	}
	var interval = setInterval(function(){self.loadResources(interval)}, 100);
};

Map.prototype.loadResources = function(_interval) {
	if(this.game.sprite.isLoaded()) {
		clearInterval(_interval);
		this.createContext();
		this.loaded = true;
    }
};

Map.prototype.addProperties = function() {
	for(var i = 0; i < this.json.layers.length; i++) {
		if(this.json.layers[i].type === 'tilelayer') {
			if(this.json.layers[i].properties !== undefined) {
				var main = this.json.layers[i].properties['main'] === 'true'? true : false || false;
				var scrollable = this.json.layers[i].properties['scrollable'] === 'false'? false : true || true;
				var collidable = this.json.layers[i].properties['collidable'] === 'true'? true : false || false;
				var overlap = this.json.layers[i].properties['overlap'] === 'true'? true : false || false;
				var speed = parseFloat(this.json.layers[i].properties['scroll.speed']).toFixed(3) || 1;
				this.json.layers[i].properties = {scroll: {x: 0, y: 0, speed: speed}, main: main, scrollable: scrollable, collidable: collidable, overlap: overlap};
			} else {
				this.json.layers[i]['properties'] = {scroll: {x: 0, y: 0, speed: 1}, main: false, scrollable: true, collidable: false, overlap: false};
			}
		}
	}
};

Map.prototype.createContext = function() {
	for(var i = 0; i < this.json.layers.length; i++) {
		if(this.json.layers[i].type === 'tilelayer') {
			this.canvas.push(document.createElement('canvas'));
			this.context.push(this.canvas[i].getContext('2d'));
	    	this.canvas[i].width = this.json.width * this.json.tilewidth;
	    	this.canvas[i].height = this.json.height * this.json.tileheight;
			for(j = 0; j < this.json.layers[i].data.length; j++) {
				var data = this.json.layers[i].data[j];
				if(data > 0) {
				var tileset = this.getTileset(data);
				this.context[i].save();
				this.context[i].globalAlpha = this.json.layers[i].opacity;
				this.context[i].drawImage(this.image[tileset], Math.floor((data - this.json.tilesets[tileset].firstgid) % (this.json.tilesets[tileset].imagewidth / this.json.tilesets[tileset].tilewidth)) * this.json.tilesets[tileset].tilewidth, Math.floor((data - this.json.tilesets[tileset].firstgid) / (this.json.tilesets[tileset].imagewidth / this.json.tilesets[tileset].tilewidth)) * this.json.tilesets[tileset].tilewidth, this.json.tilesets[tileset].tilewidth, this.json.tilesets[tileset].tileheight, Math.floor(j % this.json.layers[i].width) * this.json.tilewidth, Math.floor(j / this.json.layers[i].width) * this.json.tilewidth, this.json.tilewidth, this.json.tileheight);
				this.context[i].restore();
				}
			}
		}
	}
};

Map.prototype.getTileset = function(_data) {
	for(var i = 0; i < this.json.tilesets.length; i++) {
		if(this.json.tilesets.length === 1 || this.json.tilesets.length === i || this.json.tilesets[i].firstgid === _data) {
			return i;
		} else if(this.json.tilesets[i].firstgid > _data) {
			return i - 1;
		}
	}
};

Map.prototype.getMainLayer = function() {
	if(this.json !== null) {
	    for(var i = 0; i < this.json.layers.length; i++) {
	        if(this.game.map.json.layers[i].type === 'tilelayer' && this.json.layers[i].properties.main) {
	            return i;
	        }
	    }
    }
    return -1;
};

Map.prototype.getLayerIdByName = function(_name) {
    for(var i = 0; i < this.json.layers.length; i++) {
        if(this.json.layers[i].name === _name) {
            return i;
        }
    }
    return -1;
};

Map.prototype.getTilesetIdByName = function(_name) {
    for(var i = 0; i < this.json.tilesets.length; i++) {
        if(this.json.tilesets[i].name === _name) {
            return i;
        }
    }
    return -1;
};

Map.prototype.getTile = function(_name, _x, _y, _width, _height) {
	_width = _width || 0;
	_height = _height || 0;
	var layer = this.getLayerIdByName(_name);
	if(this.json.layers[layer].type === 'tilelayer') {
		var tile = (Math.floor(_y / this.json.tileheight) * this.json.layers[layer].width) + Math.floor(_x / this.json.tilewidth);
		if((tile >= this.json.layers[layer].data.length || tile < 0) || (_x > this.json.layers[layer].width * this.json.tilewidth || _x + _width < 0) || (_y > this.json.layers[layer].height * this.json.tileheight || _y + _height < 0)) {
			return null;
		} else {
			return tile;
		}
	} else {
		return null;
	}
};

Map.prototype.getTileData = function(_name, _x, _y) {
	var layer = this.getLayerIdByName(_name);
	var tile = this.getTile(_name, _x, _y);
	if(tile === null) {
		return null;
	} else {
		return this.json.layers[layer].data[tile];
	}
};

Map.prototype.clearTile = function(_name, _x, _y) {
	var id = this.getLayerIdByName(_name);
	var layer = this.json.layers[id];
	var tile = this.getTile(_name, _x, _y);
	if(tile !== null) {
		layer.data[tile] = 0;
		this.context[id].save();
		this.context[id].globalAlpha = layer.opacity;
		this.context[id].clearRect(Math.floor(tile % this.json.layers[id].width) * this.json.tilewidth, Math.floor(tile / this.json.layers[id].width) * this.json.tilewidth, this.json.tilewidth, this.json.tileheight);
		this.context[id].restore();
	}
};

Map.prototype.setTile = function(_name, _x, _y, _tileset, _tile) {
	var id = this.getLayerIdByName(_name);
	var layer = this.json.layers[id];
	var tile = this.getTile(_name, _x, _y);
	var tileset = this.getTilesetIdByName(_tileset);
	var data = _tile + this.json.tilesets[tileset].firstgid;
	if(tile !== null) {
		layer.data[tile] = data;
		this.context[id].save();
		this.context[id].globalAlpha = this.json.layers[id].opacity;
		this.context[id].drawImage(this.image[tileset], Math.floor((data - this.json.tilesets[tileset].firstgid) % this.json.layers[id].width) * this.json.tilesets[tileset].tilewidth, Math.floor((data - this.json.tilesets[tileset].firstgid) / this.json.layers[id].width) * this.json.tilesets[tileset].tilewidth, this.json.tilesets[tileset].tilewidth, this.json.tilesets[tileset].tileheight, Math.floor(tile % this.json.layers[id].width) * this.json.tilewidth, Math.floor(tile / this.json.layers[id].width) * this.json.tilewidth, this.json.tilewidth, this.json.tileheight);
		this.context[id].restore();
	}
};

Map.prototype.update = function() {
	if(this.json !== null) {
		for(var i = 0; i < this.json.layers.length; i++) {
			if(this.json.layers[i].type === 'tilelayer') {
				this.json.layers[i].x += this.json.layers[i].properties.scroll.x;
				this.json.layers[i].y += this.json.layers[i].properties.scroll.y;
				this.json.layers[i].x = parseFloat(this.json.layers[i].x.toFixed(3));
				this.json.layers[i].y = parseFloat(this.json.layers[i].y.toFixed(3));
			}
		}
	}
};

Map.prototype.resetScroll = function() {
	if(this.json !== null) {
		for(var i = 0; i < this.json.layers.length; i++) {
			if(this.json.layers[i].type === 'tilelayer') {
				this.json.layers[i].properties.scroll.x = 0;
				this.json.layers[i].properties.scroll.y = 0;
			}
		}
	}
};

Map.prototype.resetPosition = function() {
	for(var i = 0; i < this.json.layers.length; i++) {
		if(this.json.layers[i].type === 'tilelayer') {
			this.json.layers[i].x = 0;
			this.json.layers[i].y = 0;
		}
	}
};

Map.prototype.draw = function(_overlap) {
	for(var i = 0; i < this.canvas.length; i++) {
		if(this.json.layers[i].type === 'tilelayer' && this.json.layers[i].visible && this.json.layers[i].properties.overlap === _overlap) {
			var w = this.game.canvas.width > this.canvas[i].width ? this.canvas[i].width : this.game.canvas.width;
			var h = this.game.canvas.height > this.canvas[i].height ? this.canvas[i].height : this.game.canvas.height;
			this.game.context.save();
			this.game.context.drawImage(this.canvas[i], Math.round(-this.json.layers[i].x), Math.round(-this.json.layers[i].y), w, h, 0, 0, w, h);
			this.game.context.restore();
		}
	}
};<|endoftext|>


<commit_before>/*
 * Wegas
 * http://wegas.albasim.ch
 *
 * Copyright (c) 2013-2018  School of Business and Engineering Vaud, Comem, MEI
 * Licensed under the MIT License
 */
/* global I18n */

/**
 * @fileOverview
 * @author Cyril Junod <cyril.junod at gmail.com>
 */
YUI.add('wegas-panel', function(Y) {
    "use strict";

    var Wegas = Y.namespace("Wegas");

    Wegas.Panel = Y.Base.create("wegas-panel", Y.Widget,
        [Y.WidgetParent, Y.WidgetPosition, Y.WidgetStdMod, Y.WidgetButtons,
            Y.WidgetModality, Y.WidgetPositionAlign, Y.WidgetStack], {
        initializer: function(cfg) {
            Wegas.Panel.superclass.initializer.apply(this, arguments);
            if (typeof cfg.cssClass === "string") {
                this.get("contentBox").addClass(cfg.cssClass);
            }
        },
        renderUI: function() {
            this._childrenContainer = this.getStdModNode(Y.WidgetStdMod.BODY, true);
            this._stdModParsed = {
                bodyContent: 1
            };
        },
        bindUI: function() {
            this.windowHandler = Y.after("windowresize", Y.bind(function() {
                /*
                 @HACK
                 Hide - sync - show
                 Avoid Widget modal to stack it again. As it will destroy it only once.
                 */
                this.hide();
                this.syncUI();
                this.show();
            }, this));
        },
        syncUI: function() {
            this.set("content", this.get("content"));
            // this.fillHeight(this.getStdModNode(Y.WidgetStdMod.BODY));
        },
        exit: function() {
            this.destroy();
            //this.get("boundingBox").hide(true);
            //Y.later(500, this, this.destroy);
        },
        destructor: function() {
            this.windowHandler.detach();
        }
    }, {
        CSS_PREFIX: "wegas-panel",
        ATTRS: {
            align: {
                "transient": true
            },
            alignOn: {
                "transient": true
            },
            content: {
                value: "",
                type: "string",
                format: "html",
                setter: function(val) {
                    if (val) {
                        this.set("bodyContent", val);
                    }
                    return val;
                }
            },
            bodyContent: {
                "transient": true
            },
            buttons: {
                "transient": true,
                value: {
                    footer: [{
                            name: 'proceed',
                            label: 'OK',
                            action: "exit"
                        }]
                }
            },
            centered: {
                value: true,
                "transient": true
            },
            defaultButton: {
                "transient": true
            },
            fillHeight: {
                "transient": true
            },
            focusOn: {
                "transient": true
            },
            footerContent: {
                "transient": true
            },
            headerContent: {
                "transient": true
            },
            maskNode: {
                "transient": true
            },
            modal: {
                value: false,
                type: "boolean"
            },
            shim: {
                "transient": true
            },
            x: {
                "transient": true
            },
            xy: {
                "transient": true
            },
            y: {
                "transient": true
            },
            zIndex: {
                value: 100000,
                getter: function(val) {
                    return val + Y.all("body > .wegas-panel").size();
                },
                "transient": true
            }
        },
        confirm: function(msg, okCb, cancelCb, okLabel, cancelLabel) {
            var panel = new Wegas.Panel({
                content: "
" + msg + "
",
                modal: true,
                width: 400,
                buttons: {
                    footer: [{
                            label: okLabel || I18n.t('global.ok') || 'OK',
                            action: function() {
                                panel.exit();
                                okCb && okCb();
                            }
                        }, {
                            label: cancelLabel || I18n.t('global.cancel') || 'Cancel',
                            action: function() {
                                panel.exit();
                                cancelCb && cancelCb();
                            }
                        }]
                }
            }).render();
            //bodyNode = panel.getStdModNode("body", true);
            // For client-side customization:
            return panel;
        },
        confirmPlayerAction: function(cb) {
            if (!Y.fire("playerAction", {})) {
                Wegas.Panel.confirm("This action will impact player data, proceed?", cb);
            } else {
                cb();
            }
        },
        prompt: function(msg, okCb, cancelCb, okLabel, cancelLabel) {
            var panel = new Wegas.Panel({
                headerContent: "" +
                    Y.Escape.html(msg) + "",
                content: "<input class='prompt-value' type='text' style='width:97%'> </input>",
                modal: true,
                width: 400,
                buttons: {
                    footer: [{
                            label: okLabel || I18n.t('global.ok') || 'OK',
                            action: function() {
                                var v = this.get("bodyContent").filter(".prompt-value").item(0).get("value");
                                panel.exit();
                                okCb && okCb(v);
                            }
                        }, {
                            label: cancelLabel || I18n.t('global.cancel') || 'Cancel',
                            action: function() {
                                panel.exit();
                                cancelCb && cancelCb();
                            }
                        }]
                }
            }).render();
            // For client-side customization:
            return panel;
        },
        alert: function(msg, okCb, okLabel) {
            var panel = new Wegas.Panel({
                content: "
" + msg + "
",
                modal: true,
                width: 400,
                buttons: {
                    footer: [{
                            label: okLabel || I18n.t('global.ok') || 'OK',
                            action: function() {
                                panel.exit();
                                okCb && okCb();
                            }
                        }]
                }
            }).render();
            // For client-side customization:
            return panel;
        },
        /**
         *
         * @param {type} children children cfg
         * @param {type} userCfg {width: 80%, height: 80%, modal: true, style:"modern" | "legacy", title: null, titleVariable:null, cssClass}
         * @returns {undefined}
         */
        openOverlay: function(children, userCfg) {
            var uCfg = Y.mix(
                Y.mix({}, userCfg),
                {
                    children: children,
                    width: "80%",
                    height: "80%",
                    modal: true,
                    style: "modern",
                    title: null,
                    titleVariable: null,
                    cssClass: null
                });

            var btn = new Y.Wegas.Button();
            btn.plug(Y.Plugin.OpenPanelWithCfg, uCfg);
            btn.wegaspanelwithcfg.execute();
        }
    });

    var DraggablePanel = Y.Base.create("wegas-panel-draggable", Y.Plugin.Base, [Y.Wegas.Plugin, Y.Wegas.Editable], {
        initializer: function() {
            var panel = this.get("host");
            panel.get("boundingBox").addClass("draggable");
            this.onMouseDown = panel.on("mousedown", function(e) {
                if (e.domEvent.button === 1) {
                    panel.get("boundingBox").addClass("dragging");
                    panel.orig = {
                        x: panel.get("x"),
                        y: panel.get("y")
                    };
                    panel.drag = {
                        x: e.domEvent.pageX,
                        y: e.domEvent.pageY
                    };
                }
            }, this);

            this.onMouseUp = Y.on("mouseup", function(e) {
                if (e.button === 1 && panel.orig) {
                    panel.get("boundingBox").removeClass("dragging");
                    panel.orig = null;
                    panel.clickOrig = null;
                }
            });

            this.onMouseMove = Y.on("mousemove", function(e) {
                if (e.button === 1 && panel.orig) {
                    var dx = e.pageX - panel.drag.x;
                    var dy = e.pageY - panel.drag.y;
                    if (Math.abs(dy) + Math.abs(dy) > 20) {
                        panel.set("x", panel.orig.x + dx);
                        panel.set("y", panel.orig.y + dy);
                    }
                }
            });
        },
        destructor: function() {
            this.onMouseMove.detach();
            this.onMouseDown.detach();
            this.onMouseUp.detach();
        }
    }, {
        NS: "draggablepanel"
    });
    Y.Plugin.DraggablePanel = DraggablePanel;
});
<commit_msg>fix panel after resizeWindow<commit_after>/*
 * Wegas
 * http://wegas.albasim.ch
 *
 * Copyright (c) 2013-2018  School of Business and Engineering Vaud, Comem, MEI
 * Licensed under the MIT License
 */
/* global I18n */

/**
 * @fileOverview
 * @author Cyril Junod <cyril.junod at gmail.com>
 */
YUI.add('wegas-panel', function(Y) {
    "use strict";

    var Wegas = Y.namespace("Wegas");

    Wegas.Panel = Y.Base.create("wegas-panel", Y.Widget,
        [Y.WidgetParent, Y.WidgetPosition, Y.WidgetStdMod, Y.WidgetButtons,
            Y.WidgetModality, Y.WidgetPositionAlign, Y.WidgetStack], {
        initializer: function(cfg) {
            Wegas.Panel.superclass.initializer.apply(this, arguments);
            if (typeof cfg.cssClass === "string") {
                this.get("contentBox").addClass(cfg.cssClass);
            }
        },
        renderUI: function() {
            this._childrenContainer = this.getStdModNode(Y.WidgetStdMod.BODY, true);
            this._stdModParsed = {
                bodyContent: 1
            };
        },
        bindUI: function() {
            this.windowHandler = Y.after("windowresize", Y.bind(function() {
                // do not this.syncUI() as Y.WidgetModality does not support it well (double overlay)
                // moreover, we only need to realign the panel -> Y.WidgetPositionAlign._syncUIPosAligh() only
                this._syncUIPosAlign();
            }, this));
        },
        syncUI: function() {
            this.set("content", this.get("content"));
            // this.fillHeight(this.getStdModNode(Y.WidgetStdMod.BODY));
        },
        exit: function() {
            this.destroy();
            //this.get("boundingBox").hide(true);
            //Y.later(500, this, this.destroy);
        },
        destructor: function() {
            this.windowHandler.detach();
        }
    }, {
        CSS_PREFIX: "wegas-panel",
        ATTRS: {
            align: {
                "transient": true
            },
            alignOn: {
                "transient": true
            },
            content: {
                value: "",
                type: "string",
                format: "html",
                setter: function(val) {
                    if (val) {
                        this.set("bodyContent", val);
                    }
                    return val;
                }
            },
            bodyContent: {
                "transient": true
            },
            buttons: {
                "transient": true,
                value: {
                    footer: [{
                            name: 'proceed',
                            label: 'OK',
                            action: "exit"
                        }]
                }
            },
            centered: {
                value: true,
                "transient": true
            },
            defaultButton: {
                "transient": true
            },
            fillHeight: {
                "transient": true
            },
            focusOn: {
                "transient": true
            },
            footerContent: {
                "transient": true
            },
            headerContent: {
                "transient": true
            },
            maskNode: {
                "transient": true
            },
            modal: {
                value: false,
                type: "boolean"
            },
            shim: {
                "transient": true
            },
            x: {
                "transient": true
            },
            xy: {
                "transient": true
            },
            y: {
                "transient": true
            },
            zIndex: {
                value: 100000,
                getter: function(val) {
                    return val + Y.all("body > .wegas-panel").size();
                },
                "transient": true
            }
        },
        confirm: function(msg, okCb, cancelCb, okLabel, cancelLabel) {
            var panel = new Wegas.Panel({
                content: "
" + msg + "
",
                modal: true,
                width: 400,
                buttons: {
                    footer: [{
                            label: okLabel || I18n.t('global.ok') || 'OK',
                            action: function() {
                                panel.exit();
                                okCb && okCb();
                            }
                        }, {
                            label: cancelLabel || I18n.t('global.cancel') || 'Cancel',
                            action: function() {
                                panel.exit();
                                cancelCb && cancelCb();
                            }
                        }]
                }
            }).render();
            //bodyNode = panel.getStdModNode("body", true);
            // For client-side customization:
            return panel;
        },
        confirmPlayerAction: function(cb) {
            if (!Y.fire("playerAction", {})) {
                Wegas.Panel.confirm("This action will impact player data, proceed?", cb);
            } else {
                cb();
            }
        },
        prompt: function(msg, okCb, cancelCb, okLabel, cancelLabel) {
            var panel = new Wegas.Panel({
                headerContent: "" +
                    Y.Escape.html(msg) + "",
                content: "<input class='prompt-value' type='text' style='width:97%'> </input>",
                modal: true,
                width: 400,
                buttons: {
                    footer: [{
                            label: okLabel || I18n.t('global.ok') || 'OK',
                            action: function() {
                                var v = this.get("bodyContent").filter(".prompt-value").item(0).get("value");
                                panel.exit();
                                okCb && okCb(v);
                            }
                        }, {
                            label: cancelLabel || I18n.t('global.cancel') || 'Cancel',
                            action: function() {
                                panel.exit();
                                cancelCb && cancelCb();
                            }
                        }]
                }
            }).render();
            // For client-side customization:
            return panel;
        },
        alert: function(msg, okCb, okLabel) {
            var panel = new Wegas.Panel({
                content: "
" + msg + "
",
                modal: true,
                width: 400,
                buttons: {
                    footer: [{
                            label: okLabel || I18n.t('global.ok') || 'OK',
                            action: function() {
                                panel.exit();
                                okCb && okCb();
                            }
                        }]
                }
            }).render();
            // For client-side customization:
            return panel;
        },
        /**
         *
         * @param {type} children children cfg
         * @param {type} userCfg {width: 80%, height: 80%, modal: true, style:"modern" | "legacy", title: null, titleVariable:null, cssClass}
         * @returns {undefined}
         */
        openOverlay: function(children, userCfg) {
            var uCfg = Y.mix(
                Y.mix({}, userCfg),
                {
                    children: children,
                    width: "80%",
                    height: "80%",
                    modal: true,
                    style: "modern",
                    title: null,
                    titleVariable: null,
                    cssClass: null
                });

            var btn = new Y.Wegas.Button();
            btn.plug(Y.Plugin.OpenPanelWithCfg, uCfg);
            btn.wegaspanelwithcfg.execute();
        }
    });

    var DraggablePanel = Y.Base.create("wegas-panel-draggable", Y.Plugin.Base, [Y.Wegas.Plugin, Y.Wegas.Editable], {
        initializer: function() {
            var panel = this.get("host");
            panel.get("boundingBox").addClass("draggable");
            this.onMouseDown = panel.on("mousedown", function(e) {
                if (e.domEvent.button === 1) {
                    panel.get("boundingBox").addClass("dragging");
                    panel.orig = {
                        x: panel.get("x"),
                        y: panel.get("y")
                    };
                    panel.drag = {
                        x: e.domEvent.pageX,
                        y: e.domEvent.pageY
                    };
                }
            }, this);

            this.onMouseUp = Y.on("mouseup", function(e) {
                if (e.button === 1 && panel.orig) {
                    panel.get("boundingBox").removeClass("dragging");
                    panel.orig = null;
                    panel.clickOrig = null;
                }
            });

            this.onMouseMove = Y.on("mousemove", function(e) {
                if (e.button === 1 && panel.orig) {
                    var dx = e.pageX - panel.drag.x;
                    var dy = e.pageY - panel.drag.y;
                    if (Math.abs(dy) + Math.abs(dy) > 20) {
                        panel.set("x", panel.orig.x + dx);
                        panel.set("y", panel.orig.y + dy);
                    }
                }
            });
        },
        destructor: function() {
            this.onMouseMove.detach();
            this.onMouseDown.detach();
            this.onMouseUp.detach();
        }
    }, {
        NS: "draggablepanel"
    });
    Y.Plugin.DraggablePanel = DraggablePanel;
});
<|endoftext|>


<commit_before>// title      : AtomicLabCover
// author     : Gino Tubaro
// license    : MIT License
// description: Conector inferior x1 (bottom)
// file       : xbottom.jscad
/**
* Toma:
* -----> Base de tapa
* -----> Alto de tapa
* -----> Alto de la rosca znut
* -----> Ancho de la rosca sizenut
*
* prototipo:
* -----> tornillo(zscre,sizescrew,znut,sizenut))
*
* Devuelve:
* -----> Tornillo y tuerca
*  var ptornillo=tornillo();
*  var newTornillo= ptornillo.dentado;
*  var newTuerca = ptornillo.tuerca;
*
*/

include('lodash.js');

include("node_modules/jscad-utils/jscad-utils.jscad");
include("node_modules/jscad-utils/jscad-utils-color.jscad");

include('node_modules/jscad-utils/jscad-utils-parts.jscad');
include('node_modules/jscad-utils/jscad-boxes.jscad');

include('conectores.jscad');
include('tornillotuerca.jscad');
function main() {
  util.init(CSG);
return AtomicLabCover(10,2); // AtomicLabCover(60);
}
function AtomicLabCover(base,alto) {

  var alto=base/2

  var l = vector_text(0,0,"ATOMIC LAB");   // l contains a list of polylines to be drawn
  var o = [];
  l.forEach(function(pl) {                   // pl = polyline (not closed)
   o.push(rectangular_extrude(pl, {w: 3, h: 5}).scale([base/200,base/200,base/100]));   // extrude it to 3D
});

//  var ptornillo=tornillo(5,alto/6,0,0);
//  var newTornillo=ptornillo.dentado;

  var h = hull( square({size: [base,1], center: true}),circle({r: base/3, center: true}).translate([0,alto,0]) );
  var temp;
  var tapa =

  union(
      temp=linear_extrude({ height: 3 }, h) .fillet(0.5, 'z+'),
      tornillotuerca(3,1)[1].snap(temp,'z','outside+').translate([0, 2, 0])
  //    newTornillo.rotateX(180).translate([0, 4, 0])
);
tapa= difference(tapa,
     union(o).snap(tapa,'z','outside-').translate([-base/2+base/25, base/80, -0.5])
  );
  return tapa.rotateX(90);

}
<commit_msg>Limpieza y parametrizacion<commit_after>// title      : AtomicLabCover
// author     : Gino Tubaro
// license    : MIT License
// description: Conector inferior x1 (bottom)
// file       : xbottom.jscad
/**
* Toma:
* -----> Base de tapa
* -----> Alto de tapa
* -----> Alto de la rosca znut
* -----> Ancho de la rosca sizenut
*
* prototipo:
* -----> tornillo(zscre,sizescrew,znut,sizenut))
*
* Devuelve:
* -----> Tornillo y tuerca
*  var ptornillo=tornillo();
*  var newTornillo= ptornillo.dentado;
*  var newTuerca = ptornillo.tuerca;
*
*/

//include('lodash.js');

//include("node_modules/jscad-utils/jscad-utils.jscad");
//include("node_modules/jscad-utils/jscad-utils-color.jscad");

//include('node_modules/jscad-utils/jscad-utils-parts.jscad');
//include('node_modules/jscad-utils/jscad-boxes.jscad');

//include('conectores.jscad');
//include('tornillotuerca.jscad');
//function main() {
//  util.init(CSG);
//return AtomicLabCover(30,2); // AtomicLabCover(60);
//}
function AtomicLabCover(base,alto) {

  var alto=base/2

  var l = vector_text(0,0,"ATOMIC LAB");
  var o = [];
  l.forEach(function(pl) {
   o.push(rectangular_extrude(pl, {w: 3, h: 5}).scale([base/200,base/200,base/100]));
});

  var h = hull( square({size: [base,1], center: true}),circle({r: base/3, center: true}).translate([0,alto,0]) );
  var temp;
  var tapa =

  union(
      temp=linear_extrude({ height: 1 }, h) .fillet(0.5, 'z+'),
      tornillotuerca(base/3,2.5)[1].snap(temp,'z','outside+').translate([0, 3, 0])
);
tapa= difference(tapa,
     union(o).snap(tapa,'z','outside-').translate([-base/2+base/25, base/80, -0.5])
  );
  return tapa.rotateX(90);

}
<|endoftext|>


<commit_before>#!/usr/bin/env node

const config = require('../config')
const fs = require('fs')
const get = require('simple-get')
const path = require('path')
const runParallelLimit = require('run-parallel-limit')
const semver = require('semver')

const TELEMETRY_PATH = path.join(config.logPath, 'telemetry')
const PARALLEL_LIMIT = 1

main()

function main () {
  // Find all files in the telemetry folder...
  fs.readdir(TELEMETRY_PATH, function (err, files) {
    if (err) throw err

    // Filter down to just log files...
    const logFiles = files
      .filter((f) => /\d{4}-\d{2}-\d{2}.log/.test(f))
      .sort()

    const summary = {}
    // Read them all and collect summary stats...
    loadTelemetrySummary(logFiles, onTelemetrySummary)

    function onTelemetrySummary (err, telemetry) {
      if (err) throw err

      summary.telemetry = telemetry

      // Load WebTorrent Desktop release history from Github...
      loadReleases(onLoadReleases)
    }

    function onLoadReleases (err, releases) {
      if (err) throw err

      summary.releases = releases
      summary.totalInstalls = releases
        .map((r) => r.installs)
        .reduce(function (a, b) {
          a.win32 += b.win32
          a.linux += b.linux
          a.darwin += b.darwin
          a.total += b.total
          return a
        }, { win32: 0, linux: 0, darwin: 0, total: 0 })

      // Finally, write summary.json
      const summaryPath = path.join(TELEMETRY_PATH, 'summary.json')
      const summaryJSON = JSON.stringify(summary, null, 2) // pretty print
      fs.writeFile(summaryPath, summaryJSON, function (err) {
        if (err) throw err
        console.log('Done!')
      })
    }
  })
}

// Reads telemetry log files in parallel, then produces a summary array, one
// entry for each day: [{date, actives, retention, ...}, ...]
function loadTelemetrySummary (logFiles, cb) {
  console.log('Summarizing ' + logFiles.length + ' telemetry log files')

  const tasks = logFiles.map(function (filename) {
    return function (cb) {
      // Read each telemetry log file, one per day...
      const filePath = path.join(TELEMETRY_PATH, filename)
      console.log('Reading ' + filename)
      fs.readFile(filePath, 'utf8', function (err, json) {
        if (err) return cb(err)

        // Each log file contains one JSON record per line
        console.log('Parsing ' + filename)

        const lines = json.trim().split('\n')
        const records = lines
          .map(function (line, i) {
            try {
              return JSON.parse(line)
            } catch (err) {
              console.error('Skipping invalid line %s:%d', filename, i + 1)
              console.error(err)
              return null
            }
          })
          .filter(Boolean)

        console.log('Read ' + records.length + ' rows from ' + filename)
        cb(null, summarizeDailyTelemetryLog(filename, records))
      })
    }
  })

  runParallelLimit(tasks, PARALLEL_LIMIT, function (err, days) {
    if (err) return cb(err)
    cb(null, combineDailyTelemetrySummaries(days))
  })
}

// Summarize a potentially huge (GB+) log file down to a few KB...
function summarizeDailyTelemetryLog (filename, records) {
  const uniqueUsers = {}
  const sessions = { total: 0, errored: 0, byVersion: {} }
  const errors = {}
  const versionByUser = {}

  records.forEach(function (record) {
    // Filter out *very* rare empty records that only have {ip}
    if (!record.system) return

    // Count unique users
    const version = (record.version || 'pre-0.12')
    const platform = record.system.osPlatform
    uniqueUsers[record.userID] = true
    versionByUser[record.userID] = { version, platform }

    // Approximate sessions by # of telemetry reports
    sessions.total++
    let byV = sessions.byVersion[version]
    if (!byV) byV = sessions.byVersion[version] = { total: 0, errored: 0 }
    byV.total++

    // Summarize uncaught errors
    const errs = record.uncaughtErrors
    if (!errs || errs.length === 0) return
    sessions.errored++
    byV.errored++

    errs.forEach(function (err) {
      const key = err.message ? err.message.substring(0, 30) : '<missing error message>'

      // Before 0.13, we didn't log the app version for each uncaught error
      // Before 0.12, we didn't log the app version altogether, and also
      // didn't redact stacktraces.
      const errVersion = err.version
        ? err.version
        : err.stack.includes('app.asar')
          ? 'pre-0.12'
          : versionCompare(version, '0.12.0') < 0
            ? version
            : '0.12.0'

      // 1. Either update the stats for an existing error...
      const error = errors[key]
      if (error) {
        error.count++
        addToSet(platform, error.platforms)
        addToSet(err.process, error.processes)
        addToSet(errVersion, error.versions, versionCompare)

        // Use the message and stack from the latest possible version
        if (errVersion === error.versions[error.versions.length - 1]) {
          error.message = err.message
          error.stack = err.stack
        }

        return
      }

      // 2. ...or create a new error
      errors[key] = {
        key: key,
        message: err.message,
        stack: err.stack,
        count: 1,
        versions: [errVersion],
        platforms: [platform],
        processes: [err.process]
      }
    })
  })

  // Summarize usage by app version and OS
  const usage = {
    version: {},
    platform: {},
    versionPlatform: {}
  }
  for (const uid in versionByUser) {
    const v = versionByUser[uid]
    const vp = v.version + '-' + v.platform
    usage.version[v.version] = (usage.version[v.version] || 0) + 1
    usage.platform[v.platform] = (usage.platform[v.platform] || 0) + 1
    usage.versionPlatform[vp] = (usage.versionPlatform[vp] || 0) + 1
  }

  return {
    date: filename.substring(0, 10), // YYYY-MM-DD
    sessions,
    uniqueUsers,
    errors,
    usage
  }
}

function versionCompare (a, b) {
  if (a === 'pre-0.12') return b === 'pre-0.12' ? 0 : -1
  if (b === 'pre-0.12') return 1
  return semver.compare(a, b)
}

// Adds an element to an array if it doesn't exist yet, the sorts the array
function addToSet (elem, arr, sortFn) {
  if (arr.includes(elem)) return
  arr.push(elem)
  arr.sort(sortFn)
}

// Combine all the per-day summaries into a single summary...
function combineDailyTelemetrySummaries (days) {
  console.log('Combining daily telemetry summaries...')

  const uniqueUsers = {} // Running set, all unique users so far
  const newUsersByDay = [] // First-time users each day

  // Loop thru all days since we started collecting telemetry...
  return days.map(function (day, i) {
    console.log('Processing ' + day.date + '...')
    // Sanity check: we should have consecutive days, correctly sorted
    if (i > 0) {
      const delta = new Date(day.date).getTime() -
                  new Date(days[i - 1].date).getTime()
      const deltaDays = delta / 24 / 3600 / 1000
      if (deltaDays !== 1) throw new Error('Missing telemetry before ' + day.date)
    }

    // Find out who installed the app that day
    const newUsers = {}
    Object.keys(day.uniqueUsers).forEach(function (user) {
      if (uniqueUsers[user]) return
      uniqueUsers[user] = true
      newUsers[user] = true
    })
    const numInstalls = Object.keys(newUsers).length
    newUsersByDay[i] = newUsers

    const errors = Object.keys(day.errors)
      .map((key) => day.errors[key])
      .sort((a, b) => b.count - a.count)

    return {
      date: day.date,
      actives: {
        today: computeActives(days, i, 1),
        last7: computeActives(days, i, 7),
        last30: computeActives(days, i, 30)
      },
      installs: numInstalls,
      retention: {
        day1: i < 2 ? null : computeRetention(days, i, 1, newUsersByDay[i - 1]),
        day7: i < 8 ? null : computeRetention(days, i, 1, newUsersByDay[i - 7]),
        day28: i < 29 ? null : computeRetention(days, i, 1, newUsersByDay[i - 28]),
        day30to60: i < 61 ? null : computeRetention(days, i, 30, newUsersByDay[i - 60])
      },
      usage: day.usage,
      errorRates: {
        last7: computeErrorRate(days, i, 7),
        today: computeErrorRate(days, i, 1),
        'today-latest': computeErrorRate(days, i, 1, true),
        'last7-latest': computeErrorRate(days, i, 7, true)
      },
      errors
    }
  })
}

// Finds the fraction of telemetry reports that contain an error
function computeErrorRate (days, index, n, latestVersionOnly) {
  if (index < n - 1) return null
  let total = 0
  let errored = 0
  for (let i = index - n + 1; i <= index; i++) {
    const day = days[i]

    // Use either *all* sessions from that UTC day, or only those sessions
    // which were at the latest released version as of the end of the day
    let sessions
    if (latestVersionOnly) {
      let latest = '0.12.0'
      Object.keys(day.usage.version).forEach(function (version) {
        if (version === 'pre-0.12') return
        if (semver.gt(version, latest)) latest = version
      })
      sessions = day.sessions.byVersion[latest] || { total: 0, errored: 0 }
    } else {
      sessions = day.sessions
    }

    total += sessions.total
    errored += sessions.errored
  }

  return total ? (errored / total) : null
}

// Finds the number of unique active users over the last n days
// ending with days[index]. Returns null if index < n - 1
function computeActives (days, index, n) {
  if (index < n - 1) return null
  const combined = {}
  for (let i = index - n + 1; i <= index; i++) {
    Object.assign(combined, days[i].uniqueUsers)
  }
  return Object.keys(combined).length
}

// Computes retention: the # of new users from some past day that used the
// app on this day.
function computeRetention (days, index, n, prevNewUsers) {
  const uniques = {}
  for (let i = index - n + 1; i <= index; i++) {
    Object.assign(uniques, days[i].uniqueUsers)
  }

  const numToday = Object.keys(uniques).length
  Object.assign(uniques, prevNewUsers)
  const numCombined = Object.keys(uniques).length
  const numPrev = Object.keys(prevNewUsers).length
  const numLost = numCombined - numToday
  return (numPrev - numLost) / numPrev
}

// Loads all WebTorrent Desktop releases
// Callback: (err, [{tag_name, published_at}, ...])
function loadReleases (cb) {
  const opts = {
    url: 'https://api.github.com/repos/webtorrent/webtorrent-desktop/releases',
    json: true,
    timeout: 30 * 1000,
    headers: {
      'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'
    }
  }

  console.log('Fetching ' + opts.url)
  get.concat(opts, function (err, res, data) {
    if (err) return cb(err)
    console.log('Got ' + data.length + ' WebTorrent Desktop releases')
    const releases = data.map(function (d) {
      // Count total downloads
      let win32 = 0
      let darwin = 0
      let linux = 0
      d.assets.map(function (a) {
        if (a.name.endsWith('.dmg')) {
          darwin += a.download_count
        } else if (a.name.endsWith('.exe')) {
          win32 += a.download_count
        } else if (a.name.endsWith('.deb') ||
                   a.name.endsWith('linux-ia32.zip') ||
                   a.name.endsWith('linux-x64.zip')) {
          linux += a.download_count
        }
      })
      const total = win32 + darwin + linux
      const installs = { win32, darwin, linux, total }

      return { tag_name: d.tag_name, published_at: d.published_at, installs }
    })
    cb(null, releases)
  })
}
<commit_msg>standard<commit_after>#!/usr/bin/env node

const config = require('../config')
const fs = require('fs')
const get = require('simple-get')
const path = require('path')
const runParallelLimit = require('run-parallel-limit')
const semver = require('semver')

const TELEMETRY_PATH = path.join(config.logPath, 'telemetry')
const PARALLEL_LIMIT = 1

main()

function main () {
  // Find all files in the telemetry folder...
  fs.readdir(TELEMETRY_PATH, function (err, files) {
    if (err) throw err

    // Filter down to just log files...
    const logFiles = files
      .filter((f) => /\d{4}-\d{2}-\d{2}.log/.test(f))
      .sort()

    const summary = {}
    // Read them all and collect summary stats...
    loadTelemetrySummary(logFiles, onTelemetrySummary)

    function onTelemetrySummary (err, telemetry) {
      if (err) throw err

      summary.telemetry = telemetry

      // Load WebTorrent Desktop release history from Github...
      loadReleases(onLoadReleases)
    }

    function onLoadReleases (err, releases) {
      if (err) throw err

      summary.releases = releases
      summary.totalInstalls = releases
        .map((r) => r.installs)
        .reduce(function (a, b) {
          a.win32 += b.win32
          a.linux += b.linux
          a.darwin += b.darwin
          a.total += b.total
          return a
        }, { win32: 0, linux: 0, darwin: 0, total: 0 })

      // Finally, write summary.json
      const summaryPath = path.join(TELEMETRY_PATH, 'summary.json')
      const summaryJSON = JSON.stringify(summary, null, 2) // pretty print
      fs.writeFile(summaryPath, summaryJSON, function (err) {
        if (err) throw err
        console.log('Done!')
      })
    }
  })
}

// Reads telemetry log files in parallel, then produces a summary array, one
// entry for each day: [{date, actives, retention, ...}, ...]
function loadTelemetrySummary (logFiles, cb) {
  console.log('Summarizing ' + logFiles.length + ' telemetry log files')

  const tasks = logFiles.map(function (filename) {
    return function (cb) {
      // Read each telemetry log file, one per day...
      const filePath = path.join(TELEMETRY_PATH, filename)
      console.log('Reading ' + filename)
      fs.readFile(filePath, 'utf8', function (err, json) {
        if (err) return cb(err)

        // Each log file contains one JSON record per line
        console.log('Parsing ' + filename)

        const lines = json.trim().split('\n')
        const records = lines
          .map(function (line, i) {
            try {
              return JSON.parse(line)
            } catch (err) {
              console.error('Skipping invalid line %s:%d', filename, i + 1)
              console.error(err)
              return null
            }
          })
          .filter(Boolean)

        console.log('Read ' + records.length + ' rows from ' + filename)
        cb(null, summarizeDailyTelemetryLog(filename, records))
      })
    }
  })

  runParallelLimit(tasks, PARALLEL_LIMIT, function (err, days) {
    if (err) return cb(err)
    cb(null, combineDailyTelemetrySummaries(days))
  })
}

// Summarize a potentially huge (GB+) log file down to a few KB...
function summarizeDailyTelemetryLog (filename, records) {
  const uniqueUsers = {}
  const sessions = { total: 0, errored: 0, byVersion: {} }
  const errors = {}
  const versionByUser = {}

  records.forEach(function (record) {
    // Filter out *very* rare empty records that only have {ip}
    if (!record.system) return

    // Count unique users
    const version = (record.version || 'pre-0.12')
    const platform = record.system.osPlatform
    uniqueUsers[record.userID] = true
    versionByUser[record.userID] = { version, platform }

    // Approximate sessions by # of telemetry reports
    sessions.total++
    let byV = sessions.byVersion[version]
    if (!byV) byV = sessions.byVersion[version] = { total: 0, errored: 0 }
    byV.total++

    // Summarize uncaught errors
    const errs = record.uncaughtErrors
    if (!errs || errs.length === 0) return
    sessions.errored++
    byV.errored++

    errs.forEach(function (err) {
      const key = err.message ? err.message.substring(0, 30) : '<missing error message>'

      // Before 0.13, we didn't log the app version for each uncaught error
      // Before 0.12, we didn't log the app version altogether, and also
      // didn't redact stacktraces.
      const errVersion = err.version
        ? err.version
        : err.stack.includes('app.asar')
          ? 'pre-0.12'
          : versionCompare(version, '0.12.0') < 0
            ? version
            : '0.12.0'

      // 1. Either update the stats for an existing error...
      const error = errors[key]
      if (error) {
        error.count++
        addToSet(platform, error.platforms)
        addToSet(err.process, error.processes)
        addToSet(errVersion, error.versions, versionCompare)

        // Use the message and stack from the latest possible version
        if (errVersion === error.versions[error.versions.length - 1]) {
          error.message = err.message
          error.stack = err.stack
        }

        return
      }

      // 2. ...or create a new error
      errors[key] = {
        key: key,
        message: err.message,
        stack: err.stack,
        count: 1,
        versions: [errVersion],
        platforms: [platform],
        processes: [err.process]
      }
    })
  })

  // Summarize usage by app version and OS
  const usage = {
    version: {},
    platform: {},
    versionPlatform: {}
  }
  for (const uid in versionByUser) {
    const v = versionByUser[uid]
    const vp = v.version + '-' + v.platform
    usage.version[v.version] = (usage.version[v.version] || 0) + 1
    usage.platform[v.platform] = (usage.platform[v.platform] || 0) + 1
    usage.versionPlatform[vp] = (usage.versionPlatform[vp] || 0) + 1
  }

  return {
    date: filename.substring(0, 10), // YYYY-MM-DD
    sessions,
    uniqueUsers,
    errors,
    usage
  }
}

function versionCompare (a, b) {
  if (a === 'pre-0.12') return b === 'pre-0.12' ? 0 : -1
  if (b === 'pre-0.12') return 1
  return semver.compare(a, b)
}

// Adds an element to an array if it doesn't exist yet, the sorts the array
function addToSet (elem, arr, sortFn) {
  if (arr.includes(elem)) return
  arr.push(elem)
  arr.sort(sortFn)
}

// Combine all the per-day summaries into a single summary...
function combineDailyTelemetrySummaries (days) {
  console.log('Combining daily telemetry summaries...')

  const uniqueUsers = {} // Running set, all unique users so far
  const newUsersByDay = [] // First-time users each day

  // Loop thru all days since we started collecting telemetry...
  return days.map(function (day, i) {
    console.log('Processing ' + day.date + '...')
    // Sanity check: we should have consecutive days, correctly sorted
    if (i > 0) {
      const delta = new Date(day.date).getTime() -
                  new Date(days[i - 1].date).getTime()
      const deltaDays = delta / 24 / 3600 / 1000
      if (deltaDays !== 1) throw new Error('Missing telemetry before ' + day.date)
    }

    // Find out who installed the app that day
    const newUsers = {}
    Object.keys(day.uniqueUsers).forEach(function (user) {
      if (uniqueUsers[user]) return
      uniqueUsers[user] = true
      newUsers[user] = true
    })
    const numInstalls = Object.keys(newUsers).length
    newUsersByDay[i] = newUsers

    const errors = Object.keys(day.errors)
      .map((key) => day.errors[key])
      .sort((a, b) => b.count - a.count)

    return {
      date: day.date,
      actives: {
        today: computeActives(days, i, 1),
        last7: computeActives(days, i, 7),
        last30: computeActives(days, i, 30)
      },
      installs: numInstalls,
      retention: {
        day1: i < 2 ? null : computeRetention(days, i, 1, newUsersByDay[i - 1]),
        day7: i < 8 ? null : computeRetention(days, i, 1, newUsersByDay[i - 7]),
        day28: i < 29 ? null : computeRetention(days, i, 1, newUsersByDay[i - 28]),
        day30to60: i < 61 ? null : computeRetention(days, i, 30, newUsersByDay[i - 60])
      },
      usage: day.usage,
      errorRates: {
        last7: computeErrorRate(days, i, 7),
        today: computeErrorRate(days, i, 1),
        'today-latest': computeErrorRate(days, i, 1, true),
        'last7-latest': computeErrorRate(days, i, 7, true)
      },
      errors
    }
  })
}

// Finds the fraction of telemetry reports that contain an error
function computeErrorRate (days, index, n, latestVersionOnly) {
  if (index < n - 1) return null
  let total = 0
  let errored = 0
  for (let i = index - n + 1; i <= index; i++) {
    const day = days[i]

    // Use either *all* sessions from that UTC day, or only those sessions
    // which were at the latest released version as of the end of the day
    let sessions
    if (latestVersionOnly) {
      let latest = '0.12.0'
      Object.keys(day.usage.version).forEach(function (version) {
        if (version === 'pre-0.12') return
        if (semver.gt(version, latest)) latest = version
      })
      sessions = day.sessions.byVersion[latest] || { total: 0, errored: 0 }
    } else {
      sessions = day.sessions
    }

    total += sessions.total
    errored += sessions.errored
  }

  return total ? (errored / total) : null
}

// Finds the number of unique active users over the last n days
// ending with days[index]. Returns null if index < n - 1
function computeActives (days, index, n) {
  if (index < n - 1) return null
  const combined = {}
  for (let i = index - n + 1; i <= index; i++) {
    Object.assign(combined, days[i].uniqueUsers)
  }
  return Object.keys(combined).length
}

// Computes retention: the # of new users from some past day that used the
// app on this day.
function computeRetention (days, index, n, prevNewUsers) {
  const uniques = {}
  for (let i = index - n + 1; i <= index; i++) {
    Object.assign(uniques, days[i].uniqueUsers)
  }

  const numToday = Object.keys(uniques).length
  Object.assign(uniques, prevNewUsers)
  const numCombined = Object.keys(uniques).length
  const numPrev = Object.keys(prevNewUsers).length
  const numLost = numCombined - numToday
  return (numPrev - numLost) / numPrev
}

// Loads all WebTorrent Desktop releases
// Callback: (err, [{tag_name, published_at}, ...])
function loadReleases (cb) {
  const opts = {
    url: 'https://api.github.com/repos/webtorrent/webtorrent-desktop/releases',
    json: true,
    timeout: 30 * 1000,
    headers: {
      'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'
    }
  }

  console.log('Fetching ' + opts.url)
  get.concat(opts, function (err, res, data) {
    if (err) return cb(err)
    console.log('Got ' + data.length + ' WebTorrent Desktop releases')
    const releases = data.map(function (d) {
      // Count total downloads
      let win32 = 0
      let darwin = 0
      let linux = 0
      d.assets.forEach(function (a) {
        if (a.name.endsWith('.dmg')) {
          darwin += a.download_count
        } else if (a.name.endsWith('.exe')) {
          win32 += a.download_count
        } else if (a.name.endsWith('.deb') ||
                   a.name.endsWith('linux-ia32.zip') ||
                   a.name.endsWith('linux-x64.zip')) {
          linux += a.download_count
        }
      })
      const total = win32 + darwin + linux
      const installs = { win32, darwin, linux, total }

      return { tag_name: d.tag_name, published_at: d.published_at, installs }
    })
    cb(null, releases)
  })
}
<|endoftext|>


<commit_before>MWF.xDesktop.requireApp("process.Xform", "$Module", null, false);
/** @class SmartBI 统计图表组件。
 * @o2cn 统计图表组件
 * @example
 * //可以在脚本中获取该组件
 * //方法1：
 * var div = this.form.get("name"); //获取组件
 * //方法2
 * var div = this.target; //在组件事件脚本中获取
 * @extends MWF.xApplication.process.Xform.$Module
 * @o2category FormComponents
 * @o2range {Process|CMS|Portal}
 * @hideconstructor
 */
MWF.xApplication.process.Xform.SmartBI = MWF.APPSmartBI =  new Class({
    Extends: MWF.APP$Module,

    _loadUserInterface: function(){
        if (!this.json.smartbiresource || this.json.smartbiresource==="none") this.node.destroy();
        else{
            var url;
            var value = this.json.smartbiresource;
            var SmartBIAction = o2.Actions.load("x_custom_smartbi_assemble_control");
            var addressUri = SmartBIAction.ResourceAction.address;

            if(addressUri){
                SmartBIAction.ResourceAction.address(value,function(json){
                    if(json.data.value !==""){
                        url = json.data.value;
                        url = url +"?showtoolbar="+this.json.smartbidisplaytoolbar+"&showLeftTree="+this.json.smartbidisplaylefttree;
                    }
                }.bind(this),null,false)
            }else{
                var address = SmartBIAction.ResourceAction.action.getAddress();
                var uri = SmartBIAction.ResourceAction.action.actions.open.uri;
                var url = uri.replace("{id}", encodeURIComponent(value));

                url = url +"?showtoolbar="+this.json.smartbidisplaytoolbar+"&showLeftTree="+this.json.smartbidisplaylefttree;

                url = o2.filterUrl(address+url);
            }

            this.iframe = new Element("iframe",{
                src:url,
                frameborder:"0",
                scrolling:"auto"
            }).inject(this.node,"after");

            var _properties = this.json.properties||{};
            this.node.destroy();
            this.node = this.iframe.setStyles({
                "width":"100%",
                "height":"100%",
                "min-height":"300px",
                "min-width":"300px"
            });

			this.node.set(_properties)

        }

	}
});<commit_msg>修改src参数<commit_after>MWF.xDesktop.requireApp("process.Xform", "$Module", null, false);
/** @class SmartBI 统计图表组件。
 * @o2cn 统计图表组件
 * @example
 * //可以在脚本中获取该组件
 * //方法1：
 * var div = this.form.get("name"); //获取组件
 * //方法2
 * var div = this.target; //在组件事件脚本中获取
 * @extends MWF.xApplication.process.Xform.$Module
 * @o2category FormComponents
 * @o2range {Process|CMS|Portal}
 * @hideconstructor
 */
MWF.xApplication.process.Xform.SmartBI = MWF.APPSmartBI =  new Class({
    Extends: MWF.APP$Module,

    _loadUserInterface: function(){
        if (!this.json.smartbiresource || this.json.smartbiresource==="none") this.node.destroy();
        else{
            var url;
            var value = this.json.smartbiresource;
            var SmartBIAction = o2.Actions.load("x_custom_smartbi_assemble_control");
            var addressUri = SmartBIAction.ResourceAction.address;

            if(addressUri){
                SmartBIAction.ResourceAction.address(value,function(json){
                    if(json.data.value !==""){
                        url = json.data.value;
                        url = url +"?showtoolbar="+this.json.smartbidisplaytoolbar+"&showLeftTree="+this.json.smartbidisplaylefttree;
                    }
                }.bind(this),null,false)
            }else{
                var address = SmartBIAction.ResourceAction.action.getAddress();
                var uri = SmartBIAction.ResourceAction.action.actions.open.uri;
                var url = uri.replace("{id}", encodeURIComponent(value));

                url = url +"&showtoolbar="+this.json.smartbidisplaytoolbar+"&showLeftTree="+this.json.smartbidisplaylefttree;

                url = o2.filterUrl(address+url);
            }

            this.iframe = new Element("iframe",{
                src:url,
                frameborder:"0",
                scrolling:"auto"
            }).inject(this.node,"after");

            var _properties = this.json.properties||{};
            this.node.destroy();
            this.node = this.iframe.setStyles({
                "width":"100%",
                "height":"100%",
                "min-height":"300px",
                "min-width":"300px"
            });

			this.node.set(_properties)

        }

	}
});<|endoftext|>


<commit_before>// @ts-check

// Grab the schema for reaction out of the node_modules
// to see if it is a subset of the production schema
//
// Used both by Danger during the deploy PR, and also
// before the deployment on circle
//
const path = require("path")
const { readFileSync } = require("fs")
const {
  introspectionQuery,
  buildClientSchema,
  printSchema,
  buildSchema,
} = require("graphql")

const { diff } = require("@graphql-inspector/core")
const fetch = require("isomorphic-fetch")

const downloadMetaphysicsSchema = async endpoint => {
  const postBody = {
    query: introspectionQuery,
    operationName: "IntrospectionQuery",
  }

  const response = await fetch(endpoint, {
    method: "POST",
    body: JSON.stringify(postBody),
    headers: {
      "Content-Type": "application/json",
    },
  })
  const { data } = await response.json()
  // commentDescriptions is hidden
  // @ts-ignore
  return printSchema(buildClientSchema(data), { commentDescriptions: true })
}

const downloadGitHubReaction = async release => {
  const response = await fetch(
    `https://github.com/artsy/reaction/raw/v${release}/data/schema.graphql`
  )

  const body = await response.text()
  return body
}

const getBreakingChanges = async metaphysicsEnv => {
  const packageJSON = JSON.parse(
    readFileSync(path.join(__dirname, "/../package.json"), "utf8")
  )
  const reactionVersion = packageJSON["dependencies"]["@artsy/reaction"]
  const reactionSchema = await downloadGitHubReaction(reactionVersion)
  const metaphyicsSchema = await downloadMetaphysicsSchema(
    `https://metaphysics-${metaphysicsEnv}.artsy.net/`
  )

  const allChanges = diff(
    buildSchema(reactionSchema),
    buildSchema(metaphyicsSchema)
  )
  const breakings = allChanges.filter(c => c.criticality.level === "BREAKING")
  const messages = breakings.map(c => c.message)
  return messages
}

module.exports = {
  getBreakingChanges,
}

// @ts-ignore
if (require.main === module) {
  // When this is being called as a script via `node scripts/validateSchemas.js`
  if (process.argv.length !== 3) {
    console.log(
      'This script must be called with either "staging" or "production"'
    )
    process.exitCode = 1
  } else {
    const env = process.argv[2]
    getBreakingChanges(env).then(changes => {
      if (changes.length) {
        process.exitCode = 1
        console.error(
          `Metaphysics' ${env} schema is incompatible with this project's expectations:\n\n`
        )
        console.error(changes)
        console.error(
          `\n\nYou should update Metaphysics ${env} before releasing these changes`
        )
      } else {
        console.log("No breaking changes found!")
      }
    })
  }
}
<commit_msg>Changes the wording in the validate schema script (#3853)<commit_after>// @ts-check

// Grab the schema for reaction out of the node_modules
// to see if it is a subset of the production schema
//
// Used both by Danger during the deploy PR, and also
// before the deployment on circle
//
const path = require("path")
const { readFileSync } = require("fs")
const {
  introspectionQuery,
  buildClientSchema,
  printSchema,
  buildSchema,
} = require("graphql")

const { diff } = require("@graphql-inspector/core")
const fetch = require("isomorphic-fetch")

const downloadMetaphysicsSchema = async endpoint => {
  const postBody = {
    query: introspectionQuery,
    operationName: "IntrospectionQuery",
  }

  const response = await fetch(endpoint, {
    method: "POST",
    body: JSON.stringify(postBody),
    headers: {
      "Content-Type": "application/json",
    },
  })
  const { data } = await response.json()
  // commentDescriptions is hidden
  // @ts-ignore
  return printSchema(buildClientSchema(data), { commentDescriptions: true })
}

const downloadGitHubReaction = async release => {
  const response = await fetch(
    `https://github.com/artsy/reaction/raw/v${release}/data/schema.graphql`
  )

  const body = await response.text()
  return body
}

const getBreakingChanges = async metaphysicsEnv => {
  const packageJSON = JSON.parse(
    readFileSync(path.join(__dirname, "/../package.json"), "utf8")
  )
  const reactionVersion = packageJSON["dependencies"]["@artsy/reaction"]
  const reactionSchema = await downloadGitHubReaction(reactionVersion)
  const metaphyicsSchema = await downloadMetaphysicsSchema(
    `https://metaphysics-${metaphysicsEnv}.artsy.net/`
  )

  const allChanges = diff(
    buildSchema(reactionSchema),
    buildSchema(metaphyicsSchema)
  )
  const breakings = allChanges.filter(c => c.criticality.level === "BREAKING")
  const messages = breakings.map(c => c.message)
  return messages
}

module.exports = {
  getBreakingChanges,
}

// @ts-ignore
if (require.main === module) {
  // When this is being called as a script via `node scripts/validateSchemas.js`
  if (process.argv.length !== 3) {
    console.log(
      'This script must be called with either "staging" or "production"'
    )
    process.exitCode = 1
  } else {
    const env = process.argv[2]
    getBreakingChanges(env).then(changes => {
      if (changes.length) {
        process.exitCode = 1
        console.error(
          `The schema in Reaction is incompatible with ${env} Metaphysics's Schema:\n\n`
        )
        console.error(changes)
        console.error(
          `\n\nYou should update Reaction's schema before releasing these changes`
        )
      } else {
        console.log("No breaking changes found!")
      }
    })
  }
}
<|endoftext|>