<commit_before>// dbEditorCombo plugin
// A hovering editor for with combo completion
;(function($, window, undefined) {

    // css attributes to copy from the target element to the editor when editor is shown
    var copyAttributes = ['borderTopWidth', 'borderTopStyle', 'borderTopColor',
			  'borderBottomWidth', 'borderBottomStyle', 'borderBottomColor',
			  'borderLeftWidth', 'borderLeftStyle', 'borderLeftColor',
			  'borderRightWidth', 'borderRightStyle', 'borderRightColor',
			  'marginTop', 'marginRight', 'marginBottom', 'marginLeft',
			  'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft',
			  'textAlign', 'verticalAlign', 'fontSize', 'fontFamily', 'fontWeight',
			  'width', 'height', 'box-sizing'];

    // css attributes to copy from the editor to the options div when it is shown
    var copyOptionsAttributes = ['backgroundColor',
                                 'borderTopStyle', 'borderBottomStyle', 'borderLeftStyle', 'borderRightStyle',
                                 'borderTopColor', 'borderBottomColor', 'borderLeftColor', 'borderRightColor',
                                 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft',
                                 'fontSize', 'fontFamily', 'fontWeight',
                                 'width', 'box-sizing'];

    // Uses the jQuery UI widget factory
    $.widget('qcode.dbEditorCombo', {
	_create: function() {
	    // Create the editor element, and bind event listeners.
	    this._on(window, {
		'resize': this.repaint,
                'cosmeticChange': function(event) {
                    if ( $(event.target).is(this.currentElement)
                         || jQuery.contains(event.target,this.currentElement)
                       ) {
                        this.repaint();
                    }
                }
	    });

            this._on(this.element.parentsUntil('html'), {
                'scroll': this.repaint
            });

	    this.editor = $('<input type="text">')
		.addClass('db-editor combo')
		.appendTo(this.element)
		.css({
		    'position': "absolute",
		    'background': "white",
		    'overflow': "visible",
		    'z-index': 1
		})
		.hide();
	    this._on(this.editor, {
                'focus': this._inputOnFocus,
		'keydown': this._inputOnKeyDown,
		'keyup': this._inputOnKeyUp,
		'cut': this._inputOnCut,
		'paste': this._inputOnPaste,
		'blur': this._inputOnBlur
	    });

	    this.comboOptions = $('<div>')
		.addClass('options-container')
		.appendTo('body')
		.css({
		    'position':'absolute',
		    'overflow':'auto',
		    'z-index': 1
		})
		.hide();
	    this._on(this.comboOptions, {
		'mouseup div': this._comboOptionMouseUp,
		'mouseenter div': this._comboOptionMouseEnter
	    });

	    this.currentElement = $([]);
	},
        getCurrentElement: function() {
            return this.currentElement;
        },
	getValue: function() {
	    // Get the current value of the editor
	    return this.editor.val();
	},
        setValue: function(value) {
            this.editor.val(value);
            this._valueChanged();
        },
	show: function(element, value, searchURL){
	    // Show this editor positioned over the target element and set the value of the editor
	    this.currentElement = $(element);
            this.currentElement.css('visibility', "hidden");
	    this.editor.show();
	    this.lastValue = value;
	    this.searchURL = searchURL;
	    this.repaint();
	    this.editor.val(value);
	},
	hide: function() {
	    // Hide the editor
	    if ( this.editor.is(':focus') ) {
		this.editor.trigger('blur');
	    }
	    this.editor.add(this.comboOptions)
		.hide();
	    this.currentElement.css('visibility', "inherit");
            this.currentElement = $([]);
	},
	selectOption: function(index) {
	    // Select the option for this 0-based index
	    this.comboOptions.children('.selected').removeClass('selected');
	    this.comboOptions.children(':nth-child(' + (index + 1) + ')').addClass('selected');
	},
	repaint: function() {
	    // repaint the editor
	    if ( this.currentElement.length == 1 && this.editor.css('display') !== 'none' ) {
		var editor = this.editor;
		var comboOptions = this.comboOptions;
		var element = this.currentElement;

		// Copy various style from the target element to the editor
		$.each(copyAttributes, function(i, name){
		    editor.css(name, element.css(name));
		});

		// Copy various style from the editor to combo options div
		$.each(copyOptionsAttributes, function(i, name){
		    comboOptions.css(name, editor.css(name));
		});
		var borderWidth = Math.max(
		    parseInt(editor.css('borderTopWidth')),
		    parseInt(editor.css('borderRightWidth')),
		    parseInt(editor.css('borderBottomWidth')),
		    parseInt(editor.css('borderLeftWidth'))
		) + 'px';
		comboOptions.css({
		    'borderTopWidth': borderWidth,
		    'borderRightWidth': borderWidth,
		    'borderBottomWidth': borderWidth,
		    'borderLeftWidth': borderWidth
		});

                if ( element.css('border-collapse') === "collapse" ) {
                    editor.css({
                        width: "+=" + (
                            ( parseInt(element.css('border-left-width'))
                              + parseInt(element.css('border-right-width'))
                            ) / 2
                        ),
                        height: "+=" + (
                            ( parseInt(element.css('border-top-width'))
                              + parseInt(element.css('border-bottom-width'))
                            ) / 2
                        ),
                    });
                }

		// Different browsers return different css for transparent elements
		if ( element.css('backgroundColor') == 'transparent'
		     || element.css('backgroundColor') == "rgba(0, 0, 0, 0)" ) {
		    editor.add(comboOptions)
			.css('backgroundColor', "white");
		} else {
		    editor.add(comboOptions)
			.css('backgroundColor', element.css('backgroundColor'));
		}

		// position
		var position = element.positionRelativeTo(this.editor[0].offsetParent);
		editor.css({
		    'left': position.left,
		    'top': position.top
		});

                var pagePosition = editor.positionRelativeTo('body');
		comboOptions.css({
		    'left': pagePosition.left + parseInt(editor.css('borderLeftWidth')) - parseInt(comboOptions.css('borderLeftWidth')),
		    'top': pagePosition.top + editor.outerHeight() - parseInt(comboOptions.css('borderTopWidth'))
		});
	    }
	},
	selectText: function(option) {
	    // Set the text selection / cursor position
	    switch(option) {
	    case "start":
		this.editor.textrange('set', "start", "start");
		break;
	    case "end":
		this.editor.textrange('set', "end", "end");
		break;
	    case "all":
		this.editor.textrange('set', "all");
		break;
	    }
	},
        getTextrange: function() {
            return this.editor.textrange('get');
        },
	search: function() {
	    // Server side search for available options
	    dbEditorCombo = this;
	    dbEditorCombo.comboOptions.show().text("Searching ...");

	    jQuery.ajax({
		url: dbEditorCombo.searchURL,
		data: {
		    value: dbEditorCombo.getValue()
		},
		dataType: 'xml',
		async: false,
		cache: false,
		success: function(data) {
		    dbEditorCombo.searchReturn(data)
		},
		error: function(jqXHR, textStatus, errorThrown) {
		    dbEditorCombo.comboOptions.text("Software Bug ! " + textStatus + ': ' + errorThrown);
		}
	    });
	},
	searchReturn: function(xmlDoc) {
	    // Populate comboOptions element with server response
	    var comboOptions = this.comboOptions;
	    comboOptions.empty();

	    var rec = jQuery('error', xmlDoc).first();
	    if ( rec.length ) {
		// Error returned by Server
		comboOptions.text(rec.text());
	    } else {
		// Success
		var recs = jQuery('records > record > option', xmlDoc);
		if ( recs.length ) {
		    // Matches Found
		    recs.each(function() {
			var comboOption = jQuery('<div>')
			    .text($(this).text())
			    .css({
				'width': '100%',
				'cursor': 'pointer'
			    })
			    .appendTo(comboOptions);
		    });
		    // selectOption first option
		    this.selectOption(0);
		} else {
		    // No Matches
		    comboOptions.text("No Matches");
		}
	    }
	},
        _valueChanged: function() {
	    this.lastValue = this.getValue();
            this.currentElement.trigger('editorValueChange');
        },
	_inputOnKeyDown: function(e) {
	    // Some key events are passed to the target element, but only the ones where we might need some non-default behavior.
	    var selection = this.editor.textrange('get');

	    switch(e.which) {

	    case 37: // left
		if ( selection.selectionAtStart ) {
		    break;
		} else {
		    return true;
		}
	    case 39: // right
		if ( selection.selectionAtEnd ) {
		    break;
		} else {
		    return true;
		}
	    case 83: // S
		if ( e.ctrlKey ) {
		    break;
		} else {
		    return true;
		}
	    case 38: // up
		if ( this.comboOptions.is(':visible') ) {
		    // navigate within comboOptions if it is visible
		    var index = this.comboOptions.children('.selected').prev().index();
		    if ( index !== -1 ) {
			this.selectOption(index);
		    }
		    return true
		}
                break;
	    case 40: // down
		if ( this.comboOptions.is(':visible') ) {
		    // navigate within comboOptions if it is visible
		    var index = this.comboOptions.children('.selected').next().index();
		    if ( index !== -1 ) {
			this.selectOption(index);
		    }
		    return true
		}
                break;
	    case 9: // tab
	    case 13: // return
		if ( this.comboOptions.is(':visible') ) {
		    // Update editor with the selected comboOption
		    var option = this.comboOptions.children('.selected');
		    if ( option.index() !== -1 ) {
                        this.setValue(option.text());
			this.comboOptions.hide();
	                e.preventDefault();
                        return true;
		    }
		}
                break;

	    case 46: // delete
		break;

	    default: return true
	    }

	    // propagate custom event to target element
	    var event = jQuery.Event('editorKeyDown', {
		'data': e.data,
		'ctrlKey': e.ctrlKey,
		'altKey': e.altKey,
		'shiftKey': e.shiftKey,
		'which': e.which
	    });
	    this.currentElement.trigger(event);
            if ( event.isDefaultPrevented() ) {
	        e.preventDefault();
            }
	},
        _inputOnFocus: function(e) {
	    //this.search();
        },
	_inputOnKeyUp: function(e) {
	    if ( this.getValue() !== this.lastValue ) {
                this._valueChanged();
	        this.search();
	    }

	    // Pass all key up events on to the target element.
            var event = jQuery.Event('editorKeyUp', {
		'data': e.data,
		'ctrlKey': e.ctrlKey,
		'altKey': e.altKey,
		'shiftKey': e.shiftKey,
		'which': e.which
            });
	    this.currentElement.trigger(event);
	},
	_inputOnCut: function(e) {
            this._valueChanged();
	    this.search();

	    // Pass all cut events on to the target element.
            var event = jQuery.Event('editorCut', {
		'data': e.data,
		'ctrlKey': e.ctrlKey,
		'altKey': e.altKey,
		'shiftKey': e.shiftKey,
		'which': e.which
            });
	    this.currentElement.trigger(event);
	},
	_inputOnPaste: function(e) {
            this._valueChanged();
	    this.search();

	    // Pass all paste events on to the target element.
            var event = jQuery.Event('editorPaste', {
		'data': e.data,
		'ctrlKey': e.ctrlKey,
		'altKey': e.altKey,
		'shiftKey': e.shiftKey,
		'which': e.which
            });
	    this.currentElement.trigger(event);
	},
	_inputOnBlur: function(e, source) {
	    if ( ! this.editor.is(':focus') ) {
		// really is blurred
		var event = jQuery.Event('editorBlur', {
		    'data': e.data
		});
		this.currentElement.trigger(event);
	    }
	},
	_comboOptionMouseUp: function(e) {
	    // Select the target option and update editor value
	    var option = $(e.currentTarget);

	    this.selectOption(option.index());
            this.setValue(option.text());
	    this.selectText('end');
	    this.comboOptions.hide();
	},
	_comboOptionMouseEnter: function(e) {
	    // Select the target option
	    var option = $(e.currentTarget);
	    this.selectOption(option.index());
	},
	destroy: function() {
	    // If the widget is destroyed, remove the editor from the DOM.
	    this.editor.remove();
	    this.comboOptions.remove();
	}
    });
})(jQuery, window);
<commit_msg>Fix position bug in dbEditorCombo<commit_after>// dbEditorCombo plugin
// A hovering editor for with combo completion
;(function($, window, undefined) {

    // css attributes to copy from the target element to the editor when editor is shown
    var copyAttributes = ['borderTopWidth', 'borderTopStyle', 'borderTopColor',
			  'borderBottomWidth', 'borderBottomStyle', 'borderBottomColor',
			  'borderLeftWidth', 'borderLeftStyle', 'borderLeftColor',
			  'borderRightWidth', 'borderRightStyle', 'borderRightColor',
			  'marginTop', 'marginRight', 'marginBottom', 'marginLeft',
			  'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft',
			  'textAlign', 'verticalAlign', 'fontSize', 'fontFamily', 'fontWeight',
			  'width', 'height', 'box-sizing'];

    // css attributes to copy from the editor to the options div when it is shown
    var copyOptionsAttributes = ['backgroundColor',
                                 'borderTopStyle', 'borderBottomStyle', 'borderLeftStyle', 'borderRightStyle',
                                 'borderTopColor', 'borderBottomColor', 'borderLeftColor', 'borderRightColor',
                                 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft',
                                 'fontSize', 'fontFamily', 'fontWeight',
                                 'width', 'box-sizing'];

    // Uses the jQuery UI widget factory
    $.widget('qcode.dbEditorCombo', {
	_create: function() {
	    // Create the editor element, and bind event listeners.
	    this._on(window, {
		'resize': this.repaint,
                'cosmeticChange': function(event) {
                    if ( $(event.target).is(this.currentElement)
                         || jQuery.contains(event.target,this.currentElement)
                       ) {
                        this.repaint();
                    }
                }
	    });

            this._on(this.element.parentsUntil('html'), {
                'scroll': this.repaint
            });

	    this.editor = $('<input type="text">')
		.addClass('db-editor combo')
		.appendTo(this.element)
		.css({
		    'position': "absolute",
		    'background': "white",
		    'overflow': "visible",
		    'z-index': 1
		})
		.hide();
	    this._on(this.editor, {
                'focus': this._inputOnFocus,
		'keydown': this._inputOnKeyDown,
		'keyup': this._inputOnKeyUp,
		'cut': this._inputOnCut,
		'paste': this._inputOnPaste,
		'blur': this._inputOnBlur
	    });

	    this.comboOptions = $('<div>')
		.addClass('options-container')
		.appendTo('body')
		.css({
		    'position':'absolute',
		    'overflow':'auto',
		    'z-index': 1
		})
		.hide();
	    this._on(this.comboOptions, {
		'mouseup div': this._comboOptionMouseUp,
		'mouseenter div': this._comboOptionMouseEnter
	    });

	    this.currentElement = $([]);
	},
        getCurrentElement: function() {
            return this.currentElement;
        },
	getValue: function() {
	    // Get the current value of the editor
	    return this.editor.val();
	},
        setValue: function(value) {
            this.editor.val(value);
            this._valueChanged();
        },
	show: function(element, value, searchURL){
	    // Show this editor positioned over the target element and set the value of the editor
	    this.currentElement = $(element);
            this.currentElement.css('visibility', "hidden");
	    this.editor.show();
	    this.lastValue = value;
	    this.searchURL = searchURL;
	    this.repaint();
	    this.editor.val(value);
	},
	hide: function() {
	    // Hide the editor
	    if ( this.editor.is(':focus') ) {
		this.editor.trigger('blur');
	    }
	    this.editor.add(this.comboOptions)
		.hide();
	    this.currentElement.css('visibility', "inherit");
            this.currentElement = $([]);
	},
	selectOption: function(index) {
	    // Select the option for this 0-based index
	    this.comboOptions.children('.selected').removeClass('selected');
	    this.comboOptions.children(':nth-child(' + (index + 1) + ')').addClass('selected');
	},
	repaint: function() {
	    // repaint the editor
	    if ( this.currentElement.length == 1 && this.editor.css('display') !== 'none' ) {
		var editor = this.editor;
		var comboOptions = this.comboOptions;
		var element = this.currentElement;

		// Copy various style from the target element to the editor
		$.each(copyAttributes, function(i, name){
		    editor.css(name, element.css(name));
		});

		// Copy various style from the editor to combo options div
		$.each(copyOptionsAttributes, function(i, name){
		    comboOptions.css(name, editor.css(name));
		});
		var borderWidth = Math.max(
		    parseInt(editor.css('borderTopWidth')),
		    parseInt(editor.css('borderRightWidth')),
		    parseInt(editor.css('borderBottomWidth')),
		    parseInt(editor.css('borderLeftWidth'))
		) + 'px';
		comboOptions.css({
		    'borderTopWidth': borderWidth,
		    'borderRightWidth': borderWidth,
		    'borderBottomWidth': borderWidth,
		    'borderLeftWidth': borderWidth
		});

                if ( element.css('border-collapse') === "collapse" ) {
                    editor.css({
                        width: "+=" + (
                            ( parseInt(element.css('border-left-width'))
                              + parseInt(element.css('border-right-width'))
                            ) / 2
                        ),
                        height: "+=" + (
                            ( parseInt(element.css('border-top-width'))
                              + parseInt(element.css('border-bottom-width'))
                            ) / 2
                        ),
                    });
                }

		// Different browsers return different css for transparent elements
		if ( element.css('backgroundColor') == 'transparent'
		     || element.css('backgroundColor') == "rgba(0, 0, 0, 0)" ) {
		    editor.add(comboOptions)
			.css('backgroundColor', "white");
		} else {
		    editor.add(comboOptions)
			.css('backgroundColor', element.css('backgroundColor'));
		}

		// position
		var position = element.positionRelativeTo(this.editor[0].offsetParent);
		editor.css({
		    'left': position.left,
		    'top': position.top
		});

                var pagePosition = editor[0].getBoundingClientRect();
		comboOptions.css({
		    'left': pagePosition.x + parseInt(editor.css('borderLeftWidth')) - parseInt(comboOptions.css('borderLeftWidth')),
		    'top': pagePosition.y + editor.outerHeight() - parseInt(comboOptions.css('borderTopWidth'))
		});
	    }
	},
	selectText: function(option) {
	    // Set the text selection / cursor position
	    switch(option) {
	    case "start":
		this.editor.textrange('set', "start", "start");
		break;
	    case "end":
		this.editor.textrange('set', "end", "end");
		break;
	    case "all":
		this.editor.textrange('set', "all");
		break;
	    }
	},
        getTextrange: function() {
            return this.editor.textrange('get');
        },
	search: function() {
	    // Server side search for available options
	    dbEditorCombo = this;
	    dbEditorCombo.comboOptions.show().text("Searching ...");

	    jQuery.ajax({
		url: dbEditorCombo.searchURL,
		data: {
		    value: dbEditorCombo.getValue()
		},
		dataType: 'xml',
		async: false,
		cache: false,
		success: function(data) {
		    dbEditorCombo.searchReturn(data)
		},
		error: function(jqXHR, textStatus, errorThrown) {
		    dbEditorCombo.comboOptions.text("Software Bug ! " + textStatus + ': ' + errorThrown);
		}
	    });
	},
	searchReturn: function(xmlDoc) {
	    // Populate comboOptions element with server response
	    var comboOptions = this.comboOptions;
	    comboOptions.empty();

	    var rec = jQuery('error', xmlDoc).first();
	    if ( rec.length ) {
		// Error returned by Server
		comboOptions.text(rec.text());
	    } else {
		// Success
		var recs = jQuery('records > record > option', xmlDoc);
		if ( recs.length ) {
		    // Matches Found
		    recs.each(function() {
			var comboOption = jQuery('<div>')
			    .text($(this).text())
			    .css({
				'width': '100%',
				'cursor': 'pointer'
			    })
			    .appendTo(comboOptions);
		    });
		    // selectOption first option
		    this.selectOption(0);
		} else {
		    // No Matches
		    comboOptions.text("No Matches");
		}
	    }
	},
        _valueChanged: function() {
	    this.lastValue = this.getValue();
            this.currentElement.trigger('editorValueChange');
        },
	_inputOnKeyDown: function(e) {
	    // Some key events are passed to the target element, but only the ones where we might need some non-default behavior.
	    var selection = this.editor.textrange('get');

	    switch(e.which) {

	    case 37: // left
		if ( selection.selectionAtStart ) {
		    break;
		} else {
		    return true;
		}
	    case 39: // right
		if ( selection.selectionAtEnd ) {
		    break;
		} else {
		    return true;
		}
	    case 83: // S
		if ( e.ctrlKey ) {
		    break;
		} else {
		    return true;
		}
	    case 38: // up
		if ( this.comboOptions.is(':visible') ) {
		    // navigate within comboOptions if it is visible
		    var index = this.comboOptions.children('.selected').prev().index();
		    if ( index !== -1 ) {
			this.selectOption(index);
		    }
		    return true
		}
                break;
	    case 40: // down
		if ( this.comboOptions.is(':visible') ) {
		    // navigate within comboOptions if it is visible
		    var index = this.comboOptions.children('.selected').next().index();
		    if ( index !== -1 ) {
			this.selectOption(index);
		    }
		    return true
		}
                break;
	    case 9: // tab
	    case 13: // return
		if ( this.comboOptions.is(':visible') ) {
		    // Update editor with the selected comboOption
		    var option = this.comboOptions.children('.selected');
		    if ( option.index() !== -1 ) {
                        this.setValue(option.text());
			this.comboOptions.hide();
	                e.preventDefault();
                        return true;
		    }
		}
                break;

	    case 46: // delete
		break;

	    default: return true
	    }

	    // propagate custom event to target element
	    var event = jQuery.Event('editorKeyDown', {
		'data': e.data,
		'ctrlKey': e.ctrlKey,
		'altKey': e.altKey,
		'shiftKey': e.shiftKey,
		'which': e.which
	    });
	    this.currentElement.trigger(event);
            if ( event.isDefaultPrevented() ) {
	        e.preventDefault();
            }
	},
        _inputOnFocus: function(e) {
	    //this.search();
        },
	_inputOnKeyUp: function(e) {
	    if ( this.getValue() !== this.lastValue ) {
                this._valueChanged();
	        this.search();
	    }

	    // Pass all key up events on to the target element.
            var event = jQuery.Event('editorKeyUp', {
		'data': e.data,
		'ctrlKey': e.ctrlKey,
		'altKey': e.altKey,
		'shiftKey': e.shiftKey,
		'which': e.which
            });
	    this.currentElement.trigger(event);
	},
	_inputOnCut: function(e) {
            this._valueChanged();
	    this.search();

	    // Pass all cut events on to the target element.
            var event = jQuery.Event('editorCut', {
		'data': e.data,
		'ctrlKey': e.ctrlKey,
		'altKey': e.altKey,
		'shiftKey': e.shiftKey,
		'which': e.which
            });
	    this.currentElement.trigger(event);
	},
	_inputOnPaste: function(e) {
            this._valueChanged();
	    this.search();

	    // Pass all paste events on to the target element.
            var event = jQuery.Event('editorPaste', {
		'data': e.data,
		'ctrlKey': e.ctrlKey,
		'altKey': e.altKey,
		'shiftKey': e.shiftKey,
		'which': e.which
            });
	    this.currentElement.trigger(event);
	},
	_inputOnBlur: function(e, source) {
	    if ( ! this.editor.is(':focus') ) {
		// really is blurred
		var event = jQuery.Event('editorBlur', {
		    'data': e.data
		});
		this.currentElement.trigger(event);
	    }
	},
	_comboOptionMouseUp: function(e) {
	    // Select the target option and update editor value
	    var option = $(e.currentTarget);

	    this.selectOption(option.index());
            this.setValue(option.text());
	    this.selectText('end');
	    this.comboOptions.hide();
	},
	_comboOptionMouseEnter: function(e) {
	    // Select the target option
	    var option = $(e.currentTarget);
	    this.selectOption(option.index());
	},
	destroy: function() {
	    // If the widget is destroyed, remove the editor from the DOM.
	    this.editor.remove();
	    this.comboOptions.remove();
	}
    });
})(jQuery, window);
<|endoftext|>


<commit_before>'use strict'

const extend = require('util')._extend
const semver = require('semver')

const postMergeDownloads = [
  {
    'title': 'Windows 32-bit Installer',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-x86.msi'
  },
  {
    'title': 'Windows 64-bit Installer',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-x64.msi'
  },
  {
    'title': 'Windows 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/win-x86/node.exe'
  },
  {
    'title': 'Windows 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/win-x64/node.exe'
  },
  {
    'title': 'Mac OS X 64-bit Installer',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%.pkg'
  },
  {
    'title': 'Mac OS X 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-darwin-x64.tar.gz'
  },
  {
    'title': 'Linux 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-linux-x86.tar.gz'
  },
  {
    'title': 'Linux 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-linux-x64.tar.gz'
  },
  {
    'title': 'SunOS 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-sunos-x86.tar.gz'
  },
  {
    'title': 'SunOS 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-sunos-x64.tar.gz'
  },
  {
    'title': 'ARMv6 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-linux-armv6l.tar.gz'
  },
  {
    'title': 'ARMv7 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-linux-armv7l.tar.gz'
  },
  {
    'title': 'ARMv8 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-linux-arm64.tar.gz'
  },
  {
    'title': 'Source Code',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%.tar.gz'
  }
]

// v0.x of Node.js
const legacyDownloads = [
  {
    'title': 'Windows 32-bit Installer',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-x86.msi'
  },
  {
    'title': 'Windows 64-bit Installer',
    'templateUrl': 'https://nodejs.org/dist/v%version%/x64/node-v%version%-x64.msi'
  },
  {
    'title': 'Windows 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node.exe'
  },
  {
    'title': 'Windows 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/x64/node.exe'
  },
  {
    'title': 'Mac OS X Universal Installer',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%.pkg'
  },
  {
    'title': 'Mac OS X 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-darwin-x64.tar.gz'
  },
  {
    'title': 'Mac OS X 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-darwin-x86.tar.gz'
  },
  {
    'title': 'Linux 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-linux-x86.tar.gz'
  },
  {
    'title': 'Linux 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-linux-x64.tar.gz'
  },
  {
    'title': 'SunOS 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-sunos-x86.tar.gz'
  },
  {
    'title': 'SunOS 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-sunos-x64.tar.gz'
  },
  {
    'title': 'Source Code',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%.tar.gz'
  }
]

function resolveUrl (item, version) {
  const url = item.templateUrl.replace(/%version%/g, version)
  return extend({ url }, item)
}

module.exports = (version) => {
  const downloads = semver.satisfies(version, '>= 1.0.0') ? postMergeDownloads : legacyDownloads
  return downloads.map((item) => resolveUrl(item, version))
}
<commit_msg>release posts: add ppc64le & link to tar.xz for Linux & SunOS<commit_after>'use strict'

const extend = require('util')._extend
const semver = require('semver')

const postMergeDownloads = [
  {
    'title': 'Windows 32-bit Installer',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-x86.msi'
  },
  {
    'title': 'Windows 64-bit Installer',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-x64.msi'
  },
  {
    'title': 'Windows 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/win-x86/node.exe'
  },
  {
    'title': 'Windows 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/win-x64/node.exe'
  },
  {
    'title': 'Mac OS X 64-bit Installer',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%.pkg'
  },
  {
    'title': 'Mac OS X 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-darwin-x64.tar.gz'
  },
  {
    'title': 'Linux 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-linux-x86.tar.xz'
  },
  {
    'title': 'Linux 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-linux-x64.tar.xz'
  },
  {
    'title': 'Linux PPC LE 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-linux-ppc64le.tar.xz'
  },
  {
    'title': 'SunOS 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-sunos-x86.tar.xz'
  },
  {
    'title': 'SunOS 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-sunos-x64.tar.xz'
  },
  {
    'title': 'ARMv6 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-linux-armv6l.tar.xz'
  },
  {
    'title': 'ARMv7 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-linux-armv7l.tar.xz'
  },
  {
    'title': 'ARMv8 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-linux-arm64.tar.xz'
  },
  {
    'title': 'Source Code',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%.tar.gz'
  }
]

// v0.x of Node.js
const legacyDownloads = [
  {
    'title': 'Windows 32-bit Installer',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-x86.msi'
  },
  {
    'title': 'Windows 64-bit Installer',
    'templateUrl': 'https://nodejs.org/dist/v%version%/x64/node-v%version%-x64.msi'
  },
  {
    'title': 'Windows 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node.exe'
  },
  {
    'title': 'Windows 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/x64/node.exe'
  },
  {
    'title': 'Mac OS X Universal Installer',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%.pkg'
  },
  {
    'title': 'Mac OS X 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-darwin-x64.tar.gz'
  },
  {
    'title': 'Mac OS X 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-darwin-x86.tar.gz'
  },
  {
    'title': 'Linux 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-linux-x86.tar.gz'
  },
  {
    'title': 'Linux 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-linux-x64.tar.gz'
  },
  {
    'title': 'SunOS 32-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-sunos-x86.tar.gz'
  },
  {
    'title': 'SunOS 64-bit Binary',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%-sunos-x64.tar.gz'
  },
  {
    'title': 'Source Code',
    'templateUrl': 'https://nodejs.org/dist/v%version%/node-v%version%.tar.gz'
  }
]

function resolveUrl (item, version) {
  const url = item.templateUrl.replace(/%version%/g, version)
  return extend({ url }, item)
}

module.exports = (version) => {
  const downloads = semver.satisfies(version, '>= 1.0.0') ? postMergeDownloads : legacyDownloads
  return downloads.map((item) => resolveUrl(item, version))
}
<|endoftext|>


<commit_before>//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
//>>description: Widget factory extentions for mobile.
//>>label: Widget Factory Extensions

define( [ "jquery", "../external/requirejs/depend!./jquery.ui.widget[jquery]" ], function( $ ) {
//>>excludeEnd("jqmBuildExclude");
(function( $, undefined ) {

$.widget( "mobile.widget", {
	// decorate the parent _createWidget to trigger `widgetinit` for users
	// who wish to do post post `widgetcreate` alterations/additions
	//
	// TODO create a pull request for jquery ui to trigger this event
	// in the original _createWidget
	_createWidget: function() {
		$.Widget.prototype._createWidget.apply( this, arguments );
		this._trigger( 'init' );
	},

	_getCreateOptions: function() {

		var elem = this.element,
			options = {};

		$.each( this.options, function( option ) {

			var value = elem.jqmData( option.replace( /[A-Z]/g, function( c ) {
							return "-" + c.toLowerCase();
						})
					);

			if ( value !== undefined ) {
				options[ option ] = value;
			}
		});

		return options;
	},

	enhanceWithin: function( target, useKeepNative ) {
		var page, keepNative, widgetElements;

		widgetElements = $( this.options.initSelector, target );

		// if ignoreContentEnabled is set to true the framework should
		// only enhance the selected elements when they do NOT have a
		// parent with the data-namespace-ignore attribute
		// TODO use parentNode traversal to speed things up
		if ( $.mobile.ignoreContentEnabled ) {
			if ( !widgetElements.closest( ":jqmData(ignore)").length ) {
				widgetElements[ this.widgetName ]();
			}
		} else if( useKeepNative ) {
			// TODO remove dependency on the page widget for the keepNative.
			// Currently the keepNative value is defined on the page prototype so
			// the method is as well
			page = $.mobile.closestPageData( $(target) );
			keepNative = (page && page.keepNativeSelector()) || "";

			widgetElements.not( keepNative )[ this.widgetName ]();
		} else {
			widgetElements[ this.widgetName ]();
		}
	}
});

})( jQuery );
//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
});
//>>excludeEnd("jqmBuildExclude");
<commit_msg>mobile widget whitespace<commit_after>//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
//>>description: Widget factory extentions for mobile.
//>>label: Widget Factory Extensions

define( [ "jquery", "../external/requirejs/depend!./jquery.ui.widget[jquery]" ], function( $ ) {
//>>excludeEnd("jqmBuildExclude");
(function( $, undefined ) {

$.widget( "mobile.widget", {
	// decorate the parent _createWidget to trigger `widgetinit` for users
	// who wish to do post post `widgetcreate` alterations/additions
	//
	// TODO create a pull request for jquery ui to trigger this event
	// in the original _createWidget
	_createWidget: function() {
		$.Widget.prototype._createWidget.apply( this, arguments );
		this._trigger( 'init' );
	},

	_getCreateOptions: function() {

		var elem = this.element,
			options = {};

		$.each( this.options, function( option ) {

			var value = elem.jqmData( option.replace( /[A-Z]/g, function( c ) {
							return "-" + c.toLowerCase();
						})
					);

			if ( value !== undefined ) {
				options[ option ] = value;
			}
		});

		return options;
	},

	enhanceWithin: function( target, useKeepNative ) {
		var page, keepNative, widgetElements;

		widgetElements = $( this.options.initSelector, target );

		// if ignoreContentEnabled is set to true the framework should
		// only enhance the selected elements when they do NOT have a
		// parent with the data-namespace-ignore attribute
		// TODO use parentNode traversal to speed things up
		if ( $.mobile.ignoreContentEnabled ) {
			if ( !widgetElements.closest( ":jqmData(ignore)").length ) {
				widgetElements[ this.widgetName ]();
			}
		} else if ( useKeepNative ) {
			// TODO remove dependency on the page widget for the keepNative.
			// Currently the keepNative value is defined on the page prototype so
			// the method is as well
			page = $.mobile.closestPageData( $(target) );
			keepNative = (page && page.keepNativeSelector()) || "";

			widgetElements.not( keepNative )[ this.widgetName ]();
		} else {
			widgetElements[ this.widgetName ]();
		}
	}
});

})( jQuery );
//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
});
//>>excludeEnd("jqmBuildExclude");
<|endoftext|>


<commit_before>'use strict';
const yargs = require('yargs');
const execa = require('execa');
const util = require('util');
const glob = util.promisify(require('glob'));
const fs = require('fs-extra');
const path = require('path');

const rootDir = process.cwd();

let argv = yargs
  .usage(
    '$0 [-t|--tag]'
  )
  .command({
    command: '*',
    builder: yargs => {
      return yargs
        .option('t', {
          alias: 'tag',
          describe: 'the npm dist-tag',
          default: 'latest',
          type: 'string',
        });
    },
    handler: async argv => {

			const packageJsonData = JSON.parse(
				await fs.readFile(path.join(rootDir, 'package.json'))
			);

			const packageDirs = (
				await Promise.all(packageJsonData.workspaces.map((item) => glob(item)))
			).flat();

			await Promise.all(packageDirs.map((item) => {
				const publishCmd = `npm publish --tag ${argv.tag} --registry=https://registry.npmjs.org/ `;
				return execa(publishCmd, { shell: true, stdio: 'inherit', cwd: path.join(rootDir, item) });
			}))

    },
  })
  .help().argv;
<commit_msg>dev-infra: use access public<commit_after>'use strict';
const yargs = require('yargs');
const execa = require('execa');
const util = require('util');
const glob = util.promisify(require('glob'));
const fs = require('fs-extra');
const path = require('path');

const rootDir = process.cwd();

let argv = yargs
  .usage(
    '$0 [-t|--tag]'
  )
  .command({
    command: '*',
    builder: yargs => {
      return yargs
        .option('t', {
          alias: 'tag',
          describe: 'the npm dist-tag',
          default: 'latest',
          type: 'string',
        });
    },
    handler: async argv => {

			const packageJsonData = JSON.parse(
				await fs.readFile(path.join(rootDir, 'package.json'))
			);

			const packageDirs = (
				await Promise.all(packageJsonData.workspaces.map((item) => glob(item)))
			).flat();

			await Promise.all(packageDirs.map((item) => {
				const publishCmd = `npm publish --tag ${argv.tag} --registry=https://registry.npmjs.org/ --access public`;
				return execa(publishCmd, { shell: true, stdio: 'inherit', cwd: path.join(rootDir, item) });
			}))

    },
  })
  .help().argv;
<|endoftext|>


<commit_before>'use strict';

let log = require('intel'),
  Cookie = require('tough-cookie').Cookie,
  urlParser = require('url'),
  util = require('util'),
  moment = require('moment');

module.exports = {
  eventFromLogEntry: function(logEntry) {
    const logContent = JSON.parse(logEntry.message);

    return {
      timestamp: logEntry.timestamp,
      method: logContent.message.method,
      data: logContent.message.params,
      webview: logContent.webview
    }
  },
  harFromEvents: function(events) {
    let pages = [],
      entries = [],
      currentPageId,
      ongoingDataRequests = new Set();

    for (let event of events) {
      const data = event.data;
      switch (event.method) {
        case 'Page.frameStartedLoading':
        {
          currentPageId = 'page_' + (pages.length + 1);
          let page = {
            id: currentPageId,
            startedDateTime: moment(event.timestamp).toISOString(),
            title: '',
            pageTimings: {}
          };
          pages.push(page);
        }
          break;

        case 'Network.requestWillBeSent':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          const request = data.request;
          if (request.url.startsWith('data:')) {
            ongoingDataRequests.add(data.requestId);
            continue;
          }

          const cookieHeader = getHeaderValue(request.headers, 'Cookie');
          const cookies = parseCookies(cookieHeader.split(';'));

          let req = {
            method: request.method,
            url: request.url,
            _timestamp: data.timestamp,
            queryString: parsePostParams(request.url),
            headersSize: -1,
            bodySize: -1,
            cookies: cookies,
            headers: parseHeaders(request.headers)
          };

          let entry = {
            cache: {},
            startedDateTime: moment.unix(data.wallTime).toISOString(), //epoch float64, eg 1440589909.59248
            _requestId: data.requestId,
            pageref: currentPageId,
            request: req,
            time: 0
          };

          if (data.redirectResponse) {
            let previousEntry = entries.find((entry) => {
              return entry._requestId === data.requestId;
            });
            previousEntry._requestId = previousEntry._requestId + 'r';
            populateEntryFromResponse(previousEntry, data.redirectResponse, data.timestamp);
            previousEntry.response.redirectURL = request.url;
          }

          entries.push(entry);

          let page = pages[pages.length - 1];
          // this is the first request for this page, so set timestamp of page.
          if (!page._timestamp) {
            page._timestamp = data.timestamp;
            page.startedDateTime = entry.startedDateTime;
          }
        }
          break;

        case 'Network.responseReceived':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (data.response.url.startsWith('data:')) {
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Recieved network response for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          let page = pages.find((page) => {
            return page.id === currentPageId;
          });
          if (!page.title || page.title === '') {
            // URL is better than blank, and it's what devtools uses.
            page.title = data.response.url;
          }

          try {
            populateEntryFromResponse(entry, data.response, data.timestamp);
          } catch (e) {
            log.error('Error parsing response: ' + JSON.stringify(data, null, 2));
            throw e;
          }
        }
          break;

        case 'Network.dataReceived':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (ongoingDataRequests.has(data.requestId)) {
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Recieved network data for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          entry.response.content.size += data.dataLength;
        }
          break;

        case 'Network.loadingFinished':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (ongoingDataRequests.has(data.requestId)) {
            ongoingDataRequests.delete(data.requestId);
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Network loading finished for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          entry.response.bodySize = entry.response.content.size;
          //if (entry.response.headersSize > -1) {
          //  entry.response.bodySize -= entry.response.headersSize;
          //}

          // encodedDataLength will be -1 sometimes
          if (data.encodedDataLength > 0) {
            // encodedDataLength seems to be larger than body size sometimes. Perhaps it's because full packets are
            // listed even though the actual data might be very small.
            // I've seen dataLength: 416, encodedDataLength: 1016,

            const compression = Math.max(0, entry.response.bodySize - data.encodedDataLength);
            if (compression > 0) {
              entry.response.content.compression = compression;
            }
          }
          entry.time = (data.timestamp - entry.request._timestamp) * 1000;
          //entry.timings.receive = (entry.timings.receive + data.timestamp*1000);
        }
          break;

        case 'Page.loadEventFired':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue;
          }

          let page = pages[pages.length - 1];
          page.pageTimings.onLoad = Number((data.timestamp - page._timestamp) * 1000)
        }
          break;

        case 'Page.domContentEventFired':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue;
          }

          let page = pages[pages.length - 1];
          page.pageTimings.onContentLoad = Number((data.timestamp - page._timestamp) * 1000)
        }
          break;

        case 'Page.frameAttached':
          // FIXME should check for attachments to the main frame (e.g. fonts on izettle.com)
          // and not create a new page for those.
          break;

        case 'Page.frameScheduledNavigation':
        case 'Page.frameNavigated':
        case 'Page.frameStoppedLoading':
        case 'Page.frameClearedScheduledNavigation':
          // ignore
          break;

        case 'Network.requestServedFromCache':
        case 'Network.loadingFailed':
          // ignore
          break;

        default:
          log.warn('Unhandled event: ' + event.method);
          break;
      }
    }

    entries = entries.filter((entry) => {
      // Page doesn't wait for favicon to load, and that's ok (for now).
      if (!entry.response && !entry.request.url.endsWith('.ico')) {
        log.warn('Entry without response!: ' + JSON.stringify(entry));
      }
      return entry.response;
    });

    return {
      log: {
        version: "1.2",
        creator: {"name": "Browsertime", "version": "1.0"},
        pages,
        entries
      }
    }
  }
};

function calculateRequestHeaderSize(harRequest) {
  let buffer = '';
  buffer = buffer.concat(util.format('%s %s %s\r\n', harRequest.method, harRequest.url, harRequest.httpVersion));

  const headerLines = harRequest.headers.map((header) => util.format('%s: %s\r\n', header.name, header.value));
  buffer = buffer.concat(headerLines.join(''));
  buffer = buffer.concat('\r\n');

  return buffer.length;
}

function calculateResponseHeaderSize(perflogResponse) {
  let buffer = '';
  buffer = buffer.concat(util.format('%s %d %s\r\n', perflogResponse.protocol, perflogResponse.status, perflogResponse.statusText));
  Object.keys(perflogResponse.headers).forEach((key) => {
    buffer = buffer.concat(util.format('%s: %s\r\n', key, perflogResponse.headers[key]));
  });
  buffer = buffer.concat('\r\n');

  return buffer.length;
}

function populateEntryFromResponse(entry, response, timestamp) {
  entry.response = {
    httpVersion: response.protocol,
    redirectURL: '',
    status: response.status,
    statusText: response.statusText,
    content: {
      mimeType: response.mimeType,
      size: 0
    },
    headersSize: -1,
    bodySize: -1,
    cookies: [],
    headers: parseHeaders(response.headers)
  };

  entry.request.httpVersion = response.protocol;

  if (response.fromDiskCache === true && isHttp1x(response.protocol)) {
    entry.response.headersSize = calculateResponseHeaderSize(response);
  } else {
    if (response.requestHeaders) {
      entry.request.headers = parseHeaders(response.requestHeaders);

      const cookieHeader = getHeaderValue(response.requestHeaders, 'Cookie');
      entry.request.cookies = parseCookies(cookieHeader.split(';'));
    }

    if (isHttp1x(response.protocol)) {
      if (response.headersText) {
        entry.response.headersSize = response.headersText.length;
      } else {
        entry.response.headersSize = calculateResponseHeaderSize(response);
      }

      if (response.requestHeadersText) {
        entry.request.headersSize = response.requestHeadersText.length;
      } else {
        // Since entry.request.httpVersion is now set, we can calculate header size.
        entry.request.headersSize = calculateRequestHeaderSize(entry.request);
      }
    }
  }

  let blocked = response.timing["dnsStart"];
  if (blocked < 0.0) {
    blocked = 0.0
  }
  let dns = response.timing["dnsEnd"] - response.timing["dnsStart"];
  if (dns < 0.0) {
    dns = 0.0
  }
  let connect = response.timing["connectEnd"] - response.timing["connectStart"];
  if (connect < 0.0) {
    connect = 0.0
  }
  let send = response.timing["sendEnd"] - response.timing["sendStart"];
  if (send < 0.0) {
    send = 0.0
  }
  let wait = response.timing["receiveHeadersEnd"] - response.timing["sendEnd"];
  if (wait < 0.0) {
    wait = 0.0
  }
  let ssl = response.timing["sslEnd"] - response.timing["sslStart"];
  if (ssl < 0.0) {
    ssl = 0.0
  }

  entry.timings = {
    blocked,
    dns,
    connect,
    send,
    wait,
    receive: timestamp - response.timing["requestTime"],
    ssl
  };

  entry.time = (timestamp - response.timing.requestTime) * 1000;

}

function parseCookies(cookieStrings) {
  return cookieStrings.filter(Boolean).map((cookieString) => {
    let cookie = Cookie.parse(cookieString);
    return {
      'name': cookie.key,
      'value': cookie.value,
      'path': cookie.path || undefined, // must be undefined, not null, to exclude empty path
      'domain': cookie.domain || undefined,  // must be undefined, not null, to exclude empty domain
      'expires': cookie.expires === 'Infinity' ? undefined : moment(cookie.expires).toISOString(),
      'httpOnly': cookie.httpOnly,
      'secure': cookie.secure
    };
  });
}

function parseHeaders(headers) {
  if (!headers) {
    return [];
  }
  return Object.keys(headers).map((key) => {
    return {
      name: key,
      value: headers[key]
    }
  });
}

function getHeaderValue(headers, header) {
  if (!headers) {
    return '';
  }
  // http header names are case insensitive
  const lowerCaseHeader = header.toLowerCase();
  const headerNames = Object.keys(headers);
  return headerNames.filter((key) => key.toLowerCase() === lowerCaseHeader)
      .map((key) => headers[key]).shift() || '';
}

function parsePostParams(text) {
  var paramList = [];
  var queryString = urlParser.parse(text, true).query;
  Object.keys(queryString).forEach(function(name) {
    var value = queryString[name];
    if (!Array.isArray(value))
      value = [value];
    value.forEach(function(v) {
      paramList.push(
        {
          "name": name,
          "value": v
          //                    "fileName": "example.pdf",
          //                    "contentType": "application/pdf",
        });
    });
  });
  return paramList;
}

function isHttp1x(version) {
  return version.toLowerCase().startsWith('http/1.')
}<commit_msg>Store _wallTime on page and entry for debugging.<commit_after>'use strict';

let log = require('intel'),
  Cookie = require('tough-cookie').Cookie,
  urlParser = require('url'),
  util = require('util'),
  moment = require('moment');

module.exports = {
  eventFromLogEntry: function(logEntry) {
    const logContent = JSON.parse(logEntry.message);

    return {
      timestamp: logEntry.timestamp,
      method: logContent.message.method,
      data: logContent.message.params,
      webview: logContent.webview
    }
  },
  harFromEvents: function(events) {
    let pages = [],
      entries = [],
      currentPageId,
      ongoingDataRequests = new Set();

    for (let event of events) {
      const data = event.data;
      switch (event.method) {
        case 'Page.frameStartedLoading':
        {
          currentPageId = 'page_' + (pages.length + 1);
          let page = {
            id: currentPageId,
            startedDateTime: moment(event.timestamp).toISOString(),
            title: '',
            pageTimings: {}
          };
          pages.push(page);
        }
          break;

        case 'Network.requestWillBeSent':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          const request = data.request;
          if (request.url.startsWith('data:')) {
            ongoingDataRequests.add(data.requestId);
            continue;
          }

          const cookieHeader = getHeaderValue(request.headers, 'Cookie');
          const cookies = parseCookies(cookieHeader.split(';'));

          let req = {
            method: request.method,
            url: request.url,
            _timestamp: data.timestamp,
            queryString: parsePostParams(request.url),
            headersSize: -1,
            bodySize: -1,
            cookies: cookies,
            headers: parseHeaders(request.headers)
          };

          let entry = {
            cache: {},
            startedDateTime: moment.unix(data.wallTime).toISOString(), //epoch float64, eg 1440589909.59248
            _wallTime: data.wallTime,
            _requestId: data.requestId,
            pageref: currentPageId,
            request: req,
            time: 0
          };

          if (data.redirectResponse) {
            let previousEntry = entries.find((entry) => {
              return entry._requestId === data.requestId;
            });
            previousEntry._requestId = previousEntry._requestId + 'r';
            populateEntryFromResponse(previousEntry, data.redirectResponse, data.timestamp);
            previousEntry.response.redirectURL = request.url;
          }

          entries.push(entry);

          let page = pages[pages.length - 1];
          // this is the first request for this page, so set timestamp of page.
          if (!page._timestamp) {
            page._wallTime = data.wallTime;
            page._timestamp = data.timestamp;
            page.startedDateTime = entry.startedDateTime;
          }
        }
          break;

        case 'Network.responseReceived':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (data.response.url.startsWith('data:')) {
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Recieved network response for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          let page = pages.find((page) => {
            return page.id === currentPageId;
          });
          if (!page.title || page.title === '') {
            // URL is better than blank, and it's what devtools uses.
            page.title = data.response.url;
          }

          try {
            populateEntryFromResponse(entry, data.response, data.timestamp);
          } catch (e) {
            log.error('Error parsing response: ' + JSON.stringify(data, null, 2));
            throw e;
          }
        }
          break;

        case 'Network.dataReceived':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (ongoingDataRequests.has(data.requestId)) {
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Recieved network data for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          entry.response.content.size += data.dataLength;
        }
          break;

        case 'Network.loadingFinished':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue
          }
          if (ongoingDataRequests.has(data.requestId)) {
            ongoingDataRequests.delete(data.requestId);
            continue;
          }

          let entry = entries.find((entry) => {
            return entry._requestId === data.requestId;
          });
          if (!entry) {
            log.warn('Network loading finished for requestId ' + data.requestId + ' with no matching request.');
            continue;
          }

          entry.response.bodySize = entry.response.content.size;
          //if (entry.response.headersSize > -1) {
          //  entry.response.bodySize -= entry.response.headersSize;
          //}

          // encodedDataLength will be -1 sometimes
          if (data.encodedDataLength > 0) {
            // encodedDataLength seems to be larger than body size sometimes. Perhaps it's because full packets are
            // listed even though the actual data might be very small.
            // I've seen dataLength: 416, encodedDataLength: 1016,

            const compression = Math.max(0, entry.response.bodySize - data.encodedDataLength);
            if (compression > 0) {
              entry.response.content.compression = compression;
            }
          }
          entry.time = (data.timestamp - entry.request._timestamp) * 1000;
          //entry.timings.receive = (entry.timings.receive + data.timestamp*1000);
        }
          break;

        case 'Page.loadEventFired':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue;
          }

          let page = pages[pages.length - 1];
          page.pageTimings.onLoad = Number((data.timestamp - page._timestamp) * 1000)
        }
          break;

        case 'Page.domContentEventFired':
        {
          if (pages.length < 1) {
            //we havent loaded any pages yet.
            continue;
          }

          let page = pages[pages.length - 1];
          page.pageTimings.onContentLoad = Number((data.timestamp - page._timestamp) * 1000)
        }
          break;

        case 'Page.frameAttached':
          // FIXME should check for attachments to the main frame (e.g. fonts on izettle.com)
          // and not create a new page for those.
          break;

        case 'Page.frameScheduledNavigation':
        case 'Page.frameNavigated':
        case 'Page.frameStoppedLoading':
        case 'Page.frameClearedScheduledNavigation':
          // ignore
          break;

        case 'Network.requestServedFromCache':
        case 'Network.loadingFailed':
          // ignore
          break;

        default:
          log.warn('Unhandled event: ' + event.method);
          break;
      }
    }

    entries = entries.filter((entry) => {
      // Page doesn't wait for favicon to load, and that's ok (for now).
      if (!entry.response && !entry.request.url.endsWith('.ico')) {
        log.warn('Entry without response!: ' + JSON.stringify(entry));
      }
      return entry.response;
    });

    return {
      log: {
        version: "1.2",
        creator: {"name": "Browsertime", "version": "1.0"},
        pages,
        entries
      }
    }
  }
};

function calculateRequestHeaderSize(harRequest) {
  let buffer = '';
  buffer = buffer.concat(util.format('%s %s %s\r\n', harRequest.method, harRequest.url, harRequest.httpVersion));

  const headerLines = harRequest.headers.map((header) => util.format('%s: %s\r\n', header.name, header.value));
  buffer = buffer.concat(headerLines.join(''));
  buffer = buffer.concat('\r\n');

  return buffer.length;
}

function calculateResponseHeaderSize(perflogResponse) {
  let buffer = '';
  buffer = buffer.concat(util.format('%s %d %s\r\n', perflogResponse.protocol, perflogResponse.status, perflogResponse.statusText));
  Object.keys(perflogResponse.headers).forEach((key) => {
    buffer = buffer.concat(util.format('%s: %s\r\n', key, perflogResponse.headers[key]));
  });
  buffer = buffer.concat('\r\n');

  return buffer.length;
}

function populateEntryFromResponse(entry, response, timestamp) {
  entry.response = {
    httpVersion: response.protocol,
    redirectURL: '',
    status: response.status,
    statusText: response.statusText,
    content: {
      mimeType: response.mimeType,
      size: 0
    },
    headersSize: -1,
    bodySize: -1,
    cookies: [],
    headers: parseHeaders(response.headers)
  };

  entry.request.httpVersion = response.protocol;

  if (response.fromDiskCache === true && isHttp1x(response.protocol)) {
    entry.response.headersSize = calculateResponseHeaderSize(response);
  } else {
    if (response.requestHeaders) {
      entry.request.headers = parseHeaders(response.requestHeaders);

      const cookieHeader = getHeaderValue(response.requestHeaders, 'Cookie');
      entry.request.cookies = parseCookies(cookieHeader.split(';'));
    }

    if (isHttp1x(response.protocol)) {
      if (response.headersText) {
        entry.response.headersSize = response.headersText.length;
      } else {
        entry.response.headersSize = calculateResponseHeaderSize(response);
      }

      if (response.requestHeadersText) {
        entry.request.headersSize = response.requestHeadersText.length;
      } else {
        // Since entry.request.httpVersion is now set, we can calculate header size.
        entry.request.headersSize = calculateRequestHeaderSize(entry.request);
      }
    }
  }

  let blocked = response.timing["dnsStart"];
  if (blocked < 0.0) {
    blocked = 0.0
  }
  let dns = response.timing["dnsEnd"] - response.timing["dnsStart"];
  if (dns < 0.0) {
    dns = 0.0
  }
  let connect = response.timing["connectEnd"] - response.timing["connectStart"];
  if (connect < 0.0) {
    connect = 0.0
  }
  let send = response.timing["sendEnd"] - response.timing["sendStart"];
  if (send < 0.0) {
    send = 0.0
  }
  let wait = response.timing["receiveHeadersEnd"] - response.timing["sendEnd"];
  if (wait < 0.0) {
    wait = 0.0
  }
  let ssl = response.timing["sslEnd"] - response.timing["sslStart"];
  if (ssl < 0.0) {
    ssl = 0.0
  }

  entry.timings = {
    blocked,
    dns,
    connect,
    send,
    wait,
    receive: timestamp - response.timing["requestTime"],
    ssl
  };

  entry.time = (timestamp - response.timing.requestTime) * 1000;

}

function parseCookies(cookieStrings) {
  return cookieStrings.filter(Boolean).map((cookieString) => {
    let cookie = Cookie.parse(cookieString);
    return {
      'name': cookie.key,
      'value': cookie.value,
      'path': cookie.path || undefined, // must be undefined, not null, to exclude empty path
      'domain': cookie.domain || undefined,  // must be undefined, not null, to exclude empty domain
      'expires': cookie.expires === 'Infinity' ? undefined : moment(cookie.expires).toISOString(),
      'httpOnly': cookie.httpOnly,
      'secure': cookie.secure
    };
  });
}

function parseHeaders(headers) {
  if (!headers) {
    return [];
  }
  return Object.keys(headers).map((key) => {
    return {
      name: key,
      value: headers[key]
    }
  });
}

function getHeaderValue(headers, header) {
  if (!headers) {
    return '';
  }
  // http header names are case insensitive
  const lowerCaseHeader = header.toLowerCase();
  const headerNames = Object.keys(headers);
  return headerNames.filter((key) => key.toLowerCase() === lowerCaseHeader)
      .map((key) => headers[key]).shift() || '';
}

function parsePostParams(text) {
  var paramList = [];
  var queryString = urlParser.parse(text, true).query;
  Object.keys(queryString).forEach(function(name) {
    var value = queryString[name];
    if (!Array.isArray(value))
      value = [value];
    value.forEach(function(v) {
      paramList.push(
        {
          "name": name,
          "value": v
          //                    "fileName": "example.pdf",
          //                    "contentType": "application/pdf",
        });
    });
  });
  return paramList;
}

function isHttp1x(version) {
  return version.toLowerCase().startsWith('http/1.')
}<|endoftext|>


<commit_before>PP64.ns("utils");

// Converts extracted MTNX (animation) data into a Three.js AnimationClip.
PP64.utils.MtnxToThreeJs = class MtnxToThreeJs {
  constructor() {
  }

  createClip(mtnx) {
    const tracks = this._createTracks(mtnx);
    const duration = this._framesToSeconds(mtnx.totalFrames);
    return new THREE.AnimationClip("MTNX", duration, tracks);
  }

  _createTracks(mtnx) {
    const tracks = [];
    for (let i = 0; i < mtnx.tracks.length; i++) {
      const track = this._createTrack(mtnx.tracks[i]);
      if (track) {
        tracks.push(track);
      }
    }
    this._adjustTrackOrder(tracks);
    return tracks;
  }

  _createTrack(track) {
    const TrackType = PP64.utils.MTNX.TrackType;

    switch (track.type) {
      case TrackType.Transform:
        return this._createTransformTrack(track);
      case TrackType.Rotation:
        return this._createRotationTrack(track);
      case TrackType.Scale:
        return this._createScaleTrack(track);
    }

    console.warn(`Unsupported track type ${$$hex(track.type)}`);
  }

  _createTransformTrack(track) {
    const name = this._createTransformTrackName(track);
    const keyframes = this._createKeyframeSecsArr(track.keyframes);
    const data = this._createTransformTrackData(track);
    return new THREE.VectorKeyframeTrack(name, keyframes, data);
  }

  _createTransformTrackName(track) {
    const nodeName = this._getObjNameFromIndex(track.objIndex);
    const dimension = this._getDimensionProperty(track.dimension);
    return `${nodeName}.position[${dimension}]`;
  }

  _createTransformTrackData(track) {
    const data = [];
    for (let frame in track.keyframes) {
      let pos = track.keyframes[frame].value1;
      data.push(pos);
    }
    return data;
  }

  _createRotationTrack(track) {
    const name = this._createRotationTrackName(track);
    const keyframes = this._createKeyframeSecsArr(track.keyframes);
    const data = this._createRotationTrackData(track);
    return new THREE.QuaternionKeyframeTrack(name, keyframes, data);
  }

  _createRotationTrackName(track) {
    const nodeName = this._getObjNameFromIndex(track.objIndex);
    //const dimension = this._getDimensionProperty(track.dimension);
    return `${nodeName}.quaternion`;
  }

  _createRotationTrackData(track) {
    const data = [];
    for (let frame in track.keyframes) {
      const degrees = track.keyframes[frame].value1;
      const quaternion = this._createQuaternion(track.dimension, degrees);
      data.push(quaternion.x, quaternion.y, quaternion.z, quaternion.w);
    }
    return data;
  }

  _createQuaternion(dimension, degrees) {
    const quaternion = new THREE.Quaternion();

    const rads = $$number.degreesToRadians(degrees);

    const Dimension = PP64.utils.MTNX.Dimension;
    switch (dimension) {
      case Dimension.X:
        quaternion.setFromAxisAngle(new THREE.Vector3(1, 0, 0), rads);
        break;
      case Dimension.Y:
        quaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), rads);
        break;
      case Dimension.Z:
        quaternion.setFromAxisAngle(new THREE.Vector3(0, 0, 1), rads);
        break;
      default:
        console.warn(`Didn't set quaternion value for ${$$hex(dimension)}`);
    }

    return quaternion;
  }

  _createScaleTrack(track) {
    const name = this._createScaleTrackName(track);
    const keyframes = this._createKeyframeSecsArr(track.keyframes);
    const data = this._createScaleTrackData(track);
    return new THREE.VectorKeyframeTrack(name, keyframes, data);
  }

  _createScaleTrackName(track) {
    const nodeName = this._getObjNameFromIndex(track.objIndex);
    const dimension = this._getDimensionProperty(track.dimension);
    return `${nodeName}.scale[${dimension}]`;
  }

  _createScaleTrackData(track) {
    const data = [];
    for (let frame in track.keyframes) {
      let pos = track.keyframes[frame].value1;
      data.push(pos);
    }
    return data;
  }

  _createKeyframeSecsArr(keyframesObj) {
    const arr = [];
    for (let frame in keyframesObj) {
      arr.push(this._framesToSeconds(parseInt(frame)));
    }
    return arr;
  }

  _getDimensionProperty(dimension) {
    const Dimension = PP64.utils.MTNX.Dimension;
    switch (dimension) {
      case Dimension.X:
        return "x";
      case Dimension.Y:
        return "y";
      case Dimension.Z:
        return "z";
    }
    throw `Unknown dimension ${$$hex(dimension)}`;
  }

  _getObjNameFromIndex(index) {
    let cur3DIndex = -1;
    const objs = this.form.OBJ1[0].parsed.objects;
    for (let i = 0; i < objs.length; i++) {
      if (objs[i].objType === 0x3D) {
        cur3DIndex++;
        if (cur3DIndex === index) {
          return $$hex(objs[i].globalIndex);
        }
      }
    }

    console.warn(`Couldn't find associated object ${index} for track`);
    return $$hex(index); // Probably wrong
  }

  _framesToSeconds(frames) {
    return frames / 60;
  }

  _adjustTrackOrder(tracks) {
    // for (let i = 0; i < tracks.length - 2; i++) {
    //   if (tracks[i].name.indexOf("quaternion") >= 0
    //       && tracks[i + 1] && tracks[i + 1].name === tracks[i].name
    //       && tracks[i + 2] && tracks[i + 2].name === tracks[i].name)
    //   {
    //     // Swap to ensure ZYX ordering
    //     [tracks[i], tracks[i + 1], tracks[i + 2]]
    //     = [tracks[i + 2], tracks[i + 1], tracks[i]];
    //   }
    // }
  }
};
<commit_msg>Make animations better, just cheat and use rotation instead of quaternions<commit_after>PP64.ns("utils");

// Converts extracted MTNX (animation) data into a Three.js AnimationClip.
PP64.utils.MtnxToThreeJs = class MtnxToThreeJs {
  constructor() {
  }

  createClip(mtnx) {
    const tracks = this._createTracks(mtnx);
    const duration = this._framesToSeconds(mtnx.totalFrames);
    return new THREE.AnimationClip("MTNX", duration, tracks);
  }

  _createTracks(mtnx) {
    const tracks = [];
    for (let i = 0; i < mtnx.tracks.length; i++) {
      const track = this._createTrack(mtnx.tracks[i]);
      if (track) {
        tracks.push(track);
      }
    }
    this._adjustTrackOrder(tracks);
    return tracks;
  }

  _createTrack(track) {
    const TrackType = PP64.utils.MTNX.TrackType;

    switch (track.type) {
      case TrackType.Transform:
        return this._createTransformTrack(track);
      case TrackType.Rotation:
        return this._createRotationTrack(track);
      case TrackType.Scale:
        return this._createScaleTrack(track);
    }

    console.warn(`Unsupported track type ${$$hex(track.type)}`);
  }

  _createTransformTrack(track) {
    const name = this._createTransformTrackName(track);
    const keyframes = this._createKeyframeSecsArr(track.keyframes);
    const data = this._createTransformTrackData(track);
    return new THREE.VectorKeyframeTrack(name, keyframes, data);
  }

  _createTransformTrackName(track) {
    const nodeName = this._getObjNameFromIndex(track.objIndex);
    const dimension = this._getDimensionProperty(track.dimension);
    return `${nodeName}.position[${dimension}]`;
  }

  _createTransformTrackData(track) {
    const data = [];
    for (let frame in track.keyframes) {
      let pos = track.keyframes[frame].value1;
      data.push(pos);
    }
    return data;
  }

  _createRotationTrack(track) {
    const name = this._createRotationTrackName(track);
    const keyframes = this._createKeyframeSecsArr(track.keyframes);
    const data = this._createRotationTrackData(track);
    return new THREE.VectorKeyframeTrack(name, keyframes, data);
  }

  _createRotationTrackName(track) {
    const nodeName = this._getObjNameFromIndex(track.objIndex);
    const dimension = this._getDimensionProperty(track.dimension);
    return `${nodeName}.rotation[${dimension}]`;
  }

  _createRotationTrackData(track) {
    const data = [];
    for (let frame in track.keyframes) {
      const degrees = track.keyframes[frame].value1;
      const rads = $$number.degreesToRadians(degrees);
      data.push(rads);
    }
    return data;
  }

  _createQuaternion(dimension, degrees) {
    const quaternion = new THREE.Quaternion();

    const rads = $$number.degreesToRadians(degrees);

    const Dimension = PP64.utils.MTNX.Dimension;
    switch (dimension) {
      case Dimension.X:
        quaternion.setFromAxisAngle(new THREE.Vector3(1, 0, 0), rads);
        break;
      case Dimension.Y:
        quaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), rads);
        break;
      case Dimension.Z:
        quaternion.setFromAxisAngle(new THREE.Vector3(0, 0, 1), rads);
        break;
      default:
        console.warn(`Didn't set quaternion value for ${$$hex(dimension)}`);
    }

    return quaternion;
  }

  _createScaleTrack(track) {
    const name = this._createScaleTrackName(track);
    const keyframes = this._createKeyframeSecsArr(track.keyframes);
    const data = this._createScaleTrackData(track);
    return new THREE.VectorKeyframeTrack(name, keyframes, data);
  }

  _createScaleTrackName(track) {
    const nodeName = this._getObjNameFromIndex(track.objIndex);
    const dimension = this._getDimensionProperty(track.dimension);
    return `${nodeName}.scale[${dimension}]`;
  }

  _createScaleTrackData(track) {
    const data = [];
    for (let frame in track.keyframes) {
      let pos = track.keyframes[frame].value1;
      data.push(pos);
    }
    return data;
  }

  _createKeyframeSecsArr(keyframesObj) {
    const arr = [];
    for (let frame in keyframesObj) {
      arr.push(this._framesToSeconds(parseInt(frame)));
    }
    return arr;
  }

  _getDimensionProperty(dimension) {
    const Dimension = PP64.utils.MTNX.Dimension;
    switch (dimension) {
      case Dimension.X:
        return "x";
      case Dimension.Y:
        return "y";
      case Dimension.Z:
        return "z";
    }
    throw `Unknown dimension ${$$hex(dimension)}`;
  }

  _getObjNameFromIndex(index) {
    let cur3DIndex = -1;
    const objs = this.form.OBJ1[0].parsed.objects;
    for (let i = 0; i < objs.length; i++) {
      if (objs[i].objType === 0x3D) {
        cur3DIndex++;
        if (cur3DIndex === index) {
          return $$hex(objs[i].globalIndex);
        }
      }
    }

    console.warn(`Couldn't find associated object ${index} for track`);
    return $$hex(index); // Probably wrong
  }

  _framesToSeconds(frames) {
    return frames / 60;
  }

  _adjustTrackOrder(tracks) {
    // for (let i = 0; i < tracks.length - 2; i++) {
    //   if (tracks[i].name.indexOf("quaternion") >= 0
    //       && tracks[i + 1] && tracks[i + 1].name === tracks[i].name
    //       && tracks[i + 2] && tracks[i + 2].name === tracks[i].name)
    //   {
    //     // Swap to ensure ZYX ordering
    //     [tracks[i], tracks[i + 1], tracks[i + 2]]
    //     = [tracks[i + 2], tracks[i + 1], tracks[i]];
    //   }
    // }
  }
};
<|endoftext|>


<commit_before>'use strict';
if (typeof Models == 'undefined') var Models = {};

Models.RocketLanding = function(params)
{
	var nVars = Object.keys(this.vars).length;
	for(var i = 0; i < nVars; i++)
	{
		var key = Object.keys(this.vars)[i];
		this[key] = (typeof params[key] == 'undefined')?this.vars[key]:params[key];
	}
}

Models.RocketLanding.prototype.vars =
{
	TWR: 2,
	throttle: 1,
	g: 9.81,
	theta: 0,
	dtheta: 0,
	gimbalAngle: -0.1,
	Length: 40,
	Width: 2,
	x: 0,
	dx: 0,
	y: 0,
	dy: 0,
	T: 0,
};

Models.RocketLanding.prototype.detectCollision = function ()
{
	var L = this.Length;
	var W = this.Width;
	var s = Math.sin(this.theta);
	var c = Math.cos(this.theta);
	// points relative to the rockets CG that form a convex hull.
	var outerPoints = [{x:0,y:L/2},{x:1.8*W,y:-L/2-W},{x:-1.8*W,y:-L/2-W}];
	for (var i = 0; i < outerPoints.length; i++)
	{
		var p = outerPoints[i];
		if(p.x*s+p.y*c+this.y < 0) return true;
	}
	return false;
}

Models.RocketLanding.prototype.landed = function ()
{
	return this.detectCollision()
		&& Math.abs(this.x) < 30
		&& Math.abs(this.dx) < 5
		&& Math.abs(this.dy) < 5
		&& Math.abs(this.dtheta) < 0.1
		&& Math.abs(Math.sin(this.theta)) < 0.05
		&& Math.cos(this.theta) > 0;
}

Models.RocketLanding.prototype.simulate = function (dt, controlFunc)
{
	var copy = new Models.RocketLanding(this);

	if(!this.detectCollision())
	{
		var input = controlFunc(new Models.RocketLanding(this)); // call user controller
		if(typeof input != 'object' || typeof input.throttle != 'number' || typeof input.gimbalAngle != 'number')
			throw "Error: The controlFunction must return an object: {throttle:number, gimbalAngle:number}";
		copy.throttle = Math.max(0,Math.min(1,input.throttle)); // input limits
		copy.gimbalAngle = Math.max(-.2,Math.min(.2,input.gimbalAngle));
		var state = [this.x, this.dx, this.y, this.dy, this.theta, this.dtheta]; // state vector
		var soln = numeric.dopri(0,dt,state,function(t,x){ return Models.RocketLanding.ode(copy,x); },1e-4).at(dt); // numerical integration

		copy.x = soln[0]; // extract new state
		copy.dx = soln[1];
		copy.y = soln[2];
		copy.dy = soln[3];
		copy.theta = soln[4];
		copy.dtheta = soln[5];
		copy.T = this.T + dt; // count time
	}
	return copy;
}

Models.RocketLanding.ode = function (_this, x)
{
	var currentTWR = _this.TWR * _this.throttle;
	return[
		x[1],
		_this.g * currentTWR * Math.sin(x[4]+_this.gimbalAngle),
		x[3],
		_this.g * (currentTWR * Math.cos(x[4]+_this.gimbalAngle)-1),
		x[5],
		-_this.g * currentTWR * 6 / _this.Length * Math.sin(_this.gimbalAngle)
	];
}

Models.RocketLanding.prototype.draw = function (ctx, canvas)
{
	// clear canvas
	ctx.setTransform(1,0,0,1,0,0);
	ctx.clearRect(0,0,canvas.width,canvas.height);

	ctx.translate(canvas.width/2,0.95*canvas.height);
	ctx.scale(2,-2);

	var L = this.Length;
	var W = this.Width;

	// draw rocket
	ctx.save();
	ctx.translate(this.x,this.y);
	ctx.rotate(-this.theta);

	ctx.lineWidth=L/40;
	ctx.lineJoin = 'round';
	ctx.lineCap = 'round';

	// exhaust
	ctx.strokeStyle="#FF0000";
	ctx.beginPath();
	ctx.moveTo(W/4,-L/2);
	ctx.lineTo(-3*W*this.throttle*Math.sin(2*this.gimbalAngle),-L/2-3*W*this.throttle*Math.cos(2*this.gimbalAngle));
	ctx.lineTo(-W/4,-L/2);
	ctx.stroke();

	// hull
	ctx.strokeStyle="#4444FF";
	ctx.beginPath();
	ctx.moveTo(0,L/2);
	ctx.lineTo(-W/2,L/2-W);
	ctx.lineTo(-W/2,-L/2);
	ctx.lineTo(W/2,-L/2);
	ctx.lineTo(W/2,L/2-W);
	ctx.closePath();
	ctx.stroke();

	// left leg
	ctx.beginPath();
	ctx.moveTo(-W/2,-L/2);
	ctx.lineTo(-1.8*W,-L/2-W);
	ctx.lineTo(-W/2,-L/2+W);
	ctx.stroke();

	// right leg
	ctx.beginPath();
	ctx.moveTo(W/2,-L/2);
	ctx.lineTo(1.8*W,-L/2-W);
	ctx.lineTo(W/2,-L/2+W);
	ctx.stroke();

	ctx.restore();
	// end draw rocket

	// ground

	ctx.strokeStyle="#000055";
	drawLine(ctx,-10000,-1,10000,-1,2);
	for(var x = -30; x <= 30; x+=5)
	{
		drawLine(ctx,x,-1,x,-5,1);
	}

	if(this.detectCollision())
	{
		ctx.save();
		ctx.scale(1,-1);
		ctx.font="10px Verdana";
		ctx.textAlign="center";
		if(this.landed())
		{
			ctx.fillStyle="#009900";
			ctx.fillText("Landed!",0,-80);
		}
		else
		{
			ctx.fillStyle="#990000";
			ctx.fillText("CRASHED!",0,-80);
		}
		ctx.restore();
	}
}

Models.RocketLanding.prototype.infoText = function ()
{
	return  "/* Horizontal position */ rocket.x      = " + round(this.x,2)
		+ "\n/* Horizontal velocity */ rocket.dx     = " + round(this.dx,2)
		+ "\n/* Vertical position   */ rocket.y      = " + round(this.y,2)
		+ "\n/* Vertical velocity   */ rocket.dy     = " + round(this.dy,2)
		+ "\n/* Angle from vertical */ rocket.theta  = " + round(this.theta,2)
		+ "\n/* Angular velocity    */ rocket.dtheta = " + round(this.dtheta,2)
		+ "\n/* Simulation time     */ rocket.T      = " + round(this.T,2);
}
<commit_msg>landing constraints loosened<commit_after>'use strict';
if (typeof Models == 'undefined') var Models = {};

Models.RocketLanding = function(params)
{
	var nVars = Object.keys(this.vars).length;
	for(var i = 0; i < nVars; i++)
	{
		var key = Object.keys(this.vars)[i];
		this[key] = (typeof params[key] == 'undefined')?this.vars[key]:params[key];
	}
}

Models.RocketLanding.prototype.vars =
{
	TWR: 2,
	throttle: 1,
	g: 9.81,
	theta: 0,
	dtheta: 0,
	gimbalAngle: -0.1,
	Length: 40,
	Width: 2,
	x: 0,
	dx: 0,
	y: 0,
	dy: 0,
	T: 0,
};

Models.RocketLanding.prototype.detectCollision = function ()
{
	var L = this.Length;
	var W = this.Width;
	var s = Math.sin(this.theta);
	var c = Math.cos(this.theta);
	// points relative to the rockets CG that form a convex hull.
	var outerPoints = [{x:0,y:L/2},{x:1.8*W,y:-L/2-W},{x:-1.8*W,y:-L/2-W}];
	for (var i = 0; i < outerPoints.length; i++)
	{
		var p = outerPoints[i];
		if(p.x*s+p.y*c+this.y < 0) return true;
	}
	return false;
}

Models.RocketLanding.prototype.landed = function ()
{
	return this.detectCollision()
		&& Math.abs(this.x) < 30
		&& Math.abs(this.dx) < 5
		&& Math.abs(this.dy) < 10
		&& Math.abs(this.dtheta) < 0.1
		&& Math.abs(Math.sin(this.theta)) < 0.08
		&& Math.cos(this.theta) > 0;
}

Models.RocketLanding.prototype.simulate = function (dt, controlFunc)
{
	var copy = new Models.RocketLanding(this);

	if(!this.detectCollision())
	{
		var input = controlFunc(new Models.RocketLanding(this)); // call user controller
		if(typeof input != 'object' || typeof input.throttle != 'number' || typeof input.gimbalAngle != 'number')
			throw "Error: The controlFunction must return an object: {throttle:number, gimbalAngle:number}";
		copy.throttle = Math.max(0,Math.min(1,input.throttle)); // input limits
		copy.gimbalAngle = Math.max(-.2,Math.min(.2,input.gimbalAngle));
		var state = [this.x, this.dx, this.y, this.dy, this.theta, this.dtheta]; // state vector
		var soln = numeric.dopri(0,dt,state,function(t,x){ return Models.RocketLanding.ode(copy,x); },1e-4).at(dt); // numerical integration

		copy.x = soln[0]; // extract new state
		copy.dx = soln[1];
		copy.y = soln[2];
		copy.dy = soln[3];
		copy.theta = soln[4];
		copy.dtheta = soln[5];
		copy.T = this.T + dt; // count time
	}
	return copy;
}

Models.RocketLanding.ode = function (_this, x)
{
	var currentTWR = _this.TWR * _this.throttle;
	return[
		x[1],
		_this.g * currentTWR * Math.sin(x[4]+_this.gimbalAngle),
		x[3],
		_this.g * (currentTWR * Math.cos(x[4]+_this.gimbalAngle)-1),
		x[5],
		-_this.g * currentTWR * 6 / _this.Length * Math.sin(_this.gimbalAngle)
	];
}

Models.RocketLanding.prototype.draw = function (ctx, canvas)
{
	// clear canvas
	ctx.setTransform(1,0,0,1,0,0);
	ctx.clearRect(0,0,canvas.width,canvas.height);

	ctx.translate(canvas.width/2,0.95*canvas.height);
	ctx.scale(2,-2);

	var L = this.Length;
	var W = this.Width;

	// draw rocket
	ctx.save();
	ctx.translate(this.x,this.y);
	ctx.rotate(-this.theta);

	ctx.lineWidth=L/40;
	ctx.lineJoin = 'round';
	ctx.lineCap = 'round';

	// exhaust
	ctx.strokeStyle="#FF0000";
	ctx.beginPath();
	ctx.moveTo(W/4,-L/2);
	ctx.lineTo(-3*W*this.throttle*Math.sin(2*this.gimbalAngle),-L/2-3*W*this.throttle*Math.cos(2*this.gimbalAngle));
	ctx.lineTo(-W/4,-L/2);
	ctx.stroke();

	// hull
	ctx.strokeStyle="#4444FF";
	ctx.beginPath();
	ctx.moveTo(0,L/2);
	ctx.lineTo(-W/2,L/2-W);
	ctx.lineTo(-W/2,-L/2);
	ctx.lineTo(W/2,-L/2);
	ctx.lineTo(W/2,L/2-W);
	ctx.closePath();
	ctx.stroke();

	// left leg
	ctx.beginPath();
	ctx.moveTo(-W/2,-L/2);
	ctx.lineTo(-1.8*W,-L/2-W);
	ctx.lineTo(-W/2,-L/2+W);
	ctx.stroke();

	// right leg
	ctx.beginPath();
	ctx.moveTo(W/2,-L/2);
	ctx.lineTo(1.8*W,-L/2-W);
	ctx.lineTo(W/2,-L/2+W);
	ctx.stroke();

	ctx.restore();
	// end draw rocket

	// ground

	ctx.strokeStyle="#000055";
	drawLine(ctx,-10000,-1,10000,-1,2);
	for(var x = -30; x <= 30; x+=5)
	{
		drawLine(ctx,x,-1,x,-5,1);
	}

	if(this.detectCollision())
	{
		ctx.save();
		ctx.scale(1,-1);
		ctx.font="10px Verdana";
		ctx.textAlign="center";
		if(this.landed())
		{
			ctx.fillStyle="#009900";
			ctx.fillText("Landed!",0,-80);
		}
		else
		{
			ctx.fillStyle="#990000";
			ctx.fillText("CRASHED!",0,-80);
		}
		ctx.restore();
	}
}

Models.RocketLanding.prototype.infoText = function ()
{
	return  "/* Horizontal position */ rocket.x      = " + round(this.x,2)
		+ "\n/* Horizontal velocity */ rocket.dx     = " + round(this.dx,2)
		+ "\n/* Vertical position   */ rocket.y      = " + round(this.y,2)
		+ "\n/* Vertical velocity   */ rocket.dy     = " + round(this.dy,2)
		+ "\n/* Angle from vertical */ rocket.theta  = " + round(this.theta,2)
		+ "\n/* Angular velocity    */ rocket.dtheta = " + round(this.dtheta,2)
		+ "\n/* Simulation time     */ rocket.T      = " + round(this.T,2);
}
<|endoftext|>