<commit_before>async function addCollection() {
    await syncTree();

    var selectedCollection = getSelectedCollection();
    var selectedNode = getSelectedNode();

    var newCollection = {
        text: $('#addCollectionName').val(),
        showBookmarkDescription: true,
        showBookmarkIcon: true,
        showBookmarksAsCards: false,
        bookmarkIconSize: 16,
        nodeId: getRandomInt(Number.MAX_SAFE_INTEGER),
        parentId: selectedCollection.nodeId
    };

    if (typeof selectedCollection == 'undefined' || selectedCollection.id === "tree") {
        selectedCollection = newCollection;
        updateTree(selectedCollection);
    } else {
        if (typeof selectedCollection.nodes == 'undefined') {
            selectedCollection.nodes = [];
        }

        tree.addNode(newCollection, selectedNode);

        updateTree();
    }

    $('#addCollectionName').val("");
}

async function addBookmark() {
    $("#addBookmarkSpinner").show();

    await syncTree();

    var selectedCollection = getSelectedCollection();

    if (typeof selectedCollection.bookmarks == 'undefined') {
        selectedCollection.bookmarks = [];
    }

    var bookmark = {
        text: $('#addBookmarkName').val(),
        url: $('#addBookmarkUrl').val(),
        description: $('#addBookmarkDescription').val()
    };

    // Get Favicon
    var faviconUrl = "https://favicon.sboulema.nl/favicon?url=" + $('#addBookmarkUrl').val();
    var faviconData = await $.get(faviconUrl + "&base64=true");

    bookmark.icon = faviconUrl;
    bookmark.iconData = faviconData;

    selectedCollection.bookmarks.push(bookmark);

    $('#addBookmarkName').val("");
    $('#addBookmarkUrl').val("");
    $('#addBookmarkDescription').val("");

    selectedCollection.tags = [selectedCollection.bookmarks.length];

    updateTree();

    $("#addBookmarkModal").modal('hide');
}

function deleteBookmark() {
    var selectedCollection = getSelectedCollection();
    var bookmarkIndex = parseInt($("#editIndex").text());

    selectedCollection.bookmarks.splice(bookmarkIndex, 1);

    updateTree();
}

function deleteCollection() {
    var collection = getSelectedCollection();

    if (typeof collection.parentId != 'undefined') {
        var parent = tree.getDataById(collection.parentId);

        $.each(parent.nodes, function (index, node) {
            if (node.nodeId === collection.nodeId) {
                parent.nodes.splice(index, 1);
                updateTree();
                return false;
            }
        });
    } else {
        var index = getIndex(collection);
        var parent = getParent(collection)[0];

        if (parent.id === "tree") {
            var collections = getCollections();

            collections.splice(index, 1);

            updateTree(collections, true);
        } else {
            parent.nodes.slice(index, 1);

            updateTree();
        }
    }
}

function editBookmark(node, isCard) {
    var bookmark;
    var bookmarkIndex;

    if (isCard) {
        bookmark = getSelectedBookmarkCard(node);
        bookmarkIndex = getIndexCard(node);
    } else {
        bookmark = getSelectedBookmark(node);
        bookmarkIndex = getIndex(node);
    }
    var selectedCollection = getSelectedCollection();

    $("#editName").val(bookmark.text);
    $("#editUrl").val(bookmark.url);
    $("#editIcon").val(bookmark.icon);
    $("#editIconAddon").attr("src", bookmark.icon);
    $("#editIndex").text(bookmarkIndex);
    $("#editDescription").val(bookmark.description);
    $("#editTags").val(bookmark.tags);
    $("#editBookmarkCollection").val(selectedCollection.text);

    $('#editModal').modal('show');
}

// Show 'Edit Collection' modal
function editCollection() {
    var selectedCollection = getSelectedCollection();

    $("#editCollectionName").val(selectedCollection.text);
    $("#editCollectionIcon").val(selectedCollection.icon);
    $("#editCollectionBackground").val(selectedCollection.background);
    $("#showBookmarkIconCheckbox").prop('checked', selectedCollection.showBookmarkIcon);
    $("#showBookmarkDescriptionCheckbox").prop('checked', selectedCollection.showBookmarkDescription);
    $("#bookmarkIconSizeSlider").val(selectedCollection.bookmarkIconSize);
    $("#showBookmarksAsCardsCheckbox").prop('checked', selectedCollection.showBookmarksAsCards);
    $("#editCollectionIconAddon").html("");

    $("#editIndex").text(getIndex(selectedCollection));
    $("#editCollectionParent").val(getParent(selectedCollection).text);

    $('#editCollectionModal').modal('show');
}

function saveBookmark() {
    var selectedCollection = getSelectedCollection();
    var bookmarkIndex = parseInt($("#editIndex").text());

    selectedCollection.bookmarks[bookmarkIndex].text = $("#editName").val();
    selectedCollection.bookmarks[bookmarkIndex].url = $("#editUrl").val();
    selectedCollection.bookmarks[bookmarkIndex].icon = $("#editIcon").val();
    selectedCollection.bookmarks[bookmarkIndex].description = $("#editDescription").val();
    selectedCollection.bookmarks[bookmarkIndex].tags = $("#editTags").val();

    if ($("#editIcon").val().startsWith("https://favicon.sboulema.nl/favicon")) {
        $.get($("#editIcon").val() + "&base64=true", function(data) {
            selectedCollection.bookmarks[bookmarkIndex].iconData = data;
            updateTree();
        });
    }

    var moveToCollectionId = $("#editMoveToCollection").text();

    if (moveToCollectionId != "") {
        var moveToCollection = tree.getDataById($("#editMoveToCollection").text());

        if (typeof moveToCollection != 'undefined' &&
            typeof moveToCollection.nodeId != 'undefined' &&
            selectedCollection !== moveToCollection) {
            var bookmark = selectedCollection.bookmarks[bookmarkIndex];
            selectedCollection.bookmarks.splice(bookmarkIndex, 1);
            moveToCollection.bookmarks.push(bookmark);
            moveToCollection.tags = [moveToCollection.bookmarks.length];
            selectedCollection.tags = [selectedCollection.bookmarks.length];
        }
    }

    updateTree();
}

function saveCollection() {
    var selectedCollection = getSelectedCollection();

    selectedCollection.text = $("#editCollectionName").val();
    selectedCollection.icon = $("#editCollectionIcon").val();
    selectedCollection.background = $("#editCollectionBackground").val();
    selectedCollection.showBookmarkIcon = $("#showBookmarkIconCheckbox").is(':checked');
    selectedCollection.showBookmarkDescription = $("#showBookmarkDescriptionCheckbox").is(':checked');
    selectedCollection.bookmarkIconSize = $("#bookmarkIconSizeSlider").val();
    selectedCollection.showBookmarksAsCards = $("#showBookmarksAsCardsCheckbox").is(':checked');

    // Check if we need to move the collection to a new parent
    var moveToCollection = tree.getDataByText($("#editMoveToCollection").text());
    var parentCollection = getParent(selectedCollection);

    if (typeof moveToCollection != 'undefined' &&
        typeof moveToCollection.nodeId != 'undefined' &&
        moveToCollection !== parentCollection) {
        var copyCollection = $.extend(true, {}, selectedCollection);

        if (typeof moveToCollection.nodes == 'undefined') {
            moveToCollection.nodes = [];
        }

        moveToCollection.nodes.push(copyCollection);

        deleteCollection();
    }

    tree.reload();
    tree.expand(tree.getNodeById(selectedCollection.parentId));
    tree.select(tree.getNodeById(selectedCollection.nodeId));

    updateTree();
}

function showBookmarks(collection, showAsCards) {
    var bookmarksHtml = "";

    if (collection.showBookmarksAsCards) {
        $.each(collection.bookmarks, function (index, bookmark) {
            var item = "
";

            item += "";
            if (typeof bookmark.icon != 'undefined' && bookmark.icon !== "" && !bookmark.icon.startsWith("fa")) {
                item += "";
            } else {
                item += "";
            }
            item += "";

            item += "<button type='button' class='btn btn-primary btn-sm cardEditButton' onclick='editBookmark(this, true)'>" +
                "</button>";

            item += "
";

            bookmarksHtml += item;
        });

    } else {
        $.each(collection.bookmarks, function (index, bookmark) {

            var item = "
";
            item += "
";

            item += "
";

            if (collection.showBookmarkIcon) {
                if (typeof bookmark.icon != 'undefined' && bookmark.icon !== "" && !bookmark.icon.startsWith("fa")) {
                    item += "";
                    item += "" + bookmark.text + "";
                } else {
                    item += "";
                    item += "" + bookmark.text + "";
                }
            }

            if (collection.showBookmarkDescription && typeof bookmark.description != 'undefined' && bookmark.description != "") {
                item += " - " + bookmark.description + "";
            }

            item += showTags(bookmark);

            item += "
";

            item += createBookmarkButtons(bookmark);

            item += "
";
            item += "
"

            bookmarksHtml += item;
        });
    }

    return bookmarksHtml;
}

function showTags(bookmark) {
    var tags = $("", {
        class: "tag"
    });

    if (typeof bookmark.tags === 'undefined' || bookmark.tags === "") {
        return tags.html();
    }

    bookmark.tags.split(",").forEach(tag => {
        tags.append("" + tag + "");
    });

    return tags[0].outerHTML;
}

function createBookmarkButtons(bookmark) {
    var buttons = $("
", {
        class: "col-auto btn-toolbar",
        role: "toolbar"
    });

    buttons.append(
        "
" +
        "<button type='button' class='btn btn-outline-secondary btn-sm' onclick='editBookmark(this)'>" +
        "</button>" +
        "
"
    );

    var copyButton = $(
        "
" +
        "<button type='button' class='btn btn-outline-secondary btn-sm btn-clipboard' data-clipboard-text='" +
        bookmark.url +  "'>" + "</button>" +
        "
"
    ).appendTo(buttons);

    if (navigator.share) {
        copyButton.addClass("mr-2");

        buttons.append(
            "
" +
            "<button type='button' class='btn btn-outline-secondary btn-sm' onclick='shareBookmark(this)'>" +
            "</button>" +
            "
"
        );
    }

    return buttons[0].outerHTML;
}

function shareBookmark(node) {
    var bookmark = getSelectedBookmark(node);

    navigator.share({
        title: bookmark.text,
        text: bookmark.description,
        url: bookmark.url,
    })
      .then(() => console.log('Successful share'))
      .catch((error) => console.log('Error sharing', error));
}

function getCollectionBackground(collection) {
    if (typeof collection !== 'undefined' &&
        typeof collection.background !== 'undefined' &&
        collection.background !== "inherit") {
        return collection.background;
    }

    if (collection.id === "tree") {
        return "";
    }

    return getCollectionBackground(getParent(collection));
}

function getParent(collection) {
    return tree.getDataById(collection.parentId);
}

function getRandomInt(max) {
    return Math.floor(Math.random() * Math.floor(max));
}<commit_msg>:bug: Fix adding bookmark when website has no favicon<commit_after>async function addCollection() {
    await syncTree();

    var selectedCollection = getSelectedCollection();
    var selectedNode = getSelectedNode();

    var newCollection = {
        text: $('#addCollectionName').val(),
        showBookmarkDescription: true,
        showBookmarkIcon: true,
        showBookmarksAsCards: false,
        bookmarkIconSize: 16,
        nodeId: getRandomInt(Number.MAX_SAFE_INTEGER),
        parentId: selectedCollection.nodeId
    };

    if (typeof selectedCollection == 'undefined' || selectedCollection.id === "tree") {
        selectedCollection = newCollection;
        updateTree(selectedCollection);
    } else {
        if (typeof selectedCollection.nodes == 'undefined') {
            selectedCollection.nodes = [];
        }

        tree.addNode(newCollection, selectedNode);

        updateTree();
    }

    $('#addCollectionName').val("");
}

async function addBookmark() {
    $("#addBookmarkSpinner").show();

    await syncTree();

    var selectedCollection = getSelectedCollection();

    if (typeof selectedCollection.bookmarks == 'undefined') {
        selectedCollection.bookmarks = [];
    }

    var bookmark = {
        text: $('#addBookmarkName').val(),
        url: $('#addBookmarkUrl').val(),
        description: $('#addBookmarkDescription').val()
    };

    // Get Favicon
    var faviconUrl = "https://favicon.sboulema.nl/favicon?url=" + $('#addBookmarkUrl').val();
    var faviconData = await $.get(faviconUrl + "&base64=true");

    if (typeof faviconData === 'undefined') {
        faviconUrl = "";
        faviconData = "";
    }

    bookmark.icon = faviconUrl;
    bookmark.iconData = faviconData;

    selectedCollection.bookmarks.push(bookmark);

    $('#addBookmarkName').val("");
    $('#addBookmarkUrl').val("");
    $('#addBookmarkDescription').val("");

    selectedCollection.tags = [selectedCollection.bookmarks.length];

    updateTree();

    $("#addBookmarkModal").modal('hide');
}

function deleteBookmark() {
    var selectedCollection = getSelectedCollection();
    var bookmarkIndex = parseInt($("#editIndex").text());

    selectedCollection.bookmarks.splice(bookmarkIndex, 1);

    updateTree();
}

function deleteCollection() {
    var collection = getSelectedCollection();

    if (typeof collection.parentId != 'undefined') {
        var parent = tree.getDataById(collection.parentId);

        $.each(parent.nodes, function (index, node) {
            if (node.nodeId === collection.nodeId) {
                parent.nodes.splice(index, 1);
                updateTree();
                return false;
            }
        });
    } else {
        var index = getIndex(collection);
        var parent = getParent(collection)[0];

        if (parent.id === "tree") {
            var collections = getCollections();

            collections.splice(index, 1);

            updateTree(collections, true);
        } else {
            parent.nodes.slice(index, 1);

            updateTree();
        }
    }
}

function editBookmark(node, isCard) {
    var bookmark;
    var bookmarkIndex;

    if (isCard) {
        bookmark = getSelectedBookmarkCard(node);
        bookmarkIndex = getIndexCard(node);
    } else {
        bookmark = getSelectedBookmark(node);
        bookmarkIndex = getIndex(node);
    }
    var selectedCollection = getSelectedCollection();

    $("#editName").val(bookmark.text);
    $("#editUrl").val(bookmark.url);
    $("#editIcon").val(bookmark.icon);
    $("#editIconAddon").attr("src", bookmark.icon);
    $("#editIndex").text(bookmarkIndex);
    $("#editDescription").val(bookmark.description);
    $("#editTags").val(bookmark.tags);
    $("#editBookmarkCollection").val(selectedCollection.text);

    $('#editModal').modal('show');
}

// Show 'Edit Collection' modal
function editCollection() {
    var selectedCollection = getSelectedCollection();

    $("#editCollectionName").val(selectedCollection.text);
    $("#editCollectionIcon").val(selectedCollection.icon);
    $("#editCollectionBackground").val(selectedCollection.background);
    $("#showBookmarkIconCheckbox").prop('checked', selectedCollection.showBookmarkIcon);
    $("#showBookmarkDescriptionCheckbox").prop('checked', selectedCollection.showBookmarkDescription);
    $("#bookmarkIconSizeSlider").val(selectedCollection.bookmarkIconSize);
    $("#showBookmarksAsCardsCheckbox").prop('checked', selectedCollection.showBookmarksAsCards);
    $("#editCollectionIconAddon").html("");

    $("#editIndex").text(getIndex(selectedCollection));
    $("#editCollectionParent").val(getParent(selectedCollection).text);

    $('#editCollectionModal').modal('show');
}

function saveBookmark() {
    var selectedCollection = getSelectedCollection();
    var bookmarkIndex = parseInt($("#editIndex").text());

    selectedCollection.bookmarks[bookmarkIndex].text = $("#editName").val();
    selectedCollection.bookmarks[bookmarkIndex].url = $("#editUrl").val();
    selectedCollection.bookmarks[bookmarkIndex].icon = $("#editIcon").val();
    selectedCollection.bookmarks[bookmarkIndex].description = $("#editDescription").val();
    selectedCollection.bookmarks[bookmarkIndex].tags = $("#editTags").val();

    if ($("#editIcon").val().startsWith("https://favicon.sboulema.nl/favicon")) {
        $.get($("#editIcon").val() + "&base64=true", function(data) {
            selectedCollection.bookmarks[bookmarkIndex].iconData = data;
            updateTree();
        });
    }

    var moveToCollectionId = $("#editMoveToCollection").text();

    if (moveToCollectionId != "") {
        var moveToCollection = tree.getDataById($("#editMoveToCollection").text());

        if (typeof moveToCollection != 'undefined' &&
            typeof moveToCollection.nodeId != 'undefined' &&
            selectedCollection !== moveToCollection) {
            var bookmark = selectedCollection.bookmarks[bookmarkIndex];
            selectedCollection.bookmarks.splice(bookmarkIndex, 1);
            moveToCollection.bookmarks.push(bookmark);
            moveToCollection.tags = [moveToCollection.bookmarks.length];
            selectedCollection.tags = [selectedCollection.bookmarks.length];
        }
    }

    updateTree();
}

function saveCollection() {
    var selectedCollection = getSelectedCollection();

    selectedCollection.text = $("#editCollectionName").val();
    selectedCollection.icon = $("#editCollectionIcon").val();
    selectedCollection.background = $("#editCollectionBackground").val();
    selectedCollection.showBookmarkIcon = $("#showBookmarkIconCheckbox").is(':checked');
    selectedCollection.showBookmarkDescription = $("#showBookmarkDescriptionCheckbox").is(':checked');
    selectedCollection.bookmarkIconSize = $("#bookmarkIconSizeSlider").val();
    selectedCollection.showBookmarksAsCards = $("#showBookmarksAsCardsCheckbox").is(':checked');

    // Check if we need to move the collection to a new parent
    var moveToCollection = tree.getDataByText($("#editMoveToCollection").text());
    var parentCollection = getParent(selectedCollection);

    if (typeof moveToCollection != 'undefined' &&
        typeof moveToCollection.nodeId != 'undefined' &&
        moveToCollection !== parentCollection) {
        var copyCollection = $.extend(true, {}, selectedCollection);

        if (typeof moveToCollection.nodes == 'undefined') {
            moveToCollection.nodes = [];
        }

        moveToCollection.nodes.push(copyCollection);

        deleteCollection();
    }

    tree.reload();
    tree.expand(tree.getNodeById(selectedCollection.parentId));
    tree.select(tree.getNodeById(selectedCollection.nodeId));

    updateTree();
}

function showBookmarks(collection, showAsCards) {
    var bookmarksHtml = "";

    if (collection.showBookmarksAsCards) {
        $.each(collection.bookmarks, function (index, bookmark) {
            var item = "
";

            item += "";
            if (typeof bookmark.icon != 'undefined' && bookmark.icon !== "" && !bookmark.icon.startsWith("fa")) {
                item += "";
            } else {
                item += "";
            }
            item += "";

            item += "<button type='button' class='btn btn-primary btn-sm cardEditButton' onclick='editBookmark(this, true)'>" +
                "</button>";

            item += "
";

            bookmarksHtml += item;
        });

    } else {
        $.each(collection.bookmarks, function (index, bookmark) {

            var item = "
";
            item += "
";

            item += "
";

            if (collection.showBookmarkIcon) {
                if (typeof bookmark.icon != 'undefined' && bookmark.icon !== "" && !bookmark.icon.startsWith("fa")) {
                    item += "";
                    item += "" + bookmark.text + "";
                } else {
                    item += "";
                    item += "" + bookmark.text + "";
                }
            }

            if (collection.showBookmarkDescription && typeof bookmark.description != 'undefined' && bookmark.description != "") {
                item += " - " + bookmark.description + "";
            }

            item += showTags(bookmark);

            item += "
";

            item += createBookmarkButtons(bookmark);

            item += "
";
            item += "
"

            bookmarksHtml += item;
        });
    }

    return bookmarksHtml;
}

function showTags(bookmark) {
    var tags = $("", {
        class: "tag"
    });

    if (typeof bookmark.tags === 'undefined' || bookmark.tags === "") {
        return tags.html();
    }

    bookmark.tags.split(",").forEach(tag => {
        tags.append("" + tag + "");
    });

    return tags[0].outerHTML;
}

function createBookmarkButtons(bookmark) {
    var buttons = $("
", {
        class: "col-auto btn-toolbar",
        role: "toolbar"
    });

    buttons.append(
        "
" +
        "<button type='button' class='btn btn-outline-secondary btn-sm' onclick='editBookmark(this)'>" +
        "</button>" +
        "
"
    );

    var copyButton = $(
        "
" +
        "<button type='button' class='btn btn-outline-secondary btn-sm btn-clipboard' data-clipboard-text='" +
        bookmark.url +  "'>" + "</button>" +
        "
"
    ).appendTo(buttons);

    if (navigator.share) {
        copyButton.addClass("mr-2");

        buttons.append(
            "
" +
            "<button type='button' class='btn btn-outline-secondary btn-sm' onclick='shareBookmark(this)'>" +
            "</button>" +
            "
"
        );
    }

    return buttons[0].outerHTML;
}

function shareBookmark(node) {
    var bookmark = getSelectedBookmark(node);

    navigator.share({
        title: bookmark.text,
        text: bookmark.description,
        url: bookmark.url,
    })
      .then(() => console.log('Successful share'))
      .catch((error) => console.log('Error sharing', error));
}

function getCollectionBackground(collection) {
    if (typeof collection !== 'undefined' &&
        typeof collection.background !== 'undefined' &&
        collection.background !== "inherit") {
        return collection.background;
    }

    if (collection.id === "tree") {
        return "";
    }

    return getCollectionBackground(getParent(collection));
}

function getParent(collection) {
    return tree.getDataById(collection.parentId);
}

function getRandomInt(max) {
    return Math.floor(Math.random() * Math.floor(max));
}<|endoftext|>


<commit_before>/**
 * Call, Apply and Bind change the context for `this`.
 */

var myFancyObj = {
  name: 'Jill',
  fn: function(arg1, arg2) {
    return [this.name, ' says ', arg1, ' and ', arg2].join('');
  }
};

var daveObj = {
  name: "Dave"
};

var stewObj = {
  name: "Stew"
};

var marthaObj = {
  name: 'Martha'
};

/**
 * Execute a function normally
 */

myFancyObj.fn('pow', 'zap!');

// => Jill says pow and zap!

/**
 * Function.prototype.call()
 *
 * Execute a function with a different context for `this`.
 * Properties are passed as individual arguments.
 */

myFancyObj.fn.call(daveObj, 'bam', 'baff!');

// => Dave says bam and baff!

/**
 * Function.prototype.apply()
 *
 * Execute a function with a different context for `this`.
 * Properties are passed as an array.
 */

myFancyObj.fn.apply(stewObj, ['spiff', 'zing!']);

// => Stew says spiff and zing!

/**
 * Function.prototype.bind()
 *
 * Define a new function with a different context for `this`.
 * Execute the new function someplace else.
 * Properties are passed as individual arguments.
 */

var boundObj = myFancyObj.fn.bind(marthaObj, 'ping');

boundObj('baff!');

// => Martha says ping and baff!
<commit_msg>join(' ');<commit_after>/**
 * Call, Apply and Bind change the context for `this`.
 */

var myFancyObj = {
  name: 'Jill',
  fn: function(arg1, arg2) {
    return [this.name, 'says', arg1, 'and', arg2].join(' ');
  }
};

var daveObj = {
  name: "Dave"
};

var stewObj = {
  name: "Stew"
};

var marthaObj = {
  name: 'Martha'
};

/**
 * Execute a function normally
 */

myFancyObj.fn('pow', 'zap!');

// => Jill says pow and zap!

/**
 * Function.prototype.call()
 *
 * Execute a function with a different context for `this`.
 * Properties are passed as individual arguments.
 */

myFancyObj.fn.call(daveObj, 'bam', 'baff!');

// => Dave says bam and baff!

/**
 * Function.prototype.apply()
 *
 * Execute a function with a different context for `this`.
 * Properties are passed as an array.
 */

myFancyObj.fn.apply(stewObj, ['spiff', 'zing!']);

// => Stew says spiff and zing!

/**
 * Function.prototype.bind()
 *
 * Define a new function with a different context for `this`.
 * Execute the new function someplace else.
 * Properties are passed as individual arguments.
 */

var boundObj = myFancyObj.fn.bind(marthaObj, 'ping');

boundObj('baff!');

// => Martha says ping and baff!
<|endoftext|>


<commit_before>// Specification: http://oauth.net/core/1.0/#anchor14
var SignatureBaseString = (function () {
    'use strict';

    function SignatureBaseString(httpMethod, requestUrl, parameters) {
        this._httpMethod = httpMethod || '';
        this._requestUrl = requestUrl || '';
	    this._parameters = {}; // Format: { 'key': ['value 1', 'value 2'] };
	    this._loadParameters(parameters);
	    this._sortedKeys = [];
	    this._concatenatedParameters = '';
    }

	SignatureBaseString.prototype._loadParameters = function (parameters) {
		if (parameters instanceof Array) {
			this._loadParametersFromArray(parameters);
			return;
		}
		if (typeof parameters === 'object') {
			this._loadParametersFromObject(parameters);
		}
	};

	SignatureBaseString.prototype._loadParametersFromArray = function (parameters) {
		var i;
		for (i = 0; i < parameters.length; i++) {
			this._loadParametersFromObject(parameters[i]);
		}
	};

	SignatureBaseString.prototype._loadParametersFromObject = function (parameters) {
		var key,
			value,
			i;
		for (key in parameters) {
			if (parameters.hasOwnProperty(key)) {
				value = parameters[key];
				if (value instanceof Array) {
					for (i = 0; i < value.length; i++) {
						this._addParameter(key, value[i]);
					}
				} else {
					this._addParameter(key, value);
				}
			}
		}
	};

	SignatureBaseString.prototype._addParameter = function (key, value) {
		if (!this._parameters[key]) {
			this._parameters[key] = [];
		}
		this._parameters[key].push(value);
	}

    SignatureBaseString.prototype._normalizeHttpMethod = function () {
        this._httpMethod = this._httpMethod.toUpperCase();
    };

    SignatureBaseString.prototype._normalizeRequestUrl = function () {
	    // The following is to prevent js-url from loading the window.location
        if (!this._requestUrl) {
            return '';
        }
        var scheme = url('protocol', this._requestUrl).toLowerCase(),
            authority = url('hostname', this._requestUrl).toLocaleLowerCase(),
		    port = url('port', this._requestUrl),
	        path = url('path', this._requestUrl);
	    if (this._requestUrl.toLowerCase().indexOf(scheme) != 0) {
		    scheme = '';
	    }
	    if ((port == 80 && (scheme == 'http' || !scheme))
		    || (port == 443 && scheme == 'https'))
	    {
			    port = '';
	    }
        this._requestUrl =
	        (scheme ? scheme + '://' : '')
		    + authority
            + (port ? ':' + port : '')
            + path;
    };

    SignatureBaseString.prototype._sortParameters = function () {
	    var key;
	    this._sortedKeys = [];
	    for (key in this._parameters) {
		    this._sortedKeys.push(key);
	    }
	    this._sortedKeys.sort();
    };

    SignatureBaseString.prototype._normalizeParameters = function () {

    };

    SignatureBaseString.prototype._concatenateParameters = function() {
	    var i;
	    this._concatenatedParameters = this._sortedKeys.length == 0 ? '&' : '';
	    for (i = 0; i < this._sortedKeys.length; i++) {
		    this._concatenatedParameters += this._getConcatenatedParameter(this._sortedKeys[i]);
	    }
    };

	SignatureBaseString.prototype._getConcatenatedParameter = function(key) {
		var i,
			parameters = this._parameters[key],
			concatenatedParameters = '';
		parameters.sort();
		for (i = 0; i < parameters.length; i++) {
			concatenatedParameters += '&' + key + '=' + parameters[i];
		}
		return concatenatedParameters;
	};

    SignatureBaseString.prototype._concatenateRequestElements = function () {
        // HTTP_METHOD + request url + parameters
        return this._httpMethod + '&' + this._requestUrl + this._concatenatedParameters;
    };

    SignatureBaseString.prototype.generate = function() {
        this._normalizeHttpMethod();
        this._normalizeRequestUrl();
        this._sortParameters();
        this._normalizeParameters();
	    this._concatenateParameters();
        return this._concatenateRequestElements();
    };

    return SignatureBaseString;

})();<commit_msg>Changed the code to use object initalizer for the prototype<commit_after>// Specification: http://oauth.net/core/1.0/#anchor14
var SignatureBaseString = (function () {
    'use strict';

    function SignatureBaseString(httpMethod, requestUrl, parameters) {
        this._httpMethod = httpMethod || '';
        this._requestUrl = requestUrl || '';
	    this._parameters = {}; // Format: { 'key': ['value 1', 'value 2'] };
	    this._loadParameters(parameters);
	    this._sortedKeys = [];
	    this._concatenatedParameters = '';
    }

	SignatureBaseString.prototype = {

        _loadParameters : function (parameters) {
            if (parameters instanceof Array) {
                this._loadParametersFromArray(parameters);
                return;
            }
            if (typeof parameters === 'object') {
                this._loadParametersFromObject(parameters);
            }
        },

        _loadParametersFromArray : function (parameters) {
            var i;
            for (i = 0; i < parameters.length; i++) {
                this._loadParametersFromObject(parameters[i]);
            }
        },

        _loadParametersFromObject : function (parameters) {
            var key,
                value,
                i;
            for (key in parameters) {
                if (parameters.hasOwnProperty(key)) {
                    value = parameters[key];
                    if (value instanceof Array) {
                        for (i = 0; i < value.length; i++) {
                            this._addParameter(key, value[i]);
                        }
                    } else {
                        this._addParameter(key, value);
                    }
                }
            }
        },

        _addParameter : function (key, value) {
            if (!this._parameters[key]) {
                this._parameters[key] = [];
            }
            this._parameters[key].push(value);
        },

        _normalizeHttpMethod : function () {
            this._httpMethod = this._httpMethod.toUpperCase();
        },

        _normalizeRequestUrl : function () {
            // The following is to prevent js-url from loading the window.location
            if (!this._requestUrl) {
                return '';
            }
            var scheme = url('protocol', this._requestUrl).toLowerCase(),
                authority = url('hostname', this._requestUrl).toLocaleLowerCase(),
                port = url('port', this._requestUrl),
                path = url('path', this._requestUrl);
            if (this._requestUrl.toLowerCase().indexOf(scheme) != 0) {
                scheme = '';
            }
            if ((port == 80 && (scheme == 'http' || !scheme))
                || (port == 443 && scheme == 'https'))
            {
                    port = '';
            }
            this._requestUrl =
                (scheme ? scheme + '://' : '')
                + authority
                + (port ? ':' + port : '')
                + path;
        },

        _sortParameters : function () {
            var key;
            this._sortedKeys = [];
            for (key in this._parameters) {
                this._sortedKeys.push(key);
            }
            this._sortedKeys.sort();
        },

        _normalizeParameters : function () {

        },

        _concatenateParameters : function () {
            var i;
            this._concatenatedParameters = this._sortedKeys.length == 0 ? '&' : '';
            for (i = 0; i < this._sortedKeys.length; i++) {
                this._concatenatedParameters += this._getConcatenatedParameter(this._sortedKeys[i]);
            }
        },

        _getConcatenatedParameter : function (key) {
            var i,
                parameters = this._parameters[key],
                concatenatedParameters = '';
            parameters.sort();
            for (i = 0; i < parameters.length; i++) {
                concatenatedParameters += '&' + key + '=' + parameters[i];
            }
            return concatenatedParameters;
        },

        _concatenateRequestElements : function () {
            // HTTP_METHOD + request url + parameters
            return this._httpMethod + '&' + this._requestUrl + this._concatenatedParameters;
        },

        generate : function () {
            this._normalizeHttpMethod();
            this._normalizeRequestUrl();
            this._sortParameters();
            this._normalizeParameters();
            this._concatenateParameters();
            return this._concatenateRequestElements();
        }
    };

    return SignatureBaseString;

})();

var OAuthParameterEncoder = (function () {
    "use strict";

    function OAuthParameterEncoder() {

    }

    OAuthParameterEncoder.property = {

        encode : function (value) {
            return value;
        },

        decode : function (value) {
            return value;
        }

    };

})();<|endoftext|>


<commit_before>// Dynamically set the css background images for all the sprites
import React from "react";
import { createPortal } from "react-dom";
import { connect } from "react-redux";

import { imageSelectors, cursorSelectors } from "../skinSelectors";

const mapRegionNamesToIds = {
  normal: "mainWindowClipPath",
  windowshade: "shadeMainWindowClipPath",
  equalizer: "equalizerWindowClipPath",
  equalizerws: "shadeEqualizerWindowClipPath"
};

const mapRegionNamesToMatcher = {
  normal: "#main-window:not(.shade)",
  windowshade: "#main-window.shade",
  equalizer: "#equalizer-window:not(.shade)",
  equalizerws: "#equalizer-window.shade"
};

const numExIsUsed = skinImages => !!skinImages.DIGIT_0_EX;

class Css extends React.Component {
  componentWillMount() {
    const style = document.createElement("style");
    style.type = "text/css";
    document.head.appendChild(style);
    this.style = style;
  }

  componentWillUnmount() {
    this.style.remove();
    this.style = null;
  }

  render() {
    return createPortal(this.props.children, this.style);
  }
}

class ClipPaths extends React.Component {
  componentWillMount() {
    const paths = document.createElement("div");
    document.body.appendChild(paths);
    this.paths = paths;
  }

  componentWillUnmount() {
    this.paths.remove();
    this.paths = null;
  }

  render() {
    // this.props.children should be an object containing arrays of strings. The
    // keys are ids, and the arrays are arrays of polygon point strings
    const { children } = this.props;

    return createPortal(
      <svg height={0} width={0}>
        <defs>
          {Object.keys(children).map(id => (
            <clipPath id={id} key={id}>
              {children[id].map((points, i) => (
                <polygon points={points} key={i} />
              ))}
            </clipPath>
          ))}
        </defs>
      </svg>,
      this.paths
    );
  }
}

const Skin = props => {
  const { skinImages, skinCursors } = props;
  if (!skinImages) {
    return null;
  }
  const cssRules = [];
  Object.keys(imageSelectors).map(imageName => {
    const imageUrl = skinImages[imageName];
    if (imageUrl) {
      imageSelectors[imageName].forEach(selector => {
        cssRules.push(
          `#winamp2-js ${selector} {background-image: url(${imageUrl})}`
        );
      });
    }
  });
  Object.keys(cursorSelectors).map(cursorName => {
    const cursorUrl = skinCursors[cursorName];
    if (cursorUrl) {
      cursorSelectors[cursorName].forEach(selector => {
        cssRules.push(
          `#winamp2-js ${selector} {cursor: url(${cursorUrl}), auto}`
        );
      });
    }
  });

  if (numExIsUsed(skinImages)) {
    // This alternate number file requires that the minus sign be
    // formatted differently.
    cssRules.push(
      "#winamp2-js .status #time #minus-sign { top: 0px; left: -1px; width: 9px; height: 13px; }"
    );
  }

  const clipPaths = {};
  for (const [regionName, polygons] of Object.entries(props.skinRegion)) {
    if (polygons) {
      const matcher = mapRegionNamesToMatcher[regionName];
      const id = mapRegionNamesToIds[regionName];
      clipPaths[id] = polygons;
      cssRules.push(`#winamp2-js ${matcher} { clip-path: url(#${id}); }`);
    }
  }
  return (

      <Css>{cssRules.join("\n")}</Css>
      <ClipPaths>{clipPaths}</ClipPaths>

  );
};

export default connect(state => ({
  skinImages: state.display.skinImages,
  skinCursors: state.display.skinCursors,
  skinRegion: state.display.skinRegion
}))(Skin);
<commit_msg>Sanity check skin component<commit_after>// Dynamically set the css background images for all the sprites
import React from "react";
import { createPortal } from "react-dom";
import { connect } from "react-redux";

import { imageSelectors, cursorSelectors } from "../skinSelectors";

const mapRegionNamesToIds = {
  normal: "mainWindowClipPath",
  windowshade: "shadeMainWindowClipPath",
  equalizer: "equalizerWindowClipPath",
  equalizerws: "shadeEqualizerWindowClipPath"
};

const mapRegionNamesToMatcher = {
  normal: "#main-window:not(.shade)",
  windowshade: "#main-window.shade",
  equalizer: "#equalizer-window:not(.shade)",
  equalizerws: "#equalizer-window.shade"
};

const numExIsUsed = skinImages => !!skinImages.DIGIT_0_EX;

class Css extends React.Component {
  componentWillMount() {
    const style = document.createElement("style");
    style.type = "text/css";
    document.head.appendChild(style);
    this.style = style;
  }

  componentWillUnmount() {
    this.style.remove();
    this.style = null;
  }

  render() {
    return createPortal(this.props.children, this.style);
  }
}

class ClipPaths extends React.Component {
  componentWillMount() {
    const paths = document.createElement("div");
    document.body.appendChild(paths);
    this.paths = paths;
  }

  componentWillUnmount() {
    this.paths.remove();
    this.paths = null;
  }

  render() {
    // this.props.children should be an object containing arrays of strings. The
    // keys are ids, and the arrays are arrays of polygon point strings
    const { children } = this.props;

    return createPortal(
      <svg height={0} width={0}>
        <defs>
          {Object.keys(children).map(id => (
            <clipPath id={id} key={id}>
              {children[id].map((points, i) => (
                <polygon points={points} key={i} />
              ))}
            </clipPath>
          ))}
        </defs>
      </svg>,
      this.paths
    );
  }
}

const Skin = props => {
  const { skinImages, skinCursors } = props;
  if (!skinImages || !skinCursors) {
    return null;
  }
  const cssRules = [];
  Object.keys(imageSelectors).map(imageName => {
    const imageUrl = skinImages[imageName];
    if (imageUrl) {
      imageSelectors[imageName].forEach(selector => {
        cssRules.push(
          `#winamp2-js ${selector} {background-image: url(${imageUrl})}`
        );
      });
    }
  });
  Object.keys(cursorSelectors).map(cursorName => {
    const cursorUrl = skinCursors[cursorName];
    if (cursorUrl) {
      cursorSelectors[cursorName].forEach(selector => {
        cssRules.push(
          `#winamp2-js ${selector} {cursor: url(${cursorUrl}), auto}`
        );
      });
    }
  });

  if (numExIsUsed(skinImages)) {
    // This alternate number file requires that the minus sign be
    // formatted differently.
    cssRules.push(
      "#winamp2-js .status #time #minus-sign { top: 0px; left: -1px; width: 9px; height: 13px; }"
    );
  }

  const clipPaths = {};
  for (const [regionName, polygons] of Object.entries(props.skinRegion)) {
    if (polygons) {
      const matcher = mapRegionNamesToMatcher[regionName];
      const id = mapRegionNamesToIds[regionName];
      clipPaths[id] = polygons;
      cssRules.push(`#winamp2-js ${matcher} { clip-path: url(#${id}); }`);
    }
  }
  return (

      <Css>{cssRules.join("\n")}</Css>
      <ClipPaths>{clipPaths}</ClipPaths>

  );
};

export default connect(state => ({
  skinImages: state.display.skinImages,
  skinCursors: state.display.skinCursors,
  skinRegion: state.display.skinRegion
}))(Skin);
<|endoftext|>


<commit_before>/*global require: false, module: false */
'use strict';

var _ = require('./underscore_ext');
var Rx = require('rx');
var widgets = require('./columnWidgets');
var colorScales = require('./colorScales');
var util = require('./util');
var Legend = require('./Legend');
var Column = require('./Column');
var MenuItem = require('react-bootstrap/lib/MenuItem');
var React = require('react');
var CanvasDrawing = require('./CanvasDrawing');
var {deepPureRenderMixin, rxEventsMixin} = require('./react-utils');
var drawHeatmap = require('./drawHeatmap');

// Since we don't set module.exports, but instead register ourselves
// with columWidgets, react-hot-loader can't handle the updates automatically.
// Accept hot loading here.
if (module.hot) {
	module.hot.accept();
}

// Since there are multiple components in the file we have to use makeHot
// explicitly.
function hotOrNot(component) {
	return module.makeHot ? module.makeHot(component) : component;
}

var map = _.map,
	filter = _.filter,
	zip = _.zip,
	range = _.range,
	uniq = _.uniq;

var secondExists = x => x[1] != null;

var colorFns = vs => _.map(vs, colorScales.colorScale);

//
// Tooltip
//

var prec = (function () {
	var precision = 6,
		factor = Math.pow(10, precision);
	return val => (val == null) ? 'NA' : Math.round((val * factor)) / factor;
}());

// We're getting events with coords < 0. Not sure if this
// is a side-effect of the react event system. This will
// restrict values to the given range.
function bounded(min, max, x) {
	return x < min ? min : (x > max ? max : x);
}

var nn = (...args) => args.filter(x => x); // not "falsey" (null, undefined, false, etc.)

function tooltip(heatmap, fields, column, codes, zoom, samples, ev) {
	var coord = util.eventOffset(ev),
		sampleIndex = bounded(0, samples.length, Math.floor((coord.y * zoom.count / zoom.height) + zoom.index)),
		sampleID = samples[sampleIndex],
		fieldIndex = bounded(0, fields.length, Math.floor(coord.x * fields.length / column.width)),
		field = fields[fieldIndex],
		fieldCodes = _.getIn(codes, [field]);

	var val = _.getIn(heatmap, [fieldIndex, sampleIndex]),
		code = _.getIn(fieldCodes, [val]),
		label;

	if (fields.length === 1) {
		label = column.fieldLabel.default;
	} else if (fields.length === column.fields.length) {
		label = field;
	} else {
		label = column.fieldLabel.default + ' (' + field + ')';
	}

	val = code ? code : prec(val);

	return {
		sampleID: sampleID,
		rows: nn(
			[['labelValue', label, val]],
			(val !== 'NA' && !code) &&
				[['labelValue', 'Column mean', prec(_.meannan(heatmap[fieldIndex]))]])
	};
}

//
// Legends
//

// XXX missing data handled incorrectly on reload? Is this because NaN is miscoded in json?
function categoryLegend(dataIn, colorScale, codes) {
	if (!colorScale) {
		return {colors: [], labels: [], align: 'left'};
	}
	// only finds categories for the current data in the column
	var data = _.reject(uniq(dataIn), x => x == null).sort((v1, v2) =>  v1 - v2),
		categoryLength = 19, // XXX where does this come from?
		// zip colors and their indexes, then filter out the nulls
		colors = _.map(filter(zip(range(data.length), map(data, colorScale)), secondExists),
				c => c[1]),
		labels = map(data, d => codes[d]);
	return {colors: colors, labels: labels, align: 'left', ellipsis: data.length > categoryLength ? '...' : null};
}

// Color scale cases
// Use the domain of the scale as the label.
// If using thresholded scales, add '<' '>' to labels.

var cases = ([tag], arg, c) => c[tag](arg);

function legendForColorscale(colorSpec) {
	var scale = colorScales.colorScale(colorSpec),
		values = scale.domain(),
		colors = _.map(values, scale);

	var labels = cases(colorSpec, values, {
		'no-data': () => [],
		'float': _.identity,
		'float-pos': _.identity,
		'float-neg': _.identity,
		'float-thresh': ([nl, nh, pl, ph]) => ['<' + nl, nh, pl, '>' + ph],
		'float-thresh-pos': ([low, high]) => [low, '>' + high],
		'float-thresh-neg': ([low, high]) => ['<' + low, high]
	});
	return {colors, labels};
}

// We never want to draw multiple legends. We only draw the 1st scale
// passed in. The caller should provide labels/colors in the 'legend' prop
// if there are multiple scales.
function renderFloatLegend(props) {
	var {colors, legend} = props,
		hasData = _.getIn(colors, [0]);

	var {labels, colors: legendColors} = legend ||
		(hasData ? legendForColorscale(colors[0]) : {colors: [], labels: []});

	return <Legend colors={legendColors} labels={labels} align='center' />;
}

// Might have colorScale but no data (phenotype), no data & no colorScale,
// or data & colorScale, no colorScale &  data?
function renderCodedLegend(props) {
	var {data: [data] = [], fields, codes, colors = []} = props;
	var legendProps;
	var colorfn = _.first(colorFns(colors.slice(0, 1)));

	// We can use domain() for categorical, but we want to filter out
	// values not in the plot. Also, we build the categorical from all
	// values in the db (even those not in the plot) so that colors will
	// match in other datasets.
	if (data && colorfn) { // category
		legendProps = categoryLegend(data, colorfn, codes[fields[0]]);
	} else {
		return <span />;
	}

	return <Legend {...legendProps} />;
}

var HeatmapLegend = hotOrNot(React.createClass({
	mixins: [deepPureRenderMixin],
	render: function() {
		var {codes, fields} = this.props,
			hasCodes = _.get(codes, fields[0]);
		return (hasCodes ? renderCodedLegend : renderFloatLegend)(this.props);
	}
}));

//
// plot rendering
//

function tsvProbeMatrix(heatmap, samples, fields, codes) {
	var fieldNames = ['sample'].concat(fields);
	var coded = _.map(fields, (f, i) => codes && codes[f] ?
			_.map(heatmap[i], _.propertyOf(codes[f])) :
			heatmap[i]);
	var transposed = _.zip.apply(null, coded);
	var tsvData = _.map(samples, (sample, i) => [sample].concat(transposed[i]));

// XXX
//	if (this.ws.column.dataType === 'clinicalMatrix') {
//		fieldNames = ['sample'].concat([this.ws.column.fieldLabel.default]);
//	}
	return [fieldNames, tsvData];
}

function supportsGeneAverage({dataType, fields: {length}}) {
	return ['geneProbesMatrix', 'geneMatrix'].indexOf(dataType) >= 0 && length === 1;
}

function modeMenu({dataType}, cb) {
	return dataType === 'geneMatrix' ?
		<MenuItem eventKey="geneProbesMatrix" onSelect={cb}>Detailed view</MenuItem> :
		<MenuItem eventKey="geneMatrix" onSelect={cb}>Gene average</MenuItem>;
}

var HeatmapColumn = hotOrNot(React.createClass({
	mixins: [rxEventsMixin, deepPureRenderMixin],
	componentWillMount: function () {
		this.events('mouseout', 'mousemove', 'mouseover');

		// Compute tooltip events from mouse events.
		this.ttevents = this.ev.mouseover.filter(ev => util.hasClass(ev.currentTarget, 'Tooltip-target'))
			.selectMany(() => {
				return this.ev.mousemove
					.takeUntil(this.ev.mouseout)
					.map(ev => ({
						data: this.tooltip(ev),
						open: true,
						point: {x: ev.clientX, y: ev.clientY}
					})) // look up current data
					.concat(Rx.Observable.return({open: false}));
			}).subscribe(this.props.tooltip);
	},
	componentWillUnmount: function () {
		this.ttevents.dispose();
	},
	onMode: function (ev, newMode) {
		this.props.callback(['dataType', this.props.id, newMode]);
	},
	tooltip: function (ev) {
		var {samples, data, column, zoom} = this.props,
			codes = _.get(data, 'codes'),
			{heatmap} = column,
			fields = _.getIn(data, ['req', 'probes'], column.fields);
		return tooltip(heatmap, fields, column, codes, zoom, samples, ev);
	},
	// To reduce this set of properties, we could
	//    - Drop data & move codes into the 'display' obj, outside of data
	//    - Drop dataset, and add default colors to the 'display' obj.
	// Might also want to copy fields into 'display', so we can drop the || here.
	render: function () {
		var {samples, data, column, dataset, zoom, hasSurvival} = this.props,
			{heatmap, colors, legend} = column,
			codes = _.getIn(data, ['codes']),
			fields = _.getIn(data, ['req', 'probes'], column.fields),
			download = _.partial(tsvProbeMatrix, heatmap, samples, fields, codes),
			menu = supportsGeneAverage(column) ? modeMenu(column, this.onMode) : null;

		return (
			<Column
				callback={this.props.callback}
				id={this.props.id}
				onViz={this.props.onViz}
				hasSurvival={hasSurvival}
				download={download}
				column={column}
				zoom={zoom}
				menu={menu}
				plot={<CanvasDrawing
						ref='plot'
						draw={drawHeatmap}
						wrapperProps={{
							className: 'Tooltip-target',
							onMouseMove: this.ev.mousemove,
							onMouseOut: this.ev.mouseout,
							onMouseOver: this.ev.mouseover,
							onClick: this.props.onClick
						}}
						codes={_.get(codes, column.fields[0])}
						width={_.getIn(column, ['width'])}
						zoom={zoom}
						colors={colors}
						heatmapData={heatmap}/>}
				legend={<HeatmapLegend
						fields={_.getIn(column, ['fields'])}
						colors={colors}
						legend={legend}
						data={heatmap}
						dataset={dataset}
						codes={codes}/>}
			/>
		);
	}
}));

var getColumn = (props) => <HeatmapColumn {...props} />;

widgets.column.add("probeMatrix", getColumn);
widgets.column.add("geneProbesMatrix", getColumn);
widgets.column.add("geneMatrix", getColumn);
widgets.column.add("clinicalMatrix", getColumn);
<commit_msg>Drop unused prop.<commit_after>/*global require: false, module: false */
'use strict';

var _ = require('./underscore_ext');
var Rx = require('rx');
var widgets = require('./columnWidgets');
var colorScales = require('./colorScales');
var util = require('./util');
var Legend = require('./Legend');
var Column = require('./Column');
var MenuItem = require('react-bootstrap/lib/MenuItem');
var React = require('react');
var CanvasDrawing = require('./CanvasDrawing');
var {deepPureRenderMixin, rxEventsMixin} = require('./react-utils');
var drawHeatmap = require('./drawHeatmap');

// Since we don't set module.exports, but instead register ourselves
// with columWidgets, react-hot-loader can't handle the updates automatically.
// Accept hot loading here.
if (module.hot) {
	module.hot.accept();
}

// Since there are multiple components in the file we have to use makeHot
// explicitly.
function hotOrNot(component) {
	return module.makeHot ? module.makeHot(component) : component;
}

var map = _.map,
	filter = _.filter,
	zip = _.zip,
	range = _.range,
	uniq = _.uniq;

var secondExists = x => x[1] != null;

var colorFns = vs => _.map(vs, colorScales.colorScale);

//
// Tooltip
//

var prec = (function () {
	var precision = 6,
		factor = Math.pow(10, precision);
	return val => (val == null) ? 'NA' : Math.round((val * factor)) / factor;
}());

// We're getting events with coords < 0. Not sure if this
// is a side-effect of the react event system. This will
// restrict values to the given range.
function bounded(min, max, x) {
	return x < min ? min : (x > max ? max : x);
}

var nn = (...args) => args.filter(x => x); // not "falsey" (null, undefined, false, etc.)

function tooltip(heatmap, fields, column, codes, zoom, samples, ev) {
	var coord = util.eventOffset(ev),
		sampleIndex = bounded(0, samples.length, Math.floor((coord.y * zoom.count / zoom.height) + zoom.index)),
		sampleID = samples[sampleIndex],
		fieldIndex = bounded(0, fields.length, Math.floor(coord.x * fields.length / column.width)),
		field = fields[fieldIndex],
		fieldCodes = _.getIn(codes, [field]);

	var val = _.getIn(heatmap, [fieldIndex, sampleIndex]),
		code = _.getIn(fieldCodes, [val]),
		label;

	if (fields.length === 1) {
		label = column.fieldLabel.default;
	} else if (fields.length === column.fields.length) {
		label = field;
	} else {
		label = column.fieldLabel.default + ' (' + field + ')';
	}

	val = code ? code : prec(val);

	return {
		sampleID: sampleID,
		rows: nn(
			[['labelValue', label, val]],
			(val !== 'NA' && !code) &&
				[['labelValue', 'Column mean', prec(_.meannan(heatmap[fieldIndex]))]])
	};
}

//
// Legends
//

// XXX missing data handled incorrectly on reload? Is this because NaN is miscoded in json?
function categoryLegend(dataIn, colorScale, codes) {
	if (!colorScale) {
		return {colors: [], labels: [], align: 'left'};
	}
	// only finds categories for the current data in the column
	var data = _.reject(uniq(dataIn), x => x == null).sort((v1, v2) =>  v1 - v2),
		categoryLength = 19, // XXX where does this come from?
		// zip colors and their indexes, then filter out the nulls
		colors = _.map(filter(zip(range(data.length), map(data, colorScale)), secondExists),
				c => c[1]),
		labels = map(data, d => codes[d]);
	return {colors: colors, labels: labels, align: 'left', ellipsis: data.length > categoryLength ? '...' : null};
}

// Color scale cases
// Use the domain of the scale as the label.
// If using thresholded scales, add '<' '>' to labels.

var cases = ([tag], arg, c) => c[tag](arg);

function legendForColorscale(colorSpec) {
	var scale = colorScales.colorScale(colorSpec),
		values = scale.domain(),
		colors = _.map(values, scale);

	var labels = cases(colorSpec, values, {
		'no-data': () => [],
		'float': _.identity,
		'float-pos': _.identity,
		'float-neg': _.identity,
		'float-thresh': ([nl, nh, pl, ph]) => ['<' + nl, nh, pl, '>' + ph],
		'float-thresh-pos': ([low, high]) => [low, '>' + high],
		'float-thresh-neg': ([low, high]) => ['<' + low, high]
	});
	return {colors, labels};
}

// We never want to draw multiple legends. We only draw the 1st scale
// passed in. The caller should provide labels/colors in the 'legend' prop
// if there are multiple scales.
function renderFloatLegend(props) {
	var {colors, legend} = props,
		hasData = _.getIn(colors, [0]);

	var {labels, colors: legendColors} = legend ||
		(hasData ? legendForColorscale(colors[0]) : {colors: [], labels: []});

	return <Legend colors={legendColors} labels={labels} align='center' />;
}

// Might have colorScale but no data (phenotype), no data & no colorScale,
// or data & colorScale, no colorScale &  data?
function renderCodedLegend(props) {
	var {data: [data] = [], fields, codes, colors = []} = props;
	var legendProps;
	var colorfn = _.first(colorFns(colors.slice(0, 1)));

	// We can use domain() for categorical, but we want to filter out
	// values not in the plot. Also, we build the categorical from all
	// values in the db (even those not in the plot) so that colors will
	// match in other datasets.
	if (data && colorfn) { // category
		legendProps = categoryLegend(data, colorfn, codes[fields[0]]);
	} else {
		return <span />;
	}

	return <Legend {...legendProps} />;
}

var HeatmapLegend = hotOrNot(React.createClass({
	mixins: [deepPureRenderMixin],
	render: function() {
		var {codes, fields} = this.props,
			hasCodes = _.get(codes, fields[0]);
		return (hasCodes ? renderCodedLegend : renderFloatLegend)(this.props);
	}
}));

//
// plot rendering
//

function tsvProbeMatrix(heatmap, samples, fields, codes) {
	var fieldNames = ['sample'].concat(fields);
	var coded = _.map(fields, (f, i) => codes && codes[f] ?
			_.map(heatmap[i], _.propertyOf(codes[f])) :
			heatmap[i]);
	var transposed = _.zip.apply(null, coded);
	var tsvData = _.map(samples, (sample, i) => [sample].concat(transposed[i]));

// XXX
//	if (this.ws.column.dataType === 'clinicalMatrix') {
//		fieldNames = ['sample'].concat([this.ws.column.fieldLabel.default]);
//	}
	return [fieldNames, tsvData];
}

function supportsGeneAverage({dataType, fields: {length}}) {
	return ['geneProbesMatrix', 'geneMatrix'].indexOf(dataType) >= 0 && length === 1;
}

function modeMenu({dataType}, cb) {
	return dataType === 'geneMatrix' ?
		<MenuItem eventKey="geneProbesMatrix" onSelect={cb}>Detailed view</MenuItem> :
		<MenuItem eventKey="geneMatrix" onSelect={cb}>Gene average</MenuItem>;
}

var HeatmapColumn = hotOrNot(React.createClass({
	mixins: [rxEventsMixin, deepPureRenderMixin],
	componentWillMount: function () {
		this.events('mouseout', 'mousemove', 'mouseover');

		// Compute tooltip events from mouse events.
		this.ttevents = this.ev.mouseover.filter(ev => util.hasClass(ev.currentTarget, 'Tooltip-target'))
			.selectMany(() => {
				return this.ev.mousemove
					.takeUntil(this.ev.mouseout)
					.map(ev => ({
						data: this.tooltip(ev),
						open: true,
						point: {x: ev.clientX, y: ev.clientY}
					})) // look up current data
					.concat(Rx.Observable.return({open: false}));
			}).subscribe(this.props.tooltip);
	},
	componentWillUnmount: function () {
		this.ttevents.dispose();
	},
	onMode: function (ev, newMode) {
		this.props.callback(['dataType', this.props.id, newMode]);
	},
	tooltip: function (ev) {
		var {samples, data, column, zoom} = this.props,
			codes = _.get(data, 'codes'),
			{heatmap} = column,
			fields = _.getIn(data, ['req', 'probes'], column.fields);
		return tooltip(heatmap, fields, column, codes, zoom, samples, ev);
	},
	// To reduce this set of properties, we could
	//    - Drop data & move codes into the 'display' obj, outside of data
	//    - Drop dataset, and add default colors to the 'display' obj.
	// Might also want to copy fields into 'display', so we can drop the || here.
	render: function () {
		var {samples, data, column, zoom, hasSurvival} = this.props,
			{heatmap, colors, legend} = column,
			codes = _.getIn(data, ['codes']),
			fields = _.getIn(data, ['req', 'probes'], column.fields),
			download = _.partial(tsvProbeMatrix, heatmap, samples, fields, codes),
			menu = supportsGeneAverage(column) ? modeMenu(column, this.onMode) : null;

		return (
			<Column
				callback={this.props.callback}
				id={this.props.id}
				onViz={this.props.onViz}
				hasSurvival={hasSurvival}
				download={download}
				column={column}
				zoom={zoom}
				menu={menu}
				plot={<CanvasDrawing
						ref='plot'
						draw={drawHeatmap}
						wrapperProps={{
							className: 'Tooltip-target',
							onMouseMove: this.ev.mousemove,
							onMouseOut: this.ev.mouseout,
							onMouseOver: this.ev.mouseover,
							onClick: this.props.onClick
						}}
						codes={_.get(codes, column.fields[0])}
						width={_.getIn(column, ['width'])}
						zoom={zoom}
						colors={colors}
						heatmapData={heatmap}/>}
				legend={<HeatmapLegend
						fields={_.getIn(column, ['fields'])}
						colors={colors}
						legend={legend}
						data={heatmap}
						codes={codes}/>}
			/>
		);
	}
}));

var getColumn = (props) => <HeatmapColumn {...props} />;

widgets.column.add("probeMatrix", getColumn);
widgets.column.add("geneProbesMatrix", getColumn);
widgets.column.add("geneMatrix", getColumn);
widgets.column.add("clinicalMatrix", getColumn);
<|endoftext|>


<commit_before>'use strict';

var fs = require('fs'),
    moment = require('moment'),
    _ = require('lodash'),
    request = require('request').defaults({ encoding: null }),
    User = require('./user');

module.exports = function(config, bot, listener) {

    var logger = config.logger;

    var pokemon = JSON.parse(fs.readFileSync('./locale/pokemon.en.json'));
    var seen = []; // Contains encounter data of already processed pokemen

    logger.level = 'debug';

    // Webhook receives pokemon spawn info.
    // Unfiltered at this point
    listener.on('pokemon', function(payload) {

        // Webhook gave us an expired pokemon. It happens, just ignore
        if (moment().unix() > payload.disappear_time) {
            return;
        }

        // We have seen this pokemon
        if (_.find(seen, { disappear: payload.disappear_time}) !== undefined) {
            return;
        }

        seen.push({
            id: payload.encounter_id,
            disappear: payload.disappear_time
        });

        // Find all users that are active and watching this pokemon
        User.find({ active: true, watchlist: Number(payload.pokemon_id) })
            .then(function(users) {
                logger.info(
                    'Wild %s appeared!\t Disappear time %s\t Users watching %s\t Seen pokemon %s',
                    pokemon[payload.pokemon_id],
                    payload.disappear_time,
                    _.map(users, 'telegramId'),
                    seen.length
                );

                var userIds = users.map(function(user) {
                    return user.telegramId;
                });

                if (userIds.length) {
                    sendPhoto(userIds, payload);
                }
            });

    });

    setInterval(function() {
        seen = _.filter(seen, function(encounter) {
            return encounter.disappear > moment().unix();
        });
        logger.debug('Cleared seen and expired pokemon');
    }, 15 * 60 * 1000);

    function sendPhoto(users, payload) {
        logger.debug('Starting map image download...');
        getMap(payload.latitude, payload.longitude, function(err, res, photo) {

            logger.debug('Map download complete');

            if (err) {
                logger.error('Error when downloading map image:');
                logger.error(err);
                return;
            }

            logger.debug(payload);

            if (res.statusCode !== 200) {
                logger.error('Request failed with code %s', res.statusCode);
                logger.error('Make sure you have Static Maps API enabled on your key.');
                throw new Error('Failed to get map image from Google Maps API.');
            }

            bot.sendPhotoNotification(
                users,
                photo,
                'A wild ' + pokemon[payload.pokemon_id] + ' appeared!\n' +
                'Disappears at ' + disappearTime(payload.disappear_time) + '\n' +
                '(' + timeToDisappear(payload.disappear_time) + ' left)',
                [payload.latitude, payload.longitude]
            );
        });
    }

    function timeToDisappear(timestamp) {
        var diff = moment.unix(timestamp).diff(moment());
        return moment.duration(diff).humanize();
    }

    function disappearTime(timestamp) {
        return moment.unix(timestamp).format('HH:mm:ss');
    }

    function getMap(lat, lon, cb) {
        return request({
            method: 'GET',
               uri: 'https://maps.googleapis.com/maps/api/staticmap',
               qs: {
                   center: lat + ',' + lon,
                   zoom: 16,
                   size: '1080x1080',
                   key: config.gmap_key,
                   markers: lat + ',' + lon
               },
               callback: cb
        });
    };

};
<commit_msg>Ignore duplicates by encounter id<commit_after>'use strict';

var fs = require('fs'),
    moment = require('moment'),
    _ = require('lodash'),
    request = require('request').defaults({ encoding: null }),
    User = require('./user');

module.exports = function(config, bot, listener) {

    var logger = config.logger;

    var pokemon = JSON.parse(fs.readFileSync('./locale/pokemon.en.json'));
    var seen = []; // Contains encounter data of already processed pokemen

    logger.level = 'debug';

    // Webhook receives pokemon spawn info.
    // Unfiltered at this point
    listener.on('pokemon', function(payload) {

        // Webhook gave us an expired pokemon. It happens, just ignore
        if (moment().unix() > payload.disappear_time) {
            return;
        }

        // We have seen this pokemon
        if (_.find(seen, { id: payload.encounter_id}) !== undefined) {
            logger.debug(
                'Ignoring duplicate encounter of %s\t encounter_id %s',
                pokemon[payload.pokemon_id],
                payload.encounter_id
            );
            return;
        }

        seen.push({
            id: payload.encounter_id,
            disappear: payload.disappear_time
        });

        // Find all users that are active and watching this pokemon
        User.find({ active: true, watchlist: Number(payload.pokemon_id) })
            .then(function(users) {
                logger.info(
                    'Wild %s appeared!\t Disappear time %s\t Users watching %s\t Seen pokemon %s',
                    pokemon[payload.pokemon_id],
                    payload.disappear_time,
                    _.map(users, 'telegramId'),
                    seen.length
                );

                var userIds = users.map(function(user) {
                    return user.telegramId;
                });

                if (userIds.length) {
                    sendPhoto(userIds, payload);
                }
            });

    });

    setInterval(function() {
        seen = _.filter(seen, function(encounter) {
            return encounter.disappear > moment().unix();
        });
        logger.debug('Cleared seen and expired pokemon');
    }, 15 * 60 * 1000);

    function sendPhoto(users, payload) {
        logger.debug('Starting map image download...');
        getMap(payload.latitude, payload.longitude, function(err, res, photo) {

            logger.debug('Map download complete');

            if (err) {
                logger.error('Error when downloading map image:');
                logger.error(err);
                return;
            }

            logger.debug(payload);

            if (res.statusCode !== 200) {
                logger.error('Request failed with code %s', res.statusCode);
                logger.error('Make sure you have Static Maps API enabled on your key.');
                throw new Error('Failed to get map image from Google Maps API.');
            }

            bot.sendPhotoNotification(
                users,
                photo,
                'A wild ' + pokemon[payload.pokemon_id] + ' appeared!\n' +
                'Disappears at ' + disappearTime(payload.disappear_time) + '\n' +
                '(' + timeToDisappear(payload.disappear_time) + ' left)',
                [payload.latitude, payload.longitude]
            );
        });
    }

    function timeToDisappear(timestamp) {
        var diff = moment.unix(timestamp).diff(moment());
        return moment.duration(diff).humanize();
    }

    function disappearTime(timestamp) {
        return moment.unix(timestamp).format('HH:mm:ss');
    }

    function getMap(lat, lon, cb) {
        return request({
            method: 'GET',
               uri: 'https://maps.googleapis.com/maps/api/staticmap',
               qs: {
                   center: lat + ',' + lon,
                   zoom: 16,
                   size: '1080x1080',
                   key: config.gmap_key,
                   markers: lat + ',' + lon
               },
               callback: cb
        });
    };

};
<|endoftext|>


<commit_before>/* konnector lib ready to be added to cozy-client-js */
const KONNECTORS_DOCTYPE = 'io.cozy.konnectors'

const KONNECTOR_STATE_READY = 'ready'
const JOB_STATE_READY = 'ready'
const JOB_STATE_ERRORED = 'errored'

export function addAccount (cozy, konnector, account) {
  if (!konnector.accounts) konnector.accounts = []
  konnector.accounts.push(account)
  return Promise.resolve(konnector)
}

export function fetchManifest (cozy, source) {
  return cozy.fetchJSON('GET', `/konnectors/manifests?Source=${encodeURIComponent(source)}`)
}

let cachedSlugIndex
function getSlugIndex (cozy) {
  return cachedSlugIndex
    ? Promise.resolve(cachedSlugIndex)
      : cozy.data.defineIndex(KONNECTORS_DOCTYPE, ['slug'])
          .then(index => {
            cachedSlugIndex = index
            return index
          })
}

export function findBySlug (cozy, slug) {
  if (!slug) throw new Error('Missing `slug` parameter')

  return getSlugIndex(cozy)
    .then(index => cozy.data.query(index, {selector: {slug: slug}}))
    .then(list => list.length ? list[0] : null)
}

export function install (cozy, konnector, timeout = 120000) {
  ['slug', 'source'].forEach(property => {
    if (!konnector[property]) throw new Error(`Missing '${property}' property in konnector`)
  })

  const { slug, source } = konnector

  return findBySlug(cozy, slug)
    .catch(error => {
      if (error.status !== '404') throw error
      return null
    })
    .then(konnector => konnector
      // Need JSONAPI format
      ? cozy.data.find(KONNECTORS_DOCTYPE, konnector._id)
        : cozy.fetchJSON('POST', `/konnectors/${slug}?Source=${encodeURIComponent(source)}`))
    .then(konnector => waitForKonnectorReady(cozy, konnector, timeout))
}

// monitor the status of the connector and resolve when the connector is ready
function waitForKonnectorReady (cozy, konnector, timeout) {
  return new Promise((resolve, reject) => {
    const idTimeout = setTimeout(() => {
      reject(new Error('Konnector installation timed out'))
    }, timeout)

    const idInterval = setInterval(() => {
      cozy.data.find(KONNECTORS_DOCTYPE, konnector._id)
        .then(konnectorResult => {
          if (konnectorResult.state === KONNECTOR_STATE_READY) {
            clearTimeout(idTimeout)
            clearInterval(idInterval)
            resolve(konnector)
          }
        })
        .catch(error => {
          clearTimeout(idTimeout)
          clearInterval(idInterval)
          reject(error)
        })
    }, 1000)
  })
}

function patchFolderPermission (cozy, konnector, folderId = null) {
  const slug = konnector.attributes ? konnector.attributes.slug : konnector.slug
  const saveFolder = folderId ? {type: 'io.cozy.files', values: [folderId]} : ''

  return cozy.fetchJSON('PATCH', `/permissions/konnectors/${encodeURIComponent(slug)}`, {
    data: {
      attributes: {
        permissions: {
          saveFolder: saveFolder
        }
      }
    }
  })
}

export function addFolderPermission (cozy, konnector, folderId) {
  return patchFolderPermission(cozy, konnector, folderId)
}

export function run (cozy, konnector, account, timeout = 120 * 1000) {
  if (!konnector.attributes || !konnector.attributes.slug) {
    throw new Error('Missing `attributes.slug` parameter for konnector')
  }
  if (!account._id) throw new Error('Missing `_id` parameter for account')
  if (!account.folderId) throw new Error('Missing `folderId` parameter for account')

  return cozy.fetchJSON('POST', '/jobs/queue/konnector', {
    data: {
      attributes: {
        options: {
          priority: 10,
          max_exec_count: 1
        },
        arguments: {
          konnector: konnector.slug,
          account: account._id,
          folderToSave: account.folderId
        }
      }
    }
  })
  .then(job => waitForJobFinished(cozy, job, timeout))
}

// monitor the status of the connector and resolve when the connector is ready
function waitForJobFinished (cozy, job, timeout) {
  return new Promise((resolve, reject) => {
    const idTimeout = setTimeout(() => {
      reject(new Error('Job timed out'))
    }, timeout)

    const idInterval = setInterval(() => {
      cozy.fetchJSON('GET', `/jobs/${job._id}`)
        .then(job => {
          if (job.attributes.state === JOB_STATE_ERRORED) {
            clearTimeout(idTimeout)
            clearInterval(idInterval)
            reject(job.attributes.error)
          }

          if (job.attributes.state === JOB_STATE_READY) {
            clearTimeout(idTimeout)
            clearInterval(idInterval)
            resolve(job)
          }
        })
        .catch(error => {
          clearTimeout(idTimeout)
          clearInterval(idInterval)
          reject(error)
        })
    }, 1000)
  })
}
<commit_msg>fix: use snake_case instead of CamelCase for stack data :pencil:<commit_after>/* konnector lib ready to be added to cozy-client-js */
const KONNECTORS_DOCTYPE = 'io.cozy.konnectors'

const KONNECTOR_STATE_READY = 'ready'
const JOB_STATE_READY = 'ready'
const JOB_STATE_ERRORED = 'errored'

export function addAccount (cozy, konnector, account) {
  if (!konnector.accounts) konnector.accounts = []
  konnector.accounts.push(account)
  return Promise.resolve(konnector)
}

export function fetchManifest (cozy, source) {
  return cozy.fetchJSON('GET', `/konnectors/manifests?Source=${encodeURIComponent(source)}`)
}

let cachedSlugIndex
function getSlugIndex (cozy) {
  return cachedSlugIndex
    ? Promise.resolve(cachedSlugIndex)
      : cozy.data.defineIndex(KONNECTORS_DOCTYPE, ['slug'])
          .then(index => {
            cachedSlugIndex = index
            return index
          })
}

export function findBySlug (cozy, slug) {
  if (!slug) throw new Error('Missing `slug` parameter')

  return getSlugIndex(cozy)
    .then(index => cozy.data.query(index, {selector: {slug: slug}}))
    .then(list => list.length ? list[0] : null)
}

export function install (cozy, konnector, timeout = 120000) {
  ['slug', 'source'].forEach(property => {
    if (!konnector[property]) throw new Error(`Missing '${property}' property in konnector`)
  })

  const { slug, source } = konnector

  return findBySlug(cozy, slug)
    .catch(error => {
      if (error.status !== '404') throw error
      return null
    })
    .then(konnector => konnector
      // Need JSONAPI format
      ? cozy.data.find(KONNECTORS_DOCTYPE, konnector._id)
        : cozy.fetchJSON('POST', `/konnectors/${slug}?Source=${encodeURIComponent(source)}`))
    .then(konnector => waitForKonnectorReady(cozy, konnector, timeout))
}

// monitor the status of the connector and resolve when the connector is ready
function waitForKonnectorReady (cozy, konnector, timeout) {
  return new Promise((resolve, reject) => {
    const idTimeout = setTimeout(() => {
      reject(new Error('Konnector installation timed out'))
    }, timeout)

    const idInterval = setInterval(() => {
      cozy.data.find(KONNECTORS_DOCTYPE, konnector._id)
        .then(konnectorResult => {
          if (konnectorResult.state === KONNECTOR_STATE_READY) {
            clearTimeout(idTimeout)
            clearInterval(idInterval)
            resolve(konnector)
          }
        })
        .catch(error => {
          clearTimeout(idTimeout)
          clearInterval(idInterval)
          reject(error)
        })
    }, 1000)
  })
}

function patchFolderPermission (cozy, konnector, folderId = null) {
  const slug = konnector.attributes ? konnector.attributes.slug : konnector.slug
  const saveFolder = folderId ? {type: 'io.cozy.files', values: [folderId]} : ''

  return cozy.fetchJSON('PATCH', `/permissions/konnectors/${encodeURIComponent(slug)}`, {
    data: {
      attributes: {
        permissions: {
          saveFolder: saveFolder
        }
      }
    }
  })
}

export function addFolderPermission (cozy, konnector, folderId) {
  return patchFolderPermission(cozy, konnector, folderId)
}

export function run (cozy, konnector, account, timeout = 120 * 1000) {
  if (!konnector.attributes || !konnector.attributes.slug) {
    throw new Error('Missing `attributes.slug` parameter for konnector')
  }
  if (!account._id) throw new Error('Missing `_id` parameter for account')
  if (!account.folderId) throw new Error('Missing `folderId` parameter for account')

  return cozy.fetchJSON('POST', '/jobs/queue/konnector', {
    data: {
      attributes: {
        options: {
          priority: 10,
          max_exec_count: 1
        },
        arguments: {
          konnector: konnector.slug,
          account: account._id,
          folder_to_save: account.folderId
        }
      }
    }
  })
  .then(job => waitForJobFinished(cozy, job, timeout))
}

// monitor the status of the connector and resolve when the connector is ready
function waitForJobFinished (cozy, job, timeout) {
  return new Promise((resolve, reject) => {
    const idTimeout = setTimeout(() => {
      reject(new Error('Job timed out'))
    }, timeout)

    const idInterval = setInterval(() => {
      cozy.fetchJSON('GET', `/jobs/${job._id}`)
        .then(job => {
          if (job.attributes.state === JOB_STATE_ERRORED) {
            clearTimeout(idTimeout)
            clearInterval(idInterval)
            reject(job.attributes.error)
          }

          if (job.attributes.state === JOB_STATE_READY) {
            clearTimeout(idTimeout)
            clearInterval(idInterval)
            resolve(job)
          }
        })
        .catch(error => {
          clearTimeout(idTimeout)
          clearInterval(idInterval)
          reject(error)
        })
    }, 1000)
  })
}
<|endoftext|>


<commit_before>'use strict';

angular.module('arachne.services')

/**
 * Singleton service for search access.
 * Automatically parses the query parameters in the current location
 * and caches query results.
 *
 * @author: Sebastian Cuy
 */
.factory('searchService', ['$location', 'Entity', '$rootScope', 'Query', '$q',
function($location, Entity, $rootScope, Query, $q) {

    var dirty = false;
    var _currentQuery = Query.fromSearch($location.search());
    var _result = { entities: [] };
    var _currentRequest = false;
    var CHUNK_SIZE = 50;

    $rootScope.$on("$locationChangeSuccess", function() {

        var newQuery = Query.fromSearch($location.search());
        if (!angular.equals(newQuery.toFlatObject(),_currentQuery.toFlatObject())) {
            _result = { entities: [] };
        }
        _currentQuery = newQuery;

    });

    function getCachedChunk(offset) {
        var limit = parseFloat(_currentQuery.limit);
        var entities = _result.entities.slice(offset, offset + limit);
        return entities;
    }

    /** retrieve a chunk from the current search result
     * checks if the requested chunk is cached, otherwise
     * a new query is sent to the backend
     * cancels any previous request
     *
     * @return { Promise<entities> }
     **/
    function retrieveChunk(offset) {

        var deferred = $q.defer();

        if ((!dirty) && (!angular.isUndefined(_result.entities[offset]))) {
            deferred.resolve(getCachedChunk(offset));
            return deferred.promise;
        // chunk needs to be retrieved
        } else {

            dirty = false;
            var query = _currentQuery.setParam('offset', offset);
            if (!query.q) query.q = "*";

            if (_currentRequest) {
                if (_currentRequest.query.toString() == query.toString()) {
                    return _currentRequest.request.$promise;
                } else {
                    _currentRequest.request.$cancelRequest();
                }
            }

            _currentRequest = { query: query, request: Entity.query(query.toFlatObject()) };
            return _currentRequest.request.$promise.then(function(data) {
                _currentRequest = false;
                _result.size = data.size;
                _result.facets = data.facets ? data.facets : [];
                if (data.size == 0) {
                    deferred.resolve([]);
                } else {
                    if(data.entities) for (var i = 0; i < data.entities.length; i++) {
                        _result.entities[parseInt(offset)+i] = data.entities[i];
                    }
                }
                deferred.resolve(data.entities);
                return deferred.promise;
            }, function(response) {
                deferred.reject(response);
                return deferred.promise;
            });
        }

    }

    return {

        // get a single entity from the current result
        getEntity: function(resultIndex) {

            var deferred = $q.defer();

            if (resultIndex < 1) {
                deferred.reject();
                return deferred.promise;
            }

            // resultIndex starts at 1, offset and data[] start at 0
            var offset = Math.floor((resultIndex-1) / CHUNK_SIZE) * CHUNK_SIZE;

            return retrieveChunk(offset).then(function(data) {
                deferred.resolve(data[resultIndex-1 - offset]);
                return deferred.promise;
            });

        },

        // get current facets
        getFacets: function() {
            return _result.facets;
        },

        // return the facet with field .name equal to name or null
        // if none such facet is present in the current result
        getFacet: function(name) {
            var result = null;
            if (_result.facets) {
                for (var i = 0; i < _result.facets.length; i++) {
                    if (_result.facets[i].name ==  name) {
                        result = _result.facets[i];
                        break;
                    }
                }
            }
            return result;
        },

        // retrieve more facet values for the given facet
        // returns a promise that resolves to true if more values
        // were present and resolves to false otherwise
        loadMoreFacetValues: function(facet) {
            var query = angular.extend({limit:0},_currentQuery.toFlatObject());
            query.fo = facet.values.length;
            query.facet = facet.name;
            if (!query.q) query.q = "*";
            var deferred = $q.defer();
            Entity.query(query, function(data) {
                if (data.facets && data.facets.length && data.facets[0].values.length) {
                    facet.values = facet.values.concat(data.facets[0].values);
                    if (data.facets[0].values.length < query.fl) deferred.resolve(false);
                    else deferred.resolve(true);
                } else {
                    deferred.resolve(false);
                }
            }, function(data) {
                deferred.reject(data);
            });
            return deferred.promise;
        },

        // get current results size
        getSize: function() {
            return _result.size;
        },

        // get current page as defined by the query's offset
        getCurrentPage: function() {
            var offset = _currentQuery.offset;
            if (angular.isUndefined(offset)) offset = 0;
            return retrieveChunk(offset);
        },

        // get current query
        currentQuery: function() {
            return _currentQuery;
        },

        /**
         * To make sure query gets executed in any case
         * next time getCurrentPage gets called.
         */
        markDirty: function() {
            dirty = true;
        }
    }
}]);<commit_msg>improves control flow in retrieveChunk<commit_after>'use strict';

angular.module('arachne.services')

/**
 * Singleton service for search access.
 * Automatically parses the query parameters in the current location
 * and caches query results.
 *
 * @author: Sebastian Cuy
 */
.factory('searchService', ['$location', 'Entity', '$rootScope', 'Query', '$q',
function($location, Entity, $rootScope, Query, $q) {

    var dirty = false;
    var _currentQuery = Query.fromSearch($location.search());
    var _result = { entities: [] };
    var _currentRequest = false;
    var CHUNK_SIZE = 50;

    $rootScope.$on("$locationChangeSuccess", function() {

        var newQuery = Query.fromSearch($location.search());
        if (!angular.equals(newQuery.toFlatObject(),_currentQuery.toFlatObject())) {
            _result = { entities: [] };
        }
        _currentQuery = newQuery;

    });

    function getCachedChunk(offset) {
        var limit = parseFloat(_currentQuery.limit);
        var entities = _result.entities.slice(offset, offset + limit);
        return entities;
    }

    /** retrieve a chunk from the current search result
     * checks if the requested chunk is cached, otherwise
     * a new query is sent to the backend
     * cancels any previous request
     *
     * @return { Promise<entities> }
     **/
    function retrieveChunk(offset) {

        var deferred = $q.defer();

        if ((!dirty) && (!angular.isUndefined(_result.entities[offset]))) {
            deferred.resolve(getCachedChunk(offset));
            // return deferred.promise;
        // chunk needs to be retrieved
        } else {

            dirty = false;
            var query = _currentQuery.setParam('offset', offset);
            if (!query.q) query.q = "*";

            if (_currentRequest) {
                if (_currentRequest.query.toString() == query.toString()) {
                    _currentRequest.request.$promise.then(function(data){deferred.resolve(data.entities);});
                } else {
                    _currentRequest.request.$cancelRequest();
                }
            } else {
                _currentRequest = { query: query, request: Entity.query(query.toFlatObject()) };
                _currentRequest.request.$promise.then(function(data) {
                    _currentRequest = false;
                    _result.size = data.size;
                    _result.facets = data.facets ? data.facets : [];
                    if (data.size == 0) {
                        deferred.resolve([]);
                    } else {
                        if(data.entities) for (var i = 0; i < data.entities.length; i++) {
                            _result.entities[parseInt(offset)+i] = data.entities[i];
                        }
                    }
                    deferred.resolve(data.entities);
                }, function(response) {
                    deferred.reject(response);
                });
            }
        }
        return deferred.promise;
    }

    return {

        // get a single entity from the current result
        getEntity: function(resultIndex) {

            var deferred = $q.defer();

            if (resultIndex < 1) {
                deferred.reject();
                return deferred.promise;
            }

            // resultIndex starts at 1, offset and data[] start at 0
            var offset = Math.floor((resultIndex-1) / CHUNK_SIZE) * CHUNK_SIZE;

            return retrieveChunk(offset).then(function(entities) {
                console.log(entities)
                deferred.resolve(entities[resultIndex-1 - offset]);
                return deferred.promise;
            });

        },

        // get current facets
        getFacets: function() {
            return _result.facets;
        },

        // return the facet with field .name equal to name or null
        // if none such facet is present in the current result
        getFacet: function(name) {
            var result = null;
            if (_result.facets) {
                for (var i = 0; i < _result.facets.length; i++) {
                    if (_result.facets[i].name ==  name) {
                        result = _result.facets[i];
                        break;
                    }
                }
            }
            return result;
        },

        // retrieve more facet values for the given facet
        // returns a promise that resolves to true if more values
        // were present and resolves to false otherwise
        loadMoreFacetValues: function(facet) {
            var query = angular.extend({limit:0},_currentQuery.toFlatObject());
            query.fo = facet.values.length;
            query.facet = facet.name;
            if (!query.q) query.q = "*";
            var deferred = $q.defer();
            Entity.query(query, function(data) {
                if (data.facets && data.facets.length && data.facets[0].values.length) {
                    facet.values = facet.values.concat(data.facets[0].values);
                    if (data.facets[0].values.length < query.fl) deferred.resolve(false);
                    else deferred.resolve(true);
                } else {
                    deferred.resolve(false);
                }
            }, function(data) {
                deferred.reject(data);
            });
            return deferred.promise;
        },

        // get current results size
        getSize: function() {
            return _result.size;
        },

        // get current page as defined by the query's offset
        getCurrentPage: function() {
            var offset = _currentQuery.offset;
            if (angular.isUndefined(offset)) offset = 0;
            return retrieveChunk(offset);
        },

        // get current query
        currentQuery: function() {
            return _currentQuery;
        },

        /**
         * To make sure query gets executed in any case
         * next time getCurrentPage gets called.
         */
        markDirty: function() {
            dirty = true;
        }
    }
}]);<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the License);
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 */

CLASS({
  package: 'foam.demos',
  name: 'Flow',
  extendsModel: 'foam.flow.Section',

  constants: { ELEMENT_NAME: 'foam-demos-flow' },

  requires: [
    'foam.flow.ToC',
    'foam.graphics.Circle',
    'foam.flow.CodeSample',
    'foam.flow.Slides'
  ],

  methods: {
    init: function() {
      this.SUPER.apply(this, arguments);
      this.X.registerElement('circle', 'foam.graphics.Circle');
    }
  },

  templates: [
    function CSS() {/*
      .flow-slides-slide {
        width: 500px;
        height: 300px;
      }
    */},
    { name: 'toHTML' }
  ]
});
<commit_msg>Added missing registration code to Flow demo.<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the License);
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 */

CLASS({
  package: 'foam.demos',
  name: 'Flow',
  extendsModel: 'foam.flow.Section',

  constants: { ELEMENT_NAME: 'foam-demos-flow' },

  requires: [
    'foam.flow.ToC',
    'foam.graphics.Circle',
    'foam.flow.CodeSample',
    'foam.flow.Slides'
  ],

  methods: {
    init: function() {
      this.SUPER.apply(this, arguments);
      this.X.registerElement('circle', 'foam.graphics.Circle');

      this.Slides.getProperty('installCSS').documentInstallFn.call(
        this.Slides.getPrototype(), this.X);
      this.Slides.getProperty('registerElement').documentInstallFn.call(
        this.Slides.getPrototype(), this.X);
    }
  },

  templates: [
    function CSS() {/*
      .flow-slides-slide {
        width: 500px;
        height: 300px;
      }
    */},
    { name: 'toHTML' }
  ]
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
CLASS({
  package: 'foam.u2.md',
  name: 'Card',
  extends: 'foam.u2.Element',
  properties: [
    {
      name: 'padding',
      documentation: 'Controls the padding inside the card. If your delegate ' +
          'wants to occupy the whole card, set this to false. (Corresponds ' +
          'to the old md-card-shell class.)',
      defaultValue: true
    },
  ],

  methods: [
    function init() {
      this.SUPER();
      var self = this;
      this.cls('foam-u2-md-Card')
          .cls2(function() {
            return self.padding ? '': 'foam-u2-md-Card-shell';
          }.on$(this.X, this.padding$));
    },
  ],

  templates: [
    function CSS() {/*
      .foam-u2-md-Card {
        background: #fff;
        display: block;
        margin: 10px;
        overflow: hidden;
        padding: 24px 16px;
      }
      .foam-u2-md-Card-shell {
        padding: 0;
      }

      @media not print {
        .foam-u2-md-Card {
          border-radius: 3px;
          box-shadow: 0 1px 3px rgba(0, 0, 0, 0.38);
        }
      }
      @media print {
        .foam-u2-md-Card {
          border: 6px double #000;
          margin: 6pt;
          page-break-inside: avoid;
        }
      }

      @media (max-width: 600px) {
        .foam-u2-md-Card-shell {
          margin: 0;
        }
      }
    */},
  ]
});
<commit_msg>U2 MD Card: Modernize CSS<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
CLASS({
  package: 'foam.u2.md',
  name: 'Card',
  extends: 'foam.u2.Element',
  properties: [
    {
      name: 'padding',
      documentation: 'Controls the padding inside the card. If your delegate ' +
          'wants to occupy the whole card, set this to false. (Corresponds ' +
          'to the old md-card-shell class.)',
      defaultValue: true
    },
  ],

  methods: [
    function initE() {
      var self = this;
      this.cls(this.myCls())
          .cls2(function() {
            return self.padding ? '': self.myCls('shell');
          }.on$(this.X, this.padding$));
    },
  ],

  templates: [
    function CSS() {/*
      $ {
        background: #fff;
        display: block;
        margin: 10px;
        overflow: hidden;
        padding: 24px 16px;
      }
      $-shell {
        padding: 0;
      }

      @media not print {
        $ {
          border-radius: 3px;
          box-shadow: 0 1px 3px rgba(0, 0, 0, 0.38);
        }
      }
      @media print {
        $ {
          border: 6px double #000;
          margin: 6pt;
          page-break-inside: avoid;
        }
      }

      @media (max-width: 600px) {
        $-shell {
          margin: 0;
        }
      }
    */},
  ]
});
<|endoftext|>