<commit_before>var XHR = Posterior.xhr = function(cfg) {
    return XHR.main(cfg);
},
htmlClass = D.documentElement.classList;

XHR.ctor = XMLHttpRequest;
XHR.main = function(cfg) {
    var xhr,
        promise;

    if (cfg.cache) {
        xhr = store(XHR.key(cfg));
        if (xhr) {
            promise = Promise.resolve(xhr);
        }
    }
    if (!promise) {
        xhr = new XHR.ctor();
        XHR.config(xhr, cfg);
        promise = XHR.promise(xhr, cfg);
        if (cfg.cache) {
            promise.then(function() {
                XHR.cache(xhr);
            });
        }
    }
    if (cfg.then) {
        promise = promise.then(cfg.then);
    }
    if (cfg.catch) {
        // call catch with cfg as context and xhr as second arg
        promise = promise.catch(function caught(error) {
            return cfg.catch.call(cfg, error, xhr);
        });
    }
    promise.xhr = xhr;
    return promise;
};

XHR.config = function(xhr, cfg) {
    xhr.open(XHR.method(cfg),
             XHR.url(cfg),
             'async' in cfg ? cfg.async : true,
             cfg.username, cfg.password);
    xhr.cfg = cfg;
    cfg.xhr = xhr;
    for (var prop in cfg) {
        var value = cfg[prop];
        if (prop in xhr) {
            xhr[prop] = value;
        }
        if (typeof value === "function") {
            xhr.addEventListener(prop, value.bind(xhr));
        }
    }
    if (cfg.mimeType) {
        xhr.overrideMimeType(cfg.mimeType);
    }
    if (cfg.requestedWith !== false) {
        xhr.setRequestHeader('X-Requested-With', cfg.requestedWith || 'XMLHttpRequest');
    }
    if (cfg.headers) {
        for (var header in cfg.headers) {
            xhr.setRequestHeader(header, cfg.headers[header]);
        }
    }
    if (cfg.json !== false) {
        try {
            xhr.responseType = 'json';// unsupported by phantomjs (webkit)
        } catch (e) {}
        // don't append to existing headers
        if (!cfg.headers || !cfg.headers['Accept']) {
            xhr.setRequestHeader('Accept', 'application/json');
        }
        if (!cfg.headers || !cfg.headers['Content-Type']) {
            xhr.setRequestHeader('Content-Type', 'application/json');
        }
    }
};

XHR.method = function(cfg) {
    return XHR.methodMap[cfg.method] || cfg.method || 'GET';
};
XHR.methodMap = {
    PATCH: 'POST'
};

XHR.url = function(cfg) {
    var url = cfg.url,
        was;
    while (was !== url) {
        was = url;
        url = url.replace(/([\/\?\&]|^)[^\/\.\?\&]+[\/\?\&]?\.\.([\/\?\&]?)/, '$1');
    }
    return url;
};

XHR.promise = function(xhr, cfg) {
    return new Promise(function(resolve, reject) {
        var fail = function(e) {
            if (cfg.retry) {
                XHR.retry(cfg, cfg.retry, events, fail);
            } else {
                reject(xhr.error = e);
            }
        },
        events = {
            error: fail,
            timeout: fail,
            loadstart: XHR.start,
            loadend: XHR.end,
            load: XHR.load(cfg, resolve, fail)
        };
        XHR.run(xhr, cfg, events, fail);
    });
};

XHR.retry = function(cfg, retry, events, fail) {
    if (typeof retry !== 'object') {
        retry = cfg.retry = {};
    }
    retry.wait = 'wait' in retry ? retry.wait : 1000;
    retry.limit = 'limit' in retry ? retry.limit : 3;
    retry.count = 'count' in retry ? retry.count : 0;
    if (retry.limit > retry.count++) {
        setTimeout(function retryAfterWait() {
            var xhr = new XHR.ctor();
            XHR.config(xhr, cfg);
            XHR.run(xhr, cfg, events, fail);
        }, retry.wait *= 2);
    }
};

XHR.run = function(xhr, cfg, events, fail) {
    for (var event in events) {
        xhr.addEventListener(event, events[event]);
    }
    try {
        xhr.send(XHR.data(cfg));
    } catch (e) {
        fail(e);
    }
};

XHR.load = function(cfg, resolve, reject) {
    return function() {
        try {
            // cfg status code handling (e.g. {0: 200} for file://)
            var xhr = cfg.xhr,
                status = cfg[xhr.status] || xhr.status;
            if (typeof status === "function") {
                status = status(xhr) || xhr.status;
            }
            if (status >= 200 && status < 300) {
                if (cfg.json !== false && typeof xhr.response !== "object") {
                    XHR.forceJSONResponse(xhr);
                }
                var data = xhr.responseType ? xhr.response :
                           cfg.json !== false ? xhr.responseObject :
                           xhr.responseText;
                if (cfg.responseData && XHR.isData(data)) {
                    var ret = cfg.responseData.call(xhr, data);
                    data = ret === undefined ? data : ret;
                }
                resolve(XHR.isData(data) ? data : xhr);
            } else {
                var error;
                if (status >= 400 && cfg.clientError) {
                    error = cfg.clientError(status, xhr);
                } else if (status >= 500 && cfg.serverError) {
                    error = cfg.serverError(status, xhr);
                }
                reject(error || status);
            }
        } catch (e) {
            reject(e);
        }
    };
};

XHR.forceJSONResponse = function(xhr) {
    try {
        delete xhr.response;
        Object.defineProperty(xhr, 'response', { value: xhr.responseObject, enumerable: true, configurable: true });
    } catch (e) {}
};

XHR.isData = function(data) {
    // reject as impossible output by JSON.stringify, invalid input to JSON.parse
    return !(data === undefined || data === '');
};
XHR.data = function(cfg) {
    var data = cfg.data;
    if (cfg.requestData && XHR.isData(data)) {
        var ret = cfg.requestData(data);
        data = ret === undefined ? data : ret;// return new object or keep old
    }
    if (data !== undefined && typeof data !== "string") {
        data = JSON.stringify(data);
    }
    return data || '';
};

XHR.properties = {
    responseObject: {
        get: function() {
            var response = null;
            try {
                if (this.responseText) {
                    response = JSON.parse(this.responseText);
                }
            } catch (e) {}
            if (response === null && typeof this.response === 'object') {
                response = this.response;
            }
            Object.defineProperty(this, 'responseObject', {value:response});
            return response;
        },
        enumerable: true,
        configurable: true
    },
    responseHeaders: {
        get: function() {
            var headers = {},
                all = this.getAllResponseHeaders().trim().split('\n');
            for (var i=0,m=all.length, header; i<m; i++) {
                if ((header = all[i])) {
                    var parts = header.match(/^([\w\-]+):(.*)/);
                    if (parts.length === 3) {
                        headers[parts[1]] = parts[2].trim();
                    }
                }
            }
            Object.defineProperty(this, 'responseHeaders', {value:headers});
            return headers;
        },
        enumerable: true,
        configurable: true
    }
};
Object.defineProperties(XMLHttpRequest.prototype, XHR.properties);

XHR.active = 0;
XHR.activeClass = 'xhr-active';
XHR.start = function() {
    XHR.active++;
    htmlClass.add(XHR.activeClass);
};
XHR.end = function() {
    if (!--XHR.active) {
        htmlClass.remove(XHR.activeClass);
    }
};

XHR.key = function(cfg) {
    return XHR.url(cfg)+'|'+XHR.method(cfg)+'|'+XHR.data(cfg);
};
XHR.cache = function(xhr) {
    var cfg = xhr.cfg;
    store(XHR.key(cfg), XHR.safeCopy(xhr), cfg.cache);
    return xhr;
};
XHR.safeCopy = function(object, copied) {
    var copy = {};
    copied = copied || [];
    if (copied.indexOf(object) < 0) {
        copied.push(object);
        for (var name in object) {
            try {
                var value = object[name],
                    type = typeof value;
                if (type === 'function') {
                    value = undefined;
                } else if (type === 'object') {
                    value = XHR.safeCopy(value, copied);
                }
                if (value !== undefined) {
                    copy[name] = value;
                }
            } catch (e) {}
        }
        return copy;
    }
};
<commit_msg>ensure autoset headers are exposed in cfg<commit_after>var XHR = Posterior.xhr = function(cfg) {
    return XHR.main(cfg);
},
htmlClass = D.documentElement.classList;

XHR.ctor = XMLHttpRequest;
XHR.main = function(cfg) {
    var xhr,
        promise;

    if (cfg.cache) {
        xhr = store(XHR.key(cfg));
        if (xhr) {
            promise = Promise.resolve(xhr);
        }
    }
    if (!promise) {
        xhr = new XHR.ctor();
        XHR.config(xhr, cfg);
        promise = XHR.promise(xhr, cfg);
        if (cfg.cache) {
            promise.then(function() {
                XHR.cache(xhr);
            });
        }
    }
    if (cfg.then) {
        promise = promise.then(cfg.then);
    }
    if (cfg.catch) {
        // call catch with cfg as context and xhr as second arg
        promise = promise.catch(function caught(error) {
            return cfg.catch.call(cfg, error, xhr);
        });
    }
    promise.xhr = xhr;
    return promise;
};

XHR.config = function(xhr, cfg) {
    xhr.open(XHR.method(cfg),
             XHR.url(cfg),
             'async' in cfg ? cfg.async : true,
             cfg.username, cfg.password);
    xhr.cfg = cfg;
    cfg.xhr = xhr;
    for (var prop in cfg) {
        var value = cfg[prop];
        if (prop in xhr) {
            xhr[prop] = value;
        }
        if (typeof value === "function") {
            xhr.addEventListener(prop, value.bind(xhr));
        }
    }
    if (cfg.mimeType) {
        xhr.overrideMimeType(cfg.mimeType);
    }
    // make sure headers we set are also exposed in the config
    var headers = cfg.headers || (cfg.headers = {});
    if (cfg.requestedWith !== false) {
        headers['X-Requested-With'] = cfg.requestedWith || 'XMLHttpRequest';
    }
    if (cfg.json !== false) {
        try {
            xhr.responseType = 'json';// unsupported by phantomjs (webkit)
        } catch (e) {}
        // don't squash existing headers
        if (!headers.Accept) {
            headers.Accept = 'application/json';
        }
        if (!headers['Content-Type']) {
            headers['Content-Type'] = 'application/json';
        }
    }
    for (var header in headers) {
        xhr.setRequestHeader(header, headers[header]);
    }
};

XHR.method = function(cfg) {
    return XHR.methodMap[cfg.method] || cfg.method || 'GET';
};
XHR.methodMap = {
    PATCH: 'POST'
};

XHR.url = function(cfg) {
    var url = cfg.url,
        was;
    while (was !== url) {
        was = url;
        url = url.replace(/([\/\?\&]|^)[^\/\.\?\&]+[\/\?\&]?\.\.([\/\?\&]?)/, '$1');
    }
    return url;
};

XHR.promise = function(xhr, cfg) {
    return new Promise(function(resolve, reject) {
        var fail = function(e) {
            if (cfg.retry) {
                XHR.retry(cfg, cfg.retry, events, fail);
            } else {
                reject(xhr.error = e);
            }
        },
        events = {
            error: fail,
            timeout: fail,
            loadstart: XHR.start,
            loadend: XHR.end,
            load: XHR.load(cfg, resolve, fail)
        };
        XHR.run(xhr, cfg, events, fail);
    });
};

XHR.retry = function(cfg, retry, events, fail) {
    if (typeof retry !== 'object') {
        retry = cfg.retry = {};
    }
    retry.wait = 'wait' in retry ? retry.wait : 1000;
    retry.limit = 'limit' in retry ? retry.limit : 3;
    retry.count = 'count' in retry ? retry.count : 0;
    if (retry.limit > retry.count++) {
        setTimeout(function retryAfterWait() {
            var xhr = new XHR.ctor();
            XHR.config(xhr, cfg);
            XHR.run(xhr, cfg, events, fail);
        }, retry.wait *= 2);
    }
};

XHR.run = function(xhr, cfg, events, fail) {
    for (var event in events) {
        xhr.addEventListener(event, events[event]);
    }
    try {
        xhr.send(XHR.data(cfg));
    } catch (e) {
        fail(e);
    }
};

XHR.load = function(cfg, resolve, reject) {
    return function() {
        try {
            // cfg status code handling (e.g. {0: 200} for file://)
            var xhr = cfg.xhr,
                status = cfg[xhr.status] || xhr.status;
            if (typeof status === "function") {
                status = status(xhr) || xhr.status;
            }
            if (status >= 200 && status < 300) {
                if (cfg.json !== false && typeof xhr.response !== "object") {
                    XHR.forceJSONResponse(xhr);
                }
                var data = xhr.responseType ? xhr.response :
                           cfg.json !== false ? xhr.responseObject :
                           xhr.responseText;
                if (cfg.responseData && XHR.isData(data)) {
                    var ret = cfg.responseData.call(xhr, data);
                    data = ret === undefined ? data : ret;
                }
                resolve(XHR.isData(data) ? data : xhr);
            } else {
                var error;
                if (status >= 400 && cfg.clientError) {
                    error = cfg.clientError(status, xhr);
                } else if (status >= 500 && cfg.serverError) {
                    error = cfg.serverError(status, xhr);
                }
                reject(error || status);
            }
        } catch (e) {
            reject(e);
        }
    };
};

XHR.forceJSONResponse = function(xhr) {
    try {
        delete xhr.response;
        Object.defineProperty(xhr, 'response', { value: xhr.responseObject, enumerable: true, configurable: true });
    } catch (e) {}
};

XHR.isData = function(data) {
    // reject as impossible output by JSON.stringify, invalid input to JSON.parse
    return !(data === undefined || data === '');
};
XHR.data = function(cfg) {
    var data = cfg.data;
    if (cfg.requestData && XHR.isData(data)) {
        var ret = cfg.requestData(data);
        data = ret === undefined ? data : ret;// return new object or keep old
    }
    if (data !== undefined && typeof data !== "string") {
        data = JSON.stringify(data);
    }
    return data || '';
};

XHR.properties = {
    responseObject: {
        get: function() {
            var response = null;
            try {
                if (this.responseText) {
                    response = JSON.parse(this.responseText);
                }
            } catch (e) {}
            if (response === null && typeof this.response === 'object') {
                response = this.response;
            }
            Object.defineProperty(this, 'responseObject', {value:response});
            return response;
        },
        enumerable: true,
        configurable: true
    },
    responseHeaders: {
        get: function() {
            var headers = {},
                all = this.getAllResponseHeaders().trim().split('\n');
            for (var i=0,m=all.length, header; i<m; i++) {
                if ((header = all[i])) {
                    var parts = header.match(/^([\w\-]+):(.*)/);
                    if (parts.length === 3) {
                        headers[parts[1]] = parts[2].trim();
                    }
                }
            }
            Object.defineProperty(this, 'responseHeaders', {value:headers});
            return headers;
        },
        enumerable: true,
        configurable: true
    }
};
Object.defineProperties(XMLHttpRequest.prototype, XHR.properties);

XHR.active = 0;
XHR.activeClass = 'xhr-active';
XHR.start = function() {
    XHR.active++;
    htmlClass.add(XHR.activeClass);
};
XHR.end = function() {
    if (!--XHR.active) {
        htmlClass.remove(XHR.activeClass);
    }
};

XHR.key = function(cfg) {
    return XHR.url(cfg)+'|'+XHR.method(cfg)+'|'+XHR.data(cfg);
};
XHR.cache = function(xhr) {
    var cfg = xhr.cfg;
    store(XHR.key(cfg), XHR.safeCopy(xhr), cfg.cache);
    return xhr;
};
XHR.safeCopy = function(object, copied) {
    var copy = {};
    copied = copied || [];
    if (copied.indexOf(object) < 0) {
        copied.push(object);
        for (var name in object) {
            try {
                var value = object[name],
                    type = typeof value;
                if (type === 'function') {
                    value = undefined;
                } else if (type === 'object') {
                    value = XHR.safeCopy(value, copied);
                }
                if (value !== undefined) {
                    copy[name] = value;
                }
            } catch (e) {}
        }
        return copy;
    }
};
<|endoftext|>


<commit_before>
var animationSpeed = 1000;
var currentHover = null;
var zoomTimeout = null;
var url = 'http://localhost:9998/';
var minHeight = 300;
var screenWidth = $(window).width();
var screenHeight = $(window).height();
var screenResizeTimeout, treeRedrawTimeout;
var zoomWidth = 100;
var minimapNodes, cachedZoomNodes;
var currentMousePos = { x: -1, y: -1 };
var zoomLeft = 0;
var zoomRight = 0;

/**
 * When the screen resizes, or one of the panels resizes, the others need to be resized as well
 * This function will update all other panels when one resizes, or update all panels when the screen resizes.
 */
function screenResize() {
    //If the width has changed since last time, update the width of the left sub panel accordingly
    if ($(window).width() != screenWidth) {
        $("#phylogenyContainer").width(Math.ceil($("#phylogenyContainer").width() * $(window).width() / screenWidth));
        screenWidth = $(window).width();
    }

    //If the height has changed since last time, update the height of the zoom panel accordingly
    if ($(window).height() != screenHeight) {
        $("#zoom").height(Math.ceil($("#zoom").height() * $(window).height() / screenHeight));
        screenHeight = $(window).height();
    }

    if ($("#zoom").height() + $("#header").height() > (screenHeight - 25)) {
        $("#zoom").height(screenHeight - 25 - $("#header").height());
    }

    //Update the height of the sub panel after change of the zoom panel.
    //The sub panel becomes the height of the screen, minus the height of the zoom and header bar.
    var borderHeight = parseInt($("#zoom").css("border-bottom-width").replace('px', ''));
    $('#sub').height($(window).height() - $("#zoom").height() - $("#header").height() - borderHeight);
    $('#sub').height($(window).height() - $("#zoom").height() - $("#header").height() - borderHeight);
    $('#minimap').height($('#sub').height());
    if ($('#zoom').find('canvas').length) { //Update the canvas height and width in the zoom panel
        $('#zoom').find('canvas')[0].height = $('#zoom').height();
        $('#zoom').find('canvas')[0].width = $('#zoom').width();
        updatezoomWindow();
    }

    //Update the width of the sub panel and zoom panel and the canvasses in it.
    //If the width of a subpanel has changed or the screen, update both the upper canvas and the panels in the sub.
    //Both canvasses need to be updated as well.
    var borderWidth = parseInt($("#phylogenyContainer").css("border-right-width").replace('px', ''));
    $('#minimapContainer').width($("#sub").width() - $("#phylogenyContainer").width() - borderWidth);
    if ($('#minimapContainer').find('canvas').length) {
        $('#minimap').find('canvas')[0].width = $('#minimap').width();
        $('#minimap').find('canvas')[0].height = $('#minimap').height();
        drawMinimap(null); //Update the canvas
        zoom(1, 0, 1);
        if (treeRedrawTimeout) {
            clearTimeout(treeRedrawTimeout);
        }
        treeRedrawTimeout = setTimeout(function() {
            //drawTree(null); //Update the phylogenyTree
        }, 1500);
    }
}

$('document').ready(function() {

    screenResize();

    //Add a resizable to the south side of the zoom panel
    $("#zoom").resizable({
        handles: 's'
    });
    //Add a resizable to the east side of the phylogeny panel
    $("#phylogenyContainer").resizable({
        handles: 'e'
    });

    //Trigger screenResize when a resizable panel is resized
    $('#zoom, #phylogenyContainer').resize(function() {
        if (screenResizeTimeout) {
            clearTimeout(screenResizeTimeout);
        }
        screenResizeTimeout = setTimeout(function() {
            screenResize();
        }, 500);
    });

    //Trigger screenResize after a real screen resize
    $(window).resize(function() {
        if (screenResizeTimeout) {
            clearTimeout(screenResizeTimeout);
        }
        screenResizeTimeout = setTimeout(function() {
            screenResize();
        }, 500);
    });

    $('#zoomoutbtn').on('click', function() {
        zoom(-1, 10, 1);
    });
    $('#zoominbtn').on('click', function() {
        zoom(1, 10, 1);
    });

    //Bind the mouseScroll to trigger zooming on the miniMap
    $('body').bind('mousewheel DOMMouseScroll', function(e) {
        if ($(currentHover).is('#minimap')) {
            e.preventDefault();
            zoom(e.originalEvent.wheelDelta, 1, Math.floor(currentMousePos.x - $(currentHover).position().left));
        } else if($(currentHover).is('#zoomWindow')) {
            var ratio = $('#minimap').width() / $('#zoom').width();
            e.preventDefault();
            zoom(e.originalEvent.wheelDelta, 1, Math.floor((currentMousePos.x - $(currentHover).position().left) * ratio));
            updateZoomValues();
            drawZoom(null);
        }
    });

    //Add the slider
    var slider = $('#minimap').find('.slider');
    $(slider)
        .draggable({
            containment: "parent",
            stop: function() {
                //Temp, to make it look smooth, can be done if lazy loading is implemented
                //updatezoomWindow();

                //Should be this when we have specific zoomData
                clearTimeout(zoomTimeout);
                zoomTimeout = setTimeout(function() { updatezoomWindow() }, 50);
            }
        });

    //This makes the system know if the mouse is currently over a genome.
    $('.genome').hover(function() {
        currentHover = this;
    }, function() {
        currentHover = null;
    });

    //Constantly check where the mouse is, to let the zooming work better.
    $('#minimapContainer, #zoom').mousemove(function(event) {
        currentMousePos.x = event.pageX;
        currentMousePos.y = event.pageY;
    });

    initialize();
});

/**
 * Change css pixel distance to an integer: 25px to 25
 * @param css The css value
 * @returns {Number} The css value changed to an integer
 */
function pxToInt(css) {
    return parseFloat(css.replace('px', ''));
}

/**
 * Draw the zoom data
 * @param nodes
 */
var drawZoom = function(nodes) {
    if (nodes == null) {
        nodes = cachedZoomNodes;
    } else {
        cachedZoomNodes = nodes;
    }
    var ratio = $('#zoomWindow').width() / (zoomRight - zoomLeft);
    if (Object.keys(nodes).length > 0) {
        draw(nodes, $('#zoomWindow canvas')[0], function(x) {
            return (x) * ratio;
        });
    } else {
        var c = $('#zoomWindow canvas')[0];
        var ctx = c.getContext("2d");
        ctx.clearRect(0, 0, c.width, c.height);
    }
};

/**
 * Draw the minimap
 * @param nodes
 */
var drawMinimap = function(nodes) {
    if (nodes == null) {
        nodes = minimapNodes;
    } else {
        minimapNodes = nodes;
    }
    var ratio = $('#minimap').width() / nodes[Object.keys(nodes)[Object.keys(nodes).length - 1]].x;

    draw(nodes, $('#minimap canvas')[0], function(x) {
        return x * ratio;
    });
};

/**
 * The global draw method that draws either the minimap or the zoomWindow. Based on a given translate method, the
 * coordinates are translated to the right position.
 * @param points
 * @param c
 * @param translate
 */
function draw(points, c, translate) {
    if (typeof c == "undefined") {
        return;
    }
    var ctx = c.getContext("2d");
    ctx.clearRect(0, 0, c.width, c.height);

    var nodeHeight = c.height / 2;

    var yTranslate = (c.height < 200)?c.height / 2 / 110 : 1;

    $.each(points, function(id, point) {
        ctx.beginPath();
        ctx.arc(translate(point.x), nodeHeight + point.y * yTranslate, 5, 0, 2 * Math.PI);
        ctx.stroke();

        $.each(point.edges, function(key, edge) {
            var target = points[edge.startId];
            if (edge.startId == id) {
                target = points[edge.endId];
            }
            if (target) {
                ctx.beginPath();
                ctx.moveTo(translate(point.x), nodeHeight + point.y * yTranslate);
                ctx.lineTo(translate(target.x), nodeHeight + target.y * yTranslate);
                ctx.lineWidth = edge.weight;
                ctx.strokeStyle = '#'+ edge.color;
                ctx.stroke();
                ctx.lineWidth = 1;
                ctx.strokeStyle = '#000000';
            }
        });
    });
}

/**
 * The method that does the zooming, based on your mouse position, the zooming is applied.
 * @param direction
 * @param zoomAmount
 * @param xMousePos
 */
function zoom(direction, zoomAmount, xMousePos) {
    var slider = $('.slider', '#minimap');
    var minimap = $('#minimap');
    var zoomWindow = $('#zoomWindow');
    var maxWidth = minimap.width();
    var currentWidth = slider.width();
    zoomWidth += (direction > 0)?-1*zoomAmount:zoomAmount;
    zoomWidth = Math.max(1, Math.min(zoomWidth, 100));

    var newWidth = Math.max(1, Math.min(maxWidth, zoomWidth / 100 * maxWidth));

    var left = parseInt(slider.css('left').replace('px', ''));
    var difference = Math.abs(currentWidth - newWidth);

    //If zooming in the zoomField should zoom towards the cursor
    if (direction > 0) {
        var mouseX = xMousePos;
        var leftSide = mouseX - left;
        var rightSide = left + currentWidth - mouseX;
        var dif = Math.min(difference, leftSide - rightSide);
        if (dif > 0) {
            left += dif + (difference - dif) / 2;
        } else {
            left += Math.max(0, difference + dif) / 2;
        }
    } else {
        left -= difference / 2;
    }

    left += Math.min(0, maxWidth - (left + newWidth)); //Fix slider going out of right screen.
    left = Math.max(0, Math.min(maxWidth, left));
    slider.css('left', left + 'px');
    slider.width(parseInt(newWidth) +'px');
    clearTimeout(zoomTimeout);
    //If not zooming anymore, update the ZoomWindow with new data.
    if (zoomAmount != 0) {
        zoomTimeout = setTimeout(function () {
            updatezoomWindow();
        }, 500);
    }
}

/**
 * Update the window that shows the zoomed genome
 */
function updatezoomWindow()
{
    if (minimapNodes) {
        var zoom = updateZoomValues();
        var boundingBox = {xleft: zoomLeft, xright: zoomRight, zoom: zoom};
        getNodes(boundingBox, drawZoom);
    }
}

function updateZoomValues()
{
        var slider = $('#minimap .slider');
        var totalWidth = $('#minimap').width();
        var sliderLeft = pxToInt(slider.css('left'));
        var xWidth = minimapNodes[Object.keys(minimapNodes)[Object.keys(minimapNodes).length - 1]].x;
        zoomLeft = Math.max(0, Math.floor(sliderLeft / totalWidth * xWidth));
        zoomRight =  Math.floor((sliderLeft + slider.width()) / totalWidth * xWidth);
        return Math.round(totalWidth / slider.width());
}

// This function translates from one representation of a bounding box in gui coordinates to
// coordinates expected by the REST api. It takes x, y, width and height arguments and returns
// an object with left right top and bottom properties.
function computeBoundingBox(left, width, zoom)
{
    return {
        'xleft': left,
        'xright': (left + width),
        zoom: zoom
    }
}

/**
 * Parse the received node data from the server to something that we can use better.
 * @param nodes
 * @returns {{}}
 */
function parseNodeData(nodes) {
    var result = {};

    if (typeof nodes == 'undefined' || nodes.length == 0) {
        return result;
    }
    var left = nodes[0].x;

    $.each(nodes, function(key, value) {
        result[value.id] = {
            x: value.x - zoomLeft,
            y: value.y,
            strands: value.strands,
            edges: value.edges,
            genomes: value.genomes
        }
    });
    return result;
}

/**
 * Fetch the nodes from the server based on a boundingBox.
 * @param boundingBox
 * @param callback
 */
function getNodes(boundingBox, callback) {
    $.ajax({
        url: url + 'api/getnodes',
        dataType: 'JSON',
        type: 'GET',
        data: boundingBox
    }).done(function(data) {
        callback(parseNodeData(data.cList));
    });
}

/**
 * Initialize both canvasses.
 */
function initialize() {
    initializeMinimap();
    initializeZoomWindow();
}

/**
 * Initialize the minimap canvas
 */
function initializeMinimap() {
    var minimap = $('#minimap');
    minimap.height($('#minimapContainer').height());

    var slider = minimap.find('.slider');

    minimap.find('.canvasContainer').html(
        $('<canvas/>', {'class':'genomeCanvas', Width: minimap.width(), Height: minimap.height() })
    );
    var boundingBox = computeBoundingBox(0, 10000000, 1);
    getNodes(boundingBox, drawMinimap);
    zoom(-1, 1, 1);
}

/**
 * Initialize the zooming window canvas.
 */
function initializeZoomWindow() {
    var zoomWindow = $('#zoomWindow');
    zoomWindow.prepend(
        $('<canvas/>', {'class':'zoomedCanvas', Width: zoomWindow.width(), Height: zoomWindow.height() })
    );
}

/**
 * Make the phylogney Panel small, and the minimap Panel big
 */
function fullSizeMinimap() {
    $("#zoom").animate({
        height: screenHeight - 250 - $('#header').height()
    }, 500);

    $('#phylogenyContainer').animate({
        'width': 30
    }, 1000, function () {
        zoom(1, 0, 1);
        screenResize();
    });
}<commit_msg>Added better height display<commit_after>
var animationSpeed = 1000;
var currentHover = null;
var zoomTimeout = null;
var url = 'http://localhost:9998/';
var minHeight = 300;
var screenWidth = $(window).width();
var screenHeight = $(window).height();
var screenResizeTimeout, treeRedrawTimeout;
var zoomWidth = 100;
var minimapNodes, cachedZoomNodes;
var currentMousePos = { x: -1, y: -1 };
var zoomLeft = 0;
var zoomRight = 0;
var zoomHeight = 0;
var minimapHeight = 0;

/**
 * When the screen resizes, or one of the panels resizes, the others need to be resized as well
 * This function will update all other panels when one resizes, or update all panels when the screen resizes.
 */
function screenResize() {
    //If the width has changed since last time, update the width of the left sub panel accordingly
    if ($(window).width() != screenWidth) {
        $("#phylogenyContainer").width(Math.ceil($("#phylogenyContainer").width() * $(window).width() / screenWidth));
        screenWidth = $(window).width();
    }

    //If the height has changed since last time, update the height of the zoom panel accordingly
    if ($(window).height() != screenHeight) {
        $("#zoom").height(Math.ceil($("#zoom").height() * $(window).height() / screenHeight));
        screenHeight = $(window).height();
    }

    if ($("#zoom").height() + $("#header").height() > (screenHeight - 25)) {
        $("#zoom").height(screenHeight - 25 - $("#header").height());
    }

    //Update the height of the sub panel after change of the zoom panel.
    //The sub panel becomes the height of the screen, minus the height of the zoom and header bar.
    var borderHeight = parseInt($("#zoom").css("border-bottom-width").replace('px', ''));
    $('#sub').height($(window).height() - $("#zoom").height() - $("#header").height() - borderHeight);
    $('#sub').height($(window).height() - $("#zoom").height() - $("#header").height() - borderHeight);
    $('#minimap').height($('#sub').height());
    if ($('#zoom').find('canvas').length) { //Update the canvas height and width in the zoom panel
        $('#zoom').find('canvas')[0].height = $('#zoom').height();
        $('#zoom').find('canvas')[0].width = $('#zoom').width();
        updatezoomWindow();
    }

    //Update the width of the sub panel and zoom panel and the canvasses in it.
    //If the width of a subpanel has changed or the screen, update both the upper canvas and the panels in the sub.
    //Both canvasses need to be updated as well.
    var borderWidth = parseInt($("#phylogenyContainer").css("border-right-width").replace('px', ''));
    $('#minimapContainer').width($("#sub").width() - $("#phylogenyContainer").width() - borderWidth);
    if ($('#minimapContainer').find('canvas').length) {
        $('#minimap').find('canvas')[0].width = $('#minimap').width();
        $('#minimap').find('canvas')[0].height = $('#minimap').height();
        drawMinimap(null); //Update the canvas
        zoom(1, 0, 1);
        if (treeRedrawTimeout) {
            clearTimeout(treeRedrawTimeout);
        }
        treeRedrawTimeout = setTimeout(function() {
            //drawTree(null); //Update the phylogenyTree
        }, 1500);
    }
}

$('document').ready(function() {

    screenResize();

    //Add a resizable to the south side of the zoom panel
    $("#zoom").resizable({
        handles: 's'
    });
    //Add a resizable to the east side of the phylogeny panel
    $("#phylogenyContainer").resizable({
        handles: 'e'
    });

    //Trigger screenResize when a resizable panel is resized
    $('#zoom, #phylogenyContainer').resize(function() {
        if (screenResizeTimeout) {
            clearTimeout(screenResizeTimeout);
        }
        screenResizeTimeout = setTimeout(function() {
            screenResize();
        }, 500);
    });

    //Trigger screenResize after a real screen resize
    $(window).resize(function() {
        if (screenResizeTimeout) {
            clearTimeout(screenResizeTimeout);
        }
        screenResizeTimeout = setTimeout(function() {
            screenResize();
        }, 500);
    });

    $('#zoomoutbtn').on('click', function() {
        zoom(-1, 10, 1);
    });
    $('#zoominbtn').on('click', function() {
        zoom(1, 10, 1);
    });

    //Bind the mouseScroll to trigger zooming on the miniMap
    $('body').bind('mousewheel DOMMouseScroll', function(e) {
        if ($(currentHover).is('#minimap')) {
            e.preventDefault();
            zoom(e.originalEvent.wheelDelta, 1, Math.floor(currentMousePos.x - $(currentHover).position().left));
        } else if($(currentHover).is('#zoomWindow')) {
            var ratio = $('#minimap').width() / $('#zoom').width();
            e.preventDefault();
            zoom(e.originalEvent.wheelDelta, 1, Math.floor((currentMousePos.x - $(currentHover).position().left) * ratio));
            updateZoomValues();
            drawZoom(null);
        }
    });

    //Add the slider
    var slider = $('#minimap').find('.slider');
    $(slider)
        .draggable({
            containment: "parent",
            stop: function() {
                //Temp, to make it look smooth, can be done if lazy loading is implemented
                //updatezoomWindow();

                //Should be this when we have specific zoomData
                clearTimeout(zoomTimeout);
                zoomTimeout = setTimeout(function() { updatezoomWindow() }, 50);
            }
        });

    //This makes the system know if the mouse is currently over a genome.
    $('.genome').hover(function() {
        currentHover = this;
    }, function() {
        currentHover = null;
    });

    //Constantly check where the mouse is, to let the zooming work better.
    $('#minimapContainer, #zoom').mousemove(function(event) {
        currentMousePos.x = event.pageX;
        currentMousePos.y = event.pageY;
    });

    initialize();
});

/**
 * Change css pixel distance to an integer: 25px to 25
 * @param css The css value
 * @returns {Number} The css value changed to an integer
 */
function pxToInt(css) {
    return parseFloat(css.replace('px', ''));
}

/**
 * Draw the zoom data
 * @param nodes
 */
var drawZoom = function(nodes) {
    if (nodes == null) {
        nodes = cachedZoomNodes;
    } else {
        cachedZoomNodes = nodes;
        zoomHeight = calcHeight(nodes);
    }
    var ratio = $('#zoomWindow').width() / (zoomRight - zoomLeft);
    if (Object.keys(nodes).length > 0) {
        var canvas = $('#zoomWindow canvas')[0];
        draw(nodes, canvas, canvas.height / zoomHeight, function(x) {
            return (x) * ratio;
        });
    } else {
        var c = $('#zoomWindow canvas')[0];
        var ctx = c.getContext("2d");
        ctx.clearRect(0, 0, c.width, c.height);
    }
};

/**
 * Calculate the difference in y amounts in the nodeSet
 * @param nodes The nodes
 * @returns {number} The max difference in y
 */
function calcHeight(nodes) {
    var minHeight = nodes[Object.keys(nodes)[0]].y;
    var maxHeight = minHeight;
    $.each(nodes, function(key, node) {
        minHeight = Math.min(minHeight, node.y);
        maxHeight = Math.max(maxHeight, node.y);
    });
    return maxHeight - minHeight;
}

/**
 * Draw the minimap
 * @param nodes
 */
var drawMinimap = function(nodes) {
    if (nodes == null) {
        nodes = minimapNodes;
    } else {
        minimapNodes = nodes;
        minimapHeight = calcHeight(nodes);
    }
    var ratio = $('#minimap').width() / nodes[Object.keys(nodes)[Object.keys(nodes).length - 1]].x;

    var canvas = $('#minimap canvas')[0];
    draw(nodes, canvas, canvas.height / minimapHeight, function(x) {
        return x * ratio;
    });
};

/**
 * The global draw method that draws either the minimap or the zoomWindow. Based on a given translate method, the
 * coordinates are translated to the right position.
 * @param points
 * @param c
 * @param translate
 */
function draw(points, c, yTranslate, xTranslate) {
    if (typeof c == "undefined") {
        return;
    }
    var ctx = c.getContext("2d");
    ctx.clearRect(0, 0, c.width, c.height);

    var nodeHeight = c.height / 2;

    $.each(points, function(id, point) {
        ctx.beginPath();
        ctx.arc(xTranslate(point.x), nodeHeight + point.y * yTranslate * 0.3, 5, 0, 2 * Math.PI);
        ctx.stroke();

        $.each(point.edges, function(key, edge) {
            var target = points[edge.startId];
            if (edge.startId == id) {
                target = points[edge.endId];
            }
            if (target) {
                ctx.beginPath();
                ctx.moveTo(xTranslate(point.x), nodeHeight + point.y * yTranslate * 0.3);
                ctx.lineTo(xTranslate(target.x), nodeHeight + target.y * yTranslate * 0.3);
                ctx.lineWidth = edge.weight;
                ctx.strokeStyle = '#'+ edge.color;
                ctx.stroke();
                ctx.lineWidth = 1;
                ctx.strokeStyle = '#000000';
            }
        });
    });
}

/**
 * The method that does the zooming, based on your mouse position, the zooming is applied.
 * @param direction
 * @param zoomAmount
 * @param xMousePos
 */
function zoom(direction, zoomAmount, xMousePos) {
    var slider = $('.slider', '#minimap');
    var minimap = $('#minimap');
    var zoomWindow = $('#zoomWindow');
    var maxWidth = minimap.width();
    var currentWidth = slider.width();
    zoomWidth += (direction > 0)?-1*zoomAmount:zoomAmount;
    zoomWidth = Math.max(1, Math.min(zoomWidth, 100));

    var newWidth = Math.max(1, Math.min(maxWidth, zoomWidth / 100 * maxWidth));

    var left = parseInt(slider.css('left').replace('px', ''));
    var difference = Math.abs(currentWidth - newWidth);

    //If zooming in the zoomField should zoom towards the cursor
    if (direction > 0) {
        var mouseX = xMousePos;
        var leftSide = mouseX - left;
        var rightSide = left + currentWidth - mouseX;
        var dif = Math.min(difference, leftSide - rightSide);
        if (dif > 0) {
            left += dif + (difference - dif) / 2;
        } else {
            left += Math.max(0, difference + dif) / 2;
        }
    } else {
        left -= difference / 2;
    }

    left += Math.min(0, maxWidth - (left + newWidth)); //Fix slider going out of right screen.
    left = Math.max(0, Math.min(maxWidth, left));
    slider.css('left', left + 'px');
    slider.width(parseInt(newWidth) +'px');
    clearTimeout(zoomTimeout);
    //If not zooming anymore, update the ZoomWindow with new data.
    if (zoomAmount != 0) {
        zoomTimeout = setTimeout(function () {
            updatezoomWindow();
        }, 500);
    }
}

/**
 * Update the window that shows the zoomed genome
 */
function updatezoomWindow()
{
    if (minimapNodes) {
        var zoom = updateZoomValues();
        var boundingBox = {xleft: zoomLeft, xright: zoomRight, zoom: zoom};
        getNodes(boundingBox, drawZoom);
    }
}

function updateZoomValues()
{
        var slider = $('#minimap .slider');
        var totalWidth = $('#minimap').width();
        var sliderLeft = pxToInt(slider.css('left'));
        var xWidth = minimapNodes[Object.keys(minimapNodes)[Object.keys(minimapNodes).length - 1]].x;
        zoomLeft = Math.max(0, Math.floor(sliderLeft / totalWidth * xWidth));
        zoomRight =  Math.floor((sliderLeft + slider.width()) / totalWidth * xWidth);
        return Math.round(totalWidth / slider.width());
}

// This function translates from one representation of a bounding box in gui coordinates to
// coordinates expected by the REST api. It takes x, y, width and height arguments and returns
// an object with left right top and bottom properties.
function computeBoundingBox(left, width, zoom)
{
    return {
        'xleft': left,
        'xright': (left + width),
        zoom: zoom
    }
}

/**
 * Parse the received node data from the server to something that we can use better.
 * @param nodes
 * @returns {{}}
 */
function parseNodeData(nodes) {
    var result = {};

    if (typeof nodes == 'undefined' || nodes.length == 0) {
        return result;
    }
    var left = nodes[0].x;

    $.each(nodes, function(key, value) {
        result[value.id] = {
            x: value.x - zoomLeft,
            y: value.y,
            strands: value.strands,
            edges: value.edges,
            genomes: value.genomes
        }
    });
    return result;
}

/**
 * Fetch the nodes from the server based on a boundingBox.
 * @param boundingBox
 * @param callback
 */
function getNodes(boundingBox, callback) {
    $.ajax({
        url: url + 'api/getnodes',
        dataType: 'JSON',
        type: 'GET',
        data: boundingBox
    }).done(function(data) {
        callback(parseNodeData(data.cList));
    });
}

/**
 * Initialize both canvasses.
 */
function initialize() {
    initializeMinimap();
    initializeZoomWindow();
}

/**
 * Initialize the minimap canvas
 */
function initializeMinimap() {
    var minimap = $('#minimap');
    minimap.height($('#minimapContainer').height());

    var slider = minimap.find('.slider');

    minimap.find('.canvasContainer').html(
        $('<canvas/>', {'class':'genomeCanvas', Width: minimap.width(), Height: minimap.height() })
    );
    var boundingBox = computeBoundingBox(0, 10000000, 1);
    getNodes(boundingBox, drawMinimap);
    zoom(-1, 1, 1);
}

/**
 * Initialize the zooming window canvas.
 */
function initializeZoomWindow() {
    var zoomWindow = $('#zoomWindow');
    zoomWindow.prepend(
        $('<canvas/>', {'class':'zoomedCanvas', Width: zoomWindow.width(), Height: zoomWindow.height() })
    );
}

/**
 * Make the phylogney Panel small, and the minimap Panel big
 */
function fullSizeMinimap() {
    $("#zoom").animate({
        height: screenHeight - 250 - $('#header').height()
    }, 500);

    $('#phylogenyContainer').animate({
        'width': 30
    }, 1000, function () {
        zoom(1, 0, 1);
        screenResize();
    });
}<|endoftext|>


<commit_before>GRUNT.XHR = (function() {
    // define some content types
    var CONTENT_TYPES = {
        HTML: "text/html",
        XML: "text/xml",
        TEXT: "text/plain",
        STREAM: "application/octet-stream"
    };

    // define some regular expressions to help determine the type of the request
    var REQUEST_URL_EXTENSIONS = {
        JSON: ['json'],
        PDON: ['pdon.txt']
    };

    var INDERMINATE_CONTENT_TYPES = ["TEXT", "STREAM"];

    // initialise some regexes
    var REGEX_URL = /^(\w+:)?\/\/([^\/?#]+)/;

    // define the variable content type processors
    var RESPONSE_TYPE_PROCESSORS = {
        XML: function(xhr, requestParams) {
            return xhr.responseXML;
        },

        JSON: function(xhr, requestParams) {
            // use the JSON object to convert the responseText to a JS object
            try {
                return JSON.parse(xhr.responseText);
            }
            catch (e) {
                GRUNT.Log.error("Error parsing JSON data: ", xhr.responseText);
                GRUNT.Log.exception(e);
            }
        },

        PDON: function(xhr, requestParams) {
            return GRUNT.Data.parse({
                data: xhr.responseText,
                format: "PDON"
            });
        },

        DEFAULT: function(xhr, requestParam) {
            return xhr.responseText;
        }
    }; // CONTENT_TYPE_PROCESSORS

    // define headers
    var HEADERS = {
        CONTENT_TYPE: "Content-Type"
    };

    /**
    This function is used to determine the appropriate request type based on the extension
    of the url that was originally requested.  This function is only called in the case where
    an indeterminate type of content-type has been received from the server that has supplied the
    response (such as application/octet-stream).

    @xhr - the XMLHttpRequest object
    @requestParams - the parameters that were passed to the xhr request
    @fallbackType - the type of request that we will fallback to
    */
    function getProcessorForRequestUrl(xhr, requestParams, fallbackType) {
        for (var requestType in REQUEST_URL_EXTENSIONS) {
            // iterate through the file extensions
            for (var ii = 0; ii < REQUEST_URL_EXTENSIONS[requestType].length; ii++) {
                var fileExt = REQUEST_URL_EXTENSIONS[requestType][ii];

                // if the request url ends with the specified file extension we have a match
                if (new RegExp(fileExt + "$", "i").test(requestParams.url)) {
                    return requestType;
                } // if
            } // for
        } // for

        return fallbackType ? fallbackType : "DEFAULT";
    } // getProcessorForRequestUrl

    function processResponseData(xhr, requestParams) {
        // get the content type of the response
        var contentType = xhr.getResponseHeader(HEADERS.CONTENT_TYPE);
        var processorId;
        var matchedType = false;

        GRUNT.Log.info("processing response data, content type = " + contentType);

        // determine the matching content type
        for (processorId in CONTENT_TYPES) {
            if (contentType && contentType.indexOf(CONTENT_TYPES[processorId]) >= 0) {
                matchedType = true;
                break;
            }
        } // for

        // if the match type was indeterminate, then look at the url of the request to
        // determine which is the best type to match on
        var indeterminate = (! matchedType);
        for (var ii = 0; ii < INDERMINATE_CONTENT_TYPES.length; ii++) {
            indeterminate = indeterminate || (INDERMINATE_CONTENT_TYPES[ii] == processorId);
        } // for

        if (indeterminate) {
            processorId = getProcessorForRequestUrl(xhr, requestParams, processorId);
        } // if

        try {
            GRUNT.Log.info("using processor: " + processorId + " to process response");
            return RESPONSE_TYPE_PROCESSORS[processorId](xhr, requestParams);
        }
        catch (e) {
            GRUNT.Log.warn("error applying processor '" + processorId + "' to response type, falling back to default");
            return RESPONSE_TYPE_PROCESSORS.DEFAULT(xhr, requestParams);
        } // try..catch
    } // processResponseData

    // define self
    var module = {
        id: "grunt.xhr",

        ajaxSettings: {
            xhr: null
        },

        ajax: function(params) {
            // given that I am having to write my own AJAX handling, I think it's safe to assume that I should
            // do that in the context of a try catch statement to catch the things that are going to go wrong...
            try {
                params = GRUNT.extend({
                    method: "GET",
                    data: null,
                    url: null,
                    async: true,
                    success: null,
                    contentType: "application/x-www-form-urlencoded"
                }, module.ajaxSettings, params);

                // determine if this is a remote request (as per the jQuery ajax calls)
                var parts = REGEX_URL.exec(params.url),
                    remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);

                // if we have data, then update the method to POST
                if (params.data) {
                    params.method = "POST";
                } // if

                // if the url is empty, then log an error
                if (! params.url) {
                    GRUNT.Log.warn("ajax request issued with no url - that ain't going to work...");
                    return;
                } // if

                // if the we have an xhr creator registered, then let it decide whether it wants to create the client
                var xhr = null;
                if (params.xhr) {
                    xhr = params.xhr(params);
                } // if

                // if the optional creator, didn't create the client, then create the default client
                if (! xhr) {
                    xhr = new XMLHttpRequest();
                } // if

                GRUNT.Log.info("opening request: " + JSON.stringify(params));

                // open the request
                // TODO: support basic authentication
                xhr.open(params.method, params.url, params.async);

                // if we are sending data, then set the correct content type
                if (params.data) {
                    xhr.setRequestHeader("Content-Type", params.contentType);
                } // if

                // if this is not a remote request, the set the requested with header
                if (! remote) {
                    xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
                } // if

                xhr.onreadystatechange = function() {
                    if (this.readyState === 4) {
                        var responseData = null;

                        try {
                            // process the response
                            responseData = processResponseData(this, params);
                        }
                        catch (e) {
                            GRUNT.Log.exception(e, "PROCESSING AJAX RESPONSE");
                        } // try..catch

                        // if the success callback is defined, then call it
                        GRUNT.Log.info("received response, calling success handler: " + params.success);
                        if (params.success) {
                            params.success.call(this, responseData);
                        } // if
                    } // if
                }; // onreadystatechange

                // send the request
                GRUNT.Log.info("sending request with data: " + module.param(params.data));
                xhr.send(params.method == "POST" ? module.param(params.data) : null);
            }
            catch (e) {
                GRUNT.Log.exception(e);
            } // try..catch
        }, // ajax

        param: function(data) {
            // iterate through the members of the data and convert to a paramstring
            var params = [];
            var addKeyVal = function (key, value) {
                // If value is a function, invoke it and return its value
                value = GRUNT.isFunction(value) ? value() : value;
                params[ params.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value);
            };

            // If an array was passed in, assume that it is an array of form elements.
            if (GRUNT.isArray(data)) {
                for (var ii = 0; ii < data.length; ii++) {
                    addKeyVal(data[ii].name, data[ii].value);
                } // for
            }
            else {
                for (var keyname in data) {
                    addKeyVal(keyname, data[keyname]);
                } // for
            } // if..else

            // Return the resulting serialization
            return params.join("&").replace(/%20/g, "+");
        }
    }; // self

    return module;
})();

<commit_msg>Improved content-type matching<commit_after>GRUNT.XHR = (function() {
    // define some content types
    var CONTENT_TYPES = {
        HTML: "text/html",
        XML: "text/xml",
        TEXT: "text/plain",
        STREAM: "application/octet-stream"
    };

    // define some regular expressions to help determine the type of the request
    var REQUEST_URL_EXTENSIONS = {
        JSON: ['json'],
        PDON: ['pdon.txt']
    };

    var INDERMINATE_CONTENT_TYPES = ["TEXT", "STREAM"];

    // initialise some regexes
    var REGEX_URL = /^(\w+:)?\/\/([^\/?#]+)/;

    // define the variable content type processors
    var RESPONSE_TYPE_PROCESSORS = {
        XML: function(xhr, requestParams) {
            return xhr.responseXML;
        },

        JSON: function(xhr, requestParams) {
            // use the JSON object to convert the responseText to a JS object
            try {
                return JSON.parse(xhr.responseText);
            }
            catch (e) {
                GRUNT.Log.error("Error parsing JSON data: ", xhr.responseText);
                GRUNT.Log.exception(e);
            }
        },

        PDON: function(xhr, requestParams) {
            return GRUNT.Data.parse({
                data: xhr.responseText,
                format: "PDON"
            });
        },

        DEFAULT: function(xhr, requestParam) {
            return xhr.responseText;
        }
    }; // CONTENT_TYPE_PROCESSORS

    // define headers
    var HEADERS = {
        CONTENT_TYPE: "Content-Type"
    };

    /**
    This function is used to determine the appropriate request type based on the extension
    of the url that was originally requested.  This function is only called in the case where
    an indeterminate type of content-type has been received from the server that has supplied the
    response (such as application/octet-stream).

    @xhr - the XMLHttpRequest object
    @requestParams - the parameters that were passed to the xhr request
    @fallbackType - the type of request that we will fallback to
    */
    function getProcessorForRequestUrl(xhr, requestParams, fallbackType) {
        for (var requestType in REQUEST_URL_EXTENSIONS) {
            // iterate through the file extensions
            for (var ii = 0; ii < REQUEST_URL_EXTENSIONS[requestType].length; ii++) {
                var fileExt = REQUEST_URL_EXTENSIONS[requestType][ii];

                // if the request url ends with the specified file extension we have a match
                if (new RegExp(fileExt + "$", "i").test(requestParams.url)) {
                    return requestType;
                } // if
            } // for
        } // for

        return fallbackType ? fallbackType : "DEFAULT";
    } // getProcessorForRequestUrl

    function processResponseData(xhr, requestParams) {
        // get the content type of the response
        var contentType = xhr.getResponseHeader(HEADERS.CONTENT_TYPE);
        var processorId;
        var matchedType = false;

        GRUNT.Log.info("processing response data, content type = " + contentType);

        // determine the matching content type
        for (processorId in CONTENT_TYPES) {
            if (contentType && (contentType.indexOf(CONTENT_TYPES[processorId]) >= 0)) {
                matchedType = true;
                break;
            }
        } // for

        // if the match type was indeterminate, then look at the url of the request to
        // determine which is the best type to match on
        var indeterminate = (! matchedType);
        for (var ii = 0; ii < INDERMINATE_CONTENT_TYPES.length; ii++) {
            indeterminate = indeterminate || (INDERMINATE_CONTENT_TYPES[ii] == processorId);
        } // for

        if (indeterminate) {
            processorId = getProcessorForRequestUrl(xhr, requestParams, processorId);
        } // if

        try {
            GRUNT.Log.info("using processor: " + processorId + " to process response");
            return RESPONSE_TYPE_PROCESSORS[processorId](xhr, requestParams);
        }
        catch (e) {
            GRUNT.Log.warn("error applying processor '" + processorId + "' to response type, falling back to default");
            return RESPONSE_TYPE_PROCESSORS.DEFAULT(xhr, requestParams);
        } // try..catch
    } // processResponseData

    // define self
    var module = {
        id: "grunt.xhr",

        ajaxSettings: {
            xhr: null
        },

        ajax: function(params) {
            // given that I am having to write my own AJAX handling, I think it's safe to assume that I should
            // do that in the context of a try catch statement to catch the things that are going to go wrong...
            try {
                params = GRUNT.extend({
                    method: "GET",
                    data: null,
                    url: null,
                    async: true,
                    success: null,
                    contentType: "application/x-www-form-urlencoded"
                }, module.ajaxSettings, params);

                // determine if this is a remote request (as per the jQuery ajax calls)
                var parts = REGEX_URL.exec(params.url),
                    remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);

                // if we have data, then update the method to POST
                if (params.data) {
                    params.method = "POST";
                } // if

                // if the url is empty, then log an error
                if (! params.url) {
                    GRUNT.Log.warn("ajax request issued with no url - that ain't going to work...");
                    return;
                } // if

                // if the we have an xhr creator registered, then let it decide whether it wants to create the client
                var xhr = null;
                if (params.xhr) {
                    xhr = params.xhr(params);
                } // if

                // if the optional creator, didn't create the client, then create the default client
                if (! xhr) {
                    xhr = new XMLHttpRequest();
                } // if

                GRUNT.Log.info("opening request: " + JSON.stringify(params));

                // open the request
                // TODO: support basic authentication
                xhr.open(params.method, params.url, params.async);

                // if we are sending data, then set the correct content type
                if (params.data) {
                    xhr.setRequestHeader("Content-Type", params.contentType);
                } // if

                // if this is not a remote request, the set the requested with header
                if (! remote) {
                    xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
                } // if

                xhr.onreadystatechange = function() {
                    if (this.readyState === 4) {
                        var responseData = null;

                        try {
                            // process the response
                            responseData = processResponseData(this, params);
                        }
                        catch (e) {
                            GRUNT.Log.exception(e, "PROCESSING AJAX RESPONSE");
                        } // try..catch

                        // if the success callback is defined, then call it
                        GRUNT.Log.info("received response, calling success handler: " + params.success);
                        if (params.success) {
                            params.success.call(this, responseData);
                        } // if
                    } // if
                }; // onreadystatechange

                // send the request
                GRUNT.Log.info("sending request with data: " + module.param(params.data));
                xhr.send(params.method == "POST" ? module.param(params.data) : null);
            }
            catch (e) {
                GRUNT.Log.exception(e);
            } // try..catch
        }, // ajax

        param: function(data) {
            // iterate through the members of the data and convert to a paramstring
            var params = [];
            var addKeyVal = function (key, value) {
                // If value is a function, invoke it and return its value
                value = GRUNT.isFunction(value) ? value() : value;
                params[ params.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value);
            };

            // If an array was passed in, assume that it is an array of form elements.
            if (GRUNT.isArray(data)) {
                for (var ii = 0; ii < data.length; ii++) {
                    addKeyVal(data[ii].name, data[ii].value);
                } // for
            }
            else {
                for (var keyname in data) {
                    addKeyVal(keyname, data[keyname]);
                } // for
            } // if..else

            // Return the resulting serialization
            return params.join("&").replace(/%20/g, "+");
        }
    }; // self

    return module;
})();

<|endoftext|>


<commit_before>/**
 * @author mrdoob / http://mrdoob.com/
 */

var FRAME = ( function () {

	var dom = null;
	var resources = {};

	return {

		VERSION: 3,

		getDOM: function () {

			return dom;

		},

		setDOM: function ( value ) {

			dom = value;

		},

		addResource: function ( name, resource ) {

			resources[ name ] = resource;

		},

		getResource: function ( name ) {

			return resources[ name ];

		},

		//

		/*
		Curves: {

			Linear: function ( points ) {

				function linear( p0, p1, t0, t1, t ) {

					return ( p1 - p0 ) * ( ( t - t0 ) / ( t1 - t0 ) ) + p0;

				}

				this.points = points;
				this.value = 0;

				this.update = function ( time ) {

					if ( time <= points[ 0 ] ) {

						this.value = points[ 1 ];

					} else if ( time >= points[ points.length - 2 ] ) {

						this.value = points[ points.length - 1 ];

					} else {

						for ( var i = 0, l = points.length; i < l; i += 2 ) {

							if ( time < points[ i + 2 ] ) {

								this.value = linear( points[ i + 1 ], points[ i + 3 ], points[ i ], points[ i + 2 ], time );
								break;

							}

						}

					}

				};

			},

			Sin: function () {

				var frequency = 10;

				this.value = 0;

				this.update = function ( time ) {

					this.value = Math.sin( time * frequency );

				};

			},

			Saw: function ( frequency, offset, min, max ) {

				var delta = max - min;

				this.frequency = frequency;
				this.offset = offset;
				this.min = min;
				this.max = max;
				this.value = 0;

				this.update = function ( time ) {

					this.value = ( ( ( time - offset ) % frequency ) / frequency ) * delta + min;

				};

			}

		},
		*/

		Parameters: {

			Boolean: function ( name, value ) {
				this.name = name;
				this.value = value !== undefined ? value : true;
			},

			Color: function ( name, value ) {
				this.name = name;
				this.value = value !== undefined ? value : 0xffffff;
			},

			Float: function ( name, value, min, max ) {
				this.name = name;
				this.value = value || 0.0;
				this.min = min !== undefined ? min : - Infinity;
				this.max = max !== undefined ? max : Infinity;
			},

			Integer: function ( name, value, min, max ) {
				this.name = name;
				this.value = value || 0;
				this.min = min !== undefined ? min : - Infinity;
				this.max = max !== undefined ? max : Infinity;
			},

			String: function ( name, value ) {
				this.name = name;
				this.value = value !== undefined ? value : '';
			},

			Vector2: function ( name, value ) {
				this.name = name;
				this.value = value !== undefined ? value : [ 0, 0 ];
			},

			Vector3: function ( name, value ) {
				this.name = name;
				this.value = value !== undefined ? value : [ 0, 0, 0 ];
			}

		},

		Effect: function ( name, source ) {

			this.name = name;
			this.source = source || 'var name = \'Effect\';\n\nvar parameters = {\n\tvalue: new FRAME.Parameters.Float( \'Value\', 1.0 )\n};\n\n// function init(){}\n\nfunction start(){}\n\nfunction end(){}\n\nfunction update( progress ){}';
			this.program = null;
			this.compile = function () {

				this.program = ( new Function( 'name', 'parameters', 'init', 'start', 'end', 'update', this.source + '\nreturn { parameters: parameters, init: init, start: start, end: end, update: update, name: name };' ) )();
				if ( this.program.name && this.program.name !== this.name ) {
					this.name = this.program.name;
				}

			};

		},

		Animation: function () {

			var id = 0;

			return function ( name, start, end, layer, effect ) {

				this.id = id ++;
				this.name = name;
				this.start = start;
				this.end = end;
				this.layer = layer;
				this.effect = effect;

				// compile

				if ( effect.program === null ) {

					effect.compile();

				}

			};

		}(),

		Timeline: function () {

			var curves = [];
			var animations = [];
			var active = [];

			var next = 0, prevtime = 0;

			return {

				curves: curves,
				animations: animations,

				load: function ( url, onLoad ) {

					var scope = this;

					var request = new XMLHttpRequest();
					request.open( 'GET', url, true );
					request.addEventListener( 'load', function ( event ) {

						var json = JSON.parse( event.target.response );

						var includes = json.includes;

						for ( var i = 0, l = includes.length; i < l; i ++ ) {

							var include = includes[ i ];

							var script = document.createElement( 'script' );
							script.textContent = include[ 1 ];
							document.head.appendChild( script );

						}

						var library = [];
						var effects = json.effects;

						for ( var i = 0, l = effects.length; i < l; i ++ ) {

							var data = effects[ i ];

							library.push( new FRAME.Effect( data[ 0 ], data[ 1 ] ) );

						}

						var animations = json.animations;

						for ( var i = 0, l = animations.length; i < l; i ++ ) {

							var data = animations[ i ];

							var animation = new FRAME.Animation(
								data[ 0 ],
								data[ 1 ],
								data[ 2 ],
								data[ 3 ],
								library[ data[ 4 ] ]
							);

							scope.add( animation );

						}

						if ( onLoad ) onLoad();

					} );
					request.send( null );

				},

				add: function ( animation ) {

					animations.push( animation );
					this.sort();

				},

				remove: function ( animation ) {

					var i = animations.indexOf( animation );

					if ( i !== -1 ) {

						animations.splice( i, 1 );

					}

				},

				sort: function () {

					animations.sort( function ( a, b ) { return a.start - b.start; } );

				},

				update: function ( time ) {

					if ( time < prevtime ) {

						this.reset();

					}

					var animation;

					// add to active

					while ( animations[ next ] ) {

						animation = animations[ next ];

						if ( animation.start > time ) {

							break;

						}

						if ( animation.end > time ) {

							if ( animation.effect.program.start ) {

								animation.effect.program.start();

							}

							active.push( animation );

						}

						next ++;

					}

					// remove from active

					var i = 0;

					while ( active[ i ] ) {

						animation = active[ i ];

						if ( animation.start > time || animation.end < time ) {

							if ( animation.effect.program.end ) {

								animation.effect.program.end();

							}

							active.splice( i, 1 );

							continue;

						}

						i ++;

					}

					// update curves

					for ( i = 0, l = curves.length; i < l; i ++ ) {

						curves[ i ].update( time );

					}

					// render

					active.sort( function ( a, b ) { return a.layer - b.layer; } );

					for ( i = 0, l = active.length; i < l; i ++ ) {

						animation = active[ i ];
						animation.effect.program.update( ( time - animation.start ) / ( animation.end - animation.start ) );

					}

					prevtime = time;

				},

				reset: function () {

					while ( active.length ) {

						var animation = active.pop();

						if ( animation.effect.program.end ) {

							animation.effect.program.end();

						}

					}

					next = 0;

				}

			};

		}

	};

} )();
<commit_msg>Editor/Release: Using closure for includes.<commit_after>/**
 * @author mrdoob / http://mrdoob.com/
 */

var FRAME = ( function () {

	var dom = null;
	var resources = {};

	return {

		VERSION: 3,

		getDOM: function () {

			return dom;

		},

		setDOM: function ( value ) {

			dom = value;

		},

		addResource: function ( name, resource ) {

			resources[ name ] = resource;

		},

		getResource: function ( name ) {

			return resources[ name ];

		},

		//

		/*
		Curves: {

			Linear: function ( points ) {

				function linear( p0, p1, t0, t1, t ) {

					return ( p1 - p0 ) * ( ( t - t0 ) / ( t1 - t0 ) ) + p0;

				}

				this.points = points;
				this.value = 0;

				this.update = function ( time ) {

					if ( time <= points[ 0 ] ) {

						this.value = points[ 1 ];

					} else if ( time >= points[ points.length - 2 ] ) {

						this.value = points[ points.length - 1 ];

					} else {

						for ( var i = 0, l = points.length; i < l; i += 2 ) {

							if ( time < points[ i + 2 ] ) {

								this.value = linear( points[ i + 1 ], points[ i + 3 ], points[ i ], points[ i + 2 ], time );
								break;

							}

						}

					}

				};

			},

			Sin: function () {

				var frequency = 10;

				this.value = 0;

				this.update = function ( time ) {

					this.value = Math.sin( time * frequency );

				};

			},

			Saw: function ( frequency, offset, min, max ) {

				var delta = max - min;

				this.frequency = frequency;
				this.offset = offset;
				this.min = min;
				this.max = max;
				this.value = 0;

				this.update = function ( time ) {

					this.value = ( ( ( time - offset ) % frequency ) / frequency ) * delta + min;

				};

			}

		},
		*/

		Parameters: {

			Boolean: function ( name, value ) {
				this.name = name;
				this.value = value !== undefined ? value : true;
			},

			Color: function ( name, value ) {
				this.name = name;
				this.value = value !== undefined ? value : 0xffffff;
			},

			Float: function ( name, value, min, max ) {
				this.name = name;
				this.value = value || 0.0;
				this.min = min !== undefined ? min : - Infinity;
				this.max = max !== undefined ? max : Infinity;
			},

			Integer: function ( name, value, min, max ) {
				this.name = name;
				this.value = value || 0;
				this.min = min !== undefined ? min : - Infinity;
				this.max = max !== undefined ? max : Infinity;
			},

			String: function ( name, value ) {
				this.name = name;
				this.value = value !== undefined ? value : '';
			},

			Vector2: function ( name, value ) {
				this.name = name;
				this.value = value !== undefined ? value : [ 0, 0 ];
			},

			Vector3: function ( name, value ) {
				this.name = name;
				this.value = value !== undefined ? value : [ 0, 0, 0 ];
			}

		},

		Effect: function ( name, source ) {

			this.name = name;
			this.source = source || 'var name = \'Effect\';\n\nvar parameters = {\n\tvalue: new FRAME.Parameters.Float( \'Value\', 1.0 )\n};\n\n// function init(){}\n\nfunction start(){}\n\nfunction end(){}\n\nfunction update( progress ){}';
			this.program = null;
			this.compile = function () {

				this.program = ( new Function( 'name', 'parameters', 'init', 'start', 'end', 'update', this.source + '\nreturn { parameters: parameters, init: init, start: start, end: end, update: update, name: name };' ) )();
				if ( this.program.name && this.program.name !== this.name ) {
					this.name = this.program.name;
				}

			};

		},

		Animation: function () {

			var id = 0;

			return function ( name, start, end, layer, effect ) {

				this.id = id ++;
				this.name = name;
				this.start = start;
				this.end = end;
				this.layer = layer;
				this.effect = effect;

				// compile

				if ( effect.program === null ) {

					effect.compile();

				}

			};

		}(),

		Timeline: function () {

			var curves = [];
			var animations = [];
			var active = [];

			var next = 0, prevtime = 0;

			return {

				curves: curves,
				animations: animations,

				load: function ( url, onLoad ) {

					var scope = this;

					var request = new XMLHttpRequest();
					request.open( 'GET', url, true );
					request.addEventListener( 'load', function ( event ) {

						var json = JSON.parse( event.target.response );

						var includes = json.includes;

						for ( var i = 0, l = includes.length; i < l; i ++ ) {

							var include = includes[ i ];

							var script = document.createElement( 'script' );
							script.textContent = '( function () { ' + include[ 1 ] + '} )()';
							document.head.appendChild( script );

						}

						var library = [];
						var effects = json.effects;

						for ( var i = 0, l = effects.length; i < l; i ++ ) {

							var data = effects[ i ];

							library.push( new FRAME.Effect( data[ 0 ], data[ 1 ] ) );

						}

						var animations = json.animations;

						for ( var i = 0, l = animations.length; i < l; i ++ ) {

							var data = animations[ i ];

							var animation = new FRAME.Animation(
								data[ 0 ],
								data[ 1 ],
								data[ 2 ],
								data[ 3 ],
								library[ data[ 4 ] ]
							);

							scope.add( animation );

						}

						if ( onLoad ) onLoad();

					} );
					request.send( null );

				},

				add: function ( animation ) {

					animations.push( animation );
					this.sort();

				},

				remove: function ( animation ) {

					var i = animations.indexOf( animation );

					if ( i !== -1 ) {

						animations.splice( i, 1 );

					}

				},

				sort: function () {

					animations.sort( function ( a, b ) { return a.start - b.start; } );

				},

				update: function ( time ) {

					if ( time < prevtime ) {

						this.reset();

					}

					var animation;

					// add to active

					while ( animations[ next ] ) {

						animation = animations[ next ];

						if ( animation.start > time ) {

							break;

						}

						if ( animation.end > time ) {

							if ( animation.effect.program.start ) {

								animation.effect.program.start();

							}

							active.push( animation );

						}

						next ++;

					}

					// remove from active

					var i = 0;

					while ( active[ i ] ) {

						animation = active[ i ];

						if ( animation.start > time || animation.end < time ) {

							if ( animation.effect.program.end ) {

								animation.effect.program.end();

							}

							active.splice( i, 1 );

							continue;

						}

						i ++;

					}

					// update curves

					for ( i = 0, l = curves.length; i < l; i ++ ) {

						curves[ i ].update( time );

					}

					// render

					active.sort( function ( a, b ) { return a.layer - b.layer; } );

					for ( i = 0, l = active.length; i < l; i ++ ) {

						animation = active[ i ];
						animation.effect.program.update( ( time - animation.start ) / ( animation.end - animation.start ) );

					}

					prevtime = time;

				},

				reset: function () {

					while ( active.length ) {

						var animation = active.pop();

						if ( animation.effect.program.end ) {

							animation.effect.program.end();

						}

					}

					next = 0;

				}

			};

		}

	};

} )();
<|endoftext|>


<commit_before>import React from 'react';
import cx from 'classnames';
import includes from 'lodash/collection/includes';
import { skinnable, t, props, pure } from '../utils';
import FlexView from '../flex/FlexView';
import { Table as FixedDataTable } from 'fixed-data-table';
import warn from '../utils/log';

import './patch-fixed-data-table';

import 'fixed-data-table/dist/fixed-data-table-base.min.css';
import './table.scss';

const ARROW_UP = 38;
const ARROW_DOWN = 40;

export const isInvariantSatisfied = ({ selectionType, onRowsSelect, onRowSelect }) => {
  const multipleSelectionEnabled = selectionType === 'multi';
  /* eslint-disable no-console, quotes */
  if (onRowSelect && onRowsSelect) {
    warn("'onRowSelect' and 'onRowsSelect' are exclusive. Use the former if 'multipleSelectionEnabled' is true, or the latter if it's false");
    return false;
  } else if (multipleSelectionEnabled && onRowSelect) {
    warn("'multipleSelectionEnabled' is true, so 'onRowSelect' will never be called. Use 'onRowsSelect' (plural) instead.");
    return false;
  } else if (!multipleSelectionEnabled && onRowsSelect) {
    warn("'multipleSelectionEnabled' is false, so 'onRowsSelect' will never be called. Use 'onRowSelect' (singular) instead.");
    return false;
  }
  return true;
  /* eslint-enable no-console, quotes */
};

const Props = t.subtype(t.struct({
  /** the desired height of the table */
  height: t.maybe(t.Number),
  /** the desired width of the table */
  width: t.maybe(t.Number),
  /** if true, the table will fit the container if width and height */
  autoSize: t.maybe(t.Boolean),
  /** if true, the columns will split evenly the width, avoiding horizontal scrollbars */
  autoSizeColumns: t.maybe(t.Boolean),
  /** height in pixel of every row */
  rowHeight: t.maybe(t.Number),
  /** height in pixel of header */
  headerHeight: t.maybe(t.Number),
  /** height in pixel of groupHeader */
  groupHeaderHeight: t.maybe(t.Number),
  /** height in pixel of footer */
  footerHeight: t.maybe(t.Number),
  /** method to get the desired row of data */
  rowGetter: t.Function,
  /** number of row displayed in the table */
  rowsCount: t.maybe(t.Number),
  cellRenderer: t.Function,
  children: t.ReactNode,
  sort: t.maybe(t.struct({
    by: t.maybe(t.String),
    dir: t.maybe(t.enums.of(['asc', 'desc'], 'sortDir'))
  })),

  selectedRows: t.maybe(t.list(t.Number)),
  onRowSelect: t.maybe(t.Function),
  onRowsSelect: t.maybe(t.Function),
  selectionType: t.enums.of(['none', 'single', 'multi'], 'selectionType'),

  className: t.maybe(t.String),
  style: t.maybe(t.Object),
  id: t.maybe(t.String)
}), isInvariantSatisfied, 'FixedDataTableWrapperProps');

@pure
@skinnable()
@props(Props)
export default class Table extends React.Component {

  constructor(props) {
    super(props);
    const { height, width, selectedRows } = props;
    this.state = {
      height,
      width,
      scrollToRow: selectedRows[0]
    };
  }

  static defaultProps = {
    width: 400,
    height: 400,
    selectedRows: []
  };

  getNode = () => React.findDOMNode(this.refs.wrapper);

  componentDidMount() {
    if (this.props.selectionType !== 'none') {
      this.attachKeyPressListener();
      this.scrollToSelectedRow();
    }
    if (this.props.autoSize) {
      this.autoSize();
      this.startAutoSizeInterval();
    }
  }

  componentDidUpdate(newProps) {
    if (newProps.selectedRows[0] !== this.props.selectedRows[0]) {
      this.scrollToSelectedRow();
    }
  }

  componentWillUnmount() {
    this.removeKeyPressListener();
    this.stopAutoSizeInterval();
  }

  startAutoSizeInterval = () => {
    this.autoSizeInterval = setInterval(this.autoSize, 200);
  };

  stopAutoSizeInterval = () => {
    clearInterval(this.autoSizeInterval);
  };

  autoSize = () => {
    const node = this.getNode();
    if (node) {
      const { clientHeight: height, clientWidth: width } = node;
      const { height: currentHeight, width: currentWidth } = this.state;
      if ((height !== currentHeight) || (width !== currentWidth)) {
        this.setState({ height, width }, this.scrollToSelectedRow);
      }
    }
  };

  scrollToSelectedRow = () => {
    this.setState({
      scrollToRow: this.props.selectedRows[0]
    });
  };

  onScrollStart = () => {
    this.setState({
      scrollToRow: null
    });
  };

  getDirectionFromKeyCode = (keyCode) => {
    switch (keyCode) {
      case ARROW_UP: return -1;
      case ARROW_DOWN: return 1;
      default: return null;
    }
  };

  selectAdjacentRow = (direction) => {
    const { selectedRows, selectionType, onRowSelect, onRowsSelect } = this.props;
    if (!direction || selectedRows.length !== 1) {
      return;
    }
    const rowIndexToSelect = selectedRows[0] + direction;
    if (rowIndexToSelect < 0 || rowIndexToSelect >= this.props.rowsCount) {
      return;
    }
    const multipleSelectionEnabled = selectionType === 'multi';
    if (multipleSelectionEnabled) {
      onRowsSelect([rowIndexToSelect]);
    } else {
      onRowSelect(rowIndexToSelect);
    }
  };

  onKeyPress = ({ which, keyCode }) => {
    const direction = this.getDirectionFromKeyCode(which | keyCode);
    return direction && this.selectAdjacentRow(direction);
  };

  attachKeyPressListener = () => {
    return this.getNode().addEventListener('keydown', this.onKeyPress, false);
  };

  removeKeyPressListener = () => {
    return this.getNode().removeEventListener('keydown', this.onKeyPress, false);
  };

  onRowClick = ({ ctrlKey, metaKey }, index) => {
    const { selectedRows, selectionType } = this.props;
    if (selectionType === 'none') {
      return;
    }
    const multipleSelectionEnabled = selectionType === 'multi';

    if (multipleSelectionEnabled && (ctrlKey || metaKey)) {
      if (includes(selectedRows, index)) {
        this.props.onRowsSelect(selectedRows.filter(idx => idx !== index));
      } else {
        this.props.onRowsSelect([index, ...selectedRows]);
      }
    } else if (multipleSelectionEnabled) {
      this.props.onRowsSelect([index]);
    } else {
      this.props.onRowSelect(index);
    }
  };

  cellRenderer = (cellData, cellDataKey, rowData, rowIndex) => {
    const selected = this.props.selectedRows.indexOf(rowIndex) !== -1;
    return this.props.cellRenderer(cellData, selected, cellDataKey, rowData);
  };

  getLocals() {
    const {
      onRowClick,
      cellRenderer,
      onScrollStart,
      props: {
        rowHeight, headerHeight, footerHeight, rowGetter, rowsCount, groupHeaderHeight,
        selectionType,
        id, children, autoSizeColumns
      },
      state: { height, width, scrollToRow }
    } = this;

    const tabIndex = selectionType !== 'none' ? '0' : null;

    const numberOfColumns = [].concat(children).length;
    const footerDataGetter = () => true; /*because https://github.com/facebook/fixed-data-table/issues/172*/

    const columnWidth = autoSizeColumns ? { width: width/numberOfColumns } : {};
    const columnProps = { ...columnWidth, cellRenderer, headerClassName: 'fixed-data-tablew-wrapper-header' };
    const addProps = column => React.cloneElement(column, { ...columnProps });
    const columns = numberOfColumns === 1 ? addProps(children) : [].concat(children).map(addProps);

    const style = {
      ...this.props.style,
      position: 'absolute'
    };

    const className = cx('fixed-data-table-wrapper', this.props.className, { selectable: selectionType !== 'none' });

    return {
      wrapperProps: { id, style, grow: true, className, tabIndex, ref: 'wrapper', width: '100%', height: '100%' },
      tableProps: {
        width: width + 2, height: height + 2, // as long as FDT counts the borders
        scrollToRow, onRowClick, onScrollStart,
        rowHeight, headerHeight, footerHeight, rowGetter, rowsCount, footerDataGetter, groupHeaderHeight
      },
      columns
    };
  }

  template({ wrapperProps, tableProps, columns }) {
    return (
      <FlexView {...wrapperProps}>
        <FixedDataTable {...tableProps}>
          {columns}
        </FixedDataTable>
      </FlexView>
    );
  }
}
<commit_msg>fix `warn` import<commit_after>import React from 'react';
import cx from 'classnames';
import includes from 'lodash/collection/includes';
import { skinnable, t, props, pure } from '../utils';
import FlexView from '../flex/FlexView';
import { Table as FixedDataTable } from 'fixed-data-table';
import { warn } from '../utils/log';

import './patch-fixed-data-table';

import 'fixed-data-table/dist/fixed-data-table-base.min.css';
import './table.scss';

const ARROW_UP = 38;
const ARROW_DOWN = 40;

export const isInvariantSatisfied = ({ selectionType, onRowsSelect, onRowSelect }) => {
  const multipleSelectionEnabled = selectionType === 'multi';
  /* eslint-disable no-console, quotes */
  if (onRowSelect && onRowsSelect) {
    warn("'onRowSelect' and 'onRowsSelect' are exclusive. Use the former if 'multipleSelectionEnabled' is true, or the latter if it's false");
    return false;
  } else if (multipleSelectionEnabled && onRowSelect) {
    warn("'multipleSelectionEnabled' is true, so 'onRowSelect' will never be called. Use 'onRowsSelect' (plural) instead.");
    return false;
  } else if (!multipleSelectionEnabled && onRowsSelect) {
    warn("'multipleSelectionEnabled' is false, so 'onRowsSelect' will never be called. Use 'onRowSelect' (singular) instead.");
    return false;
  }
  return true;
  /* eslint-enable no-console, quotes */
};

const Props = t.subtype(t.struct({
  /** the desired height of the table */
  height: t.maybe(t.Number),
  /** the desired width of the table */
  width: t.maybe(t.Number),
  /** if true, the table will fit the container if width and height */
  autoSize: t.maybe(t.Boolean),
  /** if true, the columns will split evenly the width, avoiding horizontal scrollbars */
  autoSizeColumns: t.maybe(t.Boolean),
  /** height in pixel of every row */
  rowHeight: t.maybe(t.Number),
  /** height in pixel of header */
  headerHeight: t.maybe(t.Number),
  /** height in pixel of groupHeader */
  groupHeaderHeight: t.maybe(t.Number),
  /** height in pixel of footer */
  footerHeight: t.maybe(t.Number),
  /** method to get the desired row of data */
  rowGetter: t.Function,
  /** number of row displayed in the table */
  rowsCount: t.maybe(t.Number),
  cellRenderer: t.Function,
  children: t.ReactNode,
  sort: t.maybe(t.struct({
    by: t.maybe(t.String),
    dir: t.maybe(t.enums.of(['asc', 'desc'], 'sortDir'))
  })),

  selectedRows: t.maybe(t.list(t.Number)),
  onRowSelect: t.maybe(t.Function),
  onRowsSelect: t.maybe(t.Function),
  selectionType: t.enums.of(['none', 'single', 'multi'], 'selectionType'),

  className: t.maybe(t.String),
  style: t.maybe(t.Object),
  id: t.maybe(t.String)
}), isInvariantSatisfied, 'FixedDataTableWrapperProps');

@pure
@skinnable()
@props(Props)
export default class Table extends React.Component {

  constructor(props) {
    super(props);
    const { height, width, selectedRows } = props;
    this.state = {
      height,
      width,
      scrollToRow: selectedRows[0]
    };
  }

  static defaultProps = {
    width: 400,
    height: 400,
    selectedRows: []
  };

  getNode = () => React.findDOMNode(this.refs.wrapper);

  componentDidMount() {
    if (this.props.selectionType !== 'none') {
      this.attachKeyPressListener();
      this.scrollToSelectedRow();
    }
    if (this.props.autoSize) {
      this.autoSize();
      this.startAutoSizeInterval();
    }
  }

  componentDidUpdate(newProps) {
    if (newProps.selectedRows[0] !== this.props.selectedRows[0]) {
      this.scrollToSelectedRow();
    }
  }

  componentWillUnmount() {
    this.removeKeyPressListener();
    this.stopAutoSizeInterval();
  }

  startAutoSizeInterval = () => {
    this.autoSizeInterval = setInterval(this.autoSize, 200);
  };

  stopAutoSizeInterval = () => {
    clearInterval(this.autoSizeInterval);
  };

  autoSize = () => {
    const node = this.getNode();
    if (node) {
      const { clientHeight: height, clientWidth: width } = node;
      const { height: currentHeight, width: currentWidth } = this.state;
      if ((height !== currentHeight) || (width !== currentWidth)) {
        this.setState({ height, width }, this.scrollToSelectedRow);
      }
    }
  };

  scrollToSelectedRow = () => {
    this.setState({
      scrollToRow: this.props.selectedRows[0]
    });
  };

  onScrollStart = () => {
    this.setState({
      scrollToRow: null
    });
  };

  getDirectionFromKeyCode = (keyCode) => {
    switch (keyCode) {
      case ARROW_UP: return -1;
      case ARROW_DOWN: return 1;
      default: return null;
    }
  };

  selectAdjacentRow = (direction) => {
    const { selectedRows, selectionType, onRowSelect, onRowsSelect } = this.props;
    if (!direction || selectedRows.length !== 1) {
      return;
    }
    const rowIndexToSelect = selectedRows[0] + direction;
    if (rowIndexToSelect < 0 || rowIndexToSelect >= this.props.rowsCount) {
      return;
    }
    const multipleSelectionEnabled = selectionType === 'multi';
    if (multipleSelectionEnabled) {
      onRowsSelect([rowIndexToSelect]);
    } else {
      onRowSelect(rowIndexToSelect);
    }
  };

  onKeyPress = ({ which, keyCode }) => {
    const direction = this.getDirectionFromKeyCode(which | keyCode);
    return direction && this.selectAdjacentRow(direction);
  };

  attachKeyPressListener = () => {
    return this.getNode().addEventListener('keydown', this.onKeyPress, false);
  };

  removeKeyPressListener = () => {
    return this.getNode().removeEventListener('keydown', this.onKeyPress, false);
  };

  onRowClick = ({ ctrlKey, metaKey }, index) => {
    const { selectedRows, selectionType } = this.props;
    if (selectionType === 'none') {
      return;
    }
    const multipleSelectionEnabled = selectionType === 'multi';

    if (multipleSelectionEnabled && (ctrlKey || metaKey)) {
      if (includes(selectedRows, index)) {
        this.props.onRowsSelect(selectedRows.filter(idx => idx !== index));
      } else {
        this.props.onRowsSelect([index, ...selectedRows]);
      }
    } else if (multipleSelectionEnabled) {
      this.props.onRowsSelect([index]);
    } else {
      this.props.onRowSelect(index);
    }
  };

  cellRenderer = (cellData, cellDataKey, rowData, rowIndex) => {
    const selected = this.props.selectedRows.indexOf(rowIndex) !== -1;
    return this.props.cellRenderer(cellData, selected, cellDataKey, rowData);
  };

  getLocals() {
    const {
      onRowClick,
      cellRenderer,
      onScrollStart,
      props: {
        rowHeight, headerHeight, footerHeight, rowGetter, rowsCount, groupHeaderHeight,
        selectionType,
        id, children, autoSizeColumns
      },
      state: { height, width, scrollToRow }
    } = this;

    const tabIndex = selectionType !== 'none' ? '0' : null;

    const numberOfColumns = [].concat(children).length;
    const footerDataGetter = () => true; /*because https://github.com/facebook/fixed-data-table/issues/172*/

    const columnWidth = autoSizeColumns ? { width: width/numberOfColumns } : {};
    const columnProps = { ...columnWidth, cellRenderer, headerClassName: 'fixed-data-tablew-wrapper-header' };
    const addProps = column => React.cloneElement(column, { ...columnProps });
    const columns = numberOfColumns === 1 ? addProps(children) : [].concat(children).map(addProps);

    const style = {
      ...this.props.style,
      position: 'absolute'
    };

    const className = cx('fixed-data-table-wrapper', this.props.className, { selectable: selectionType !== 'none' });

    return {
      wrapperProps: { id, style, grow: true, className, tabIndex, ref: 'wrapper', width: '100%', height: '100%' },
      tableProps: {
        width: width + 2, height: height + 2, // as long as FDT counts the borders
        scrollToRow, onRowClick, onScrollStart,
        rowHeight, headerHeight, footerHeight, rowGetter, rowsCount, footerDataGetter, groupHeaderHeight
      },
      columns
    };
  }

  template({ wrapperProps, tableProps, columns }) {
    return (
      <FlexView {...wrapperProps}>
        <FixedDataTable {...tableProps}>
          {columns}
        </FixedDataTable>
      </FlexView>
    );
  }
}
<|endoftext|>


<commit_before>/* Cypress.env('key') calls can be used here
Export a config object for testing */
module.exports = {
    url: Cypress.env('TEST_URL') || 'https://a-ci.labshare.org'
};
<commit_msg>test: update cypress config<commit_after>/* Cypress.env('key') calls can be used here
Export a config object for testing */
module.exports = {
    url: Cypress.env('TEST_URL') || 'http://localhost:4200'
};
<|endoftext|>


<commit_before>import { downloadVideo } from '../downloader/downloader';

const path = require('path');
const prettyBytes = require('pretty-bytes');
const { shell } = require('electron');

let $datatable;
let $tableParent;

export var init = function () {
  const $table = $('table');

  $tableParent = $table.parent();
  $tableParent.hide();

  $datatable = $('table').DataTable({
    info: false,
    paging: false,
    autoWidth: false,
  });

  $('.dataTables_scrollBody').css('max-height', 200);
};

export var downloadVideoAndAddRowToTable = function (link, onError, onVideoAddedToTable) {
  let $tr;

  downloadVideo(link, onStartDownloading, onProgress, onError, onEnd);

  function onStartDownloading(info) {
    $tr = $(videoToHTML(info));

    $datatable.row.add($tr).draw(false);
    $tableParent.show();

    onVideoAddedToTable();
  }

  function onProgress(percent) {
    $tr.find('td.status').text(Math.round(percent) + '%');

    $tr.css('background-size', percent + '% 1px');
  }

  function onEnd(destinationFilePath) {
    $tr.addClass('done');

    const $button = getShowItemInFolderButton(destinationFilePath);
    $tr.find('td.actions').html($button);
  }
};

function videoToHTML(video) {
    return '
' +
            '' + video.title + '	' +
            '' + video.uploader + '	' +
            '' + video.duration + '	' +
            '' + prettyBytes(video.size) + '	' +
            '0%	' +
            '' +
                '<button title="(Cancel the download and) delete this file" class="btn btn-danger btn-sm">' +
                    '' +
                '</button>' +
            '	' +
        '
';
}

function getShowItemInFolderButton(destinationFilePath) {
    return $('<button title="Open the folder containing this file" class="btn btn-secondary btn-sm">' +
          '' +
      '</button>').on('click', () => shell.showItemInFolder(destinationFilePath))
}
<commit_msg>Remove scroll body resize<commit_after>import { downloadVideo } from '../downloader/downloader';

const path = require('path');
const prettyBytes = require('pretty-bytes');
const { shell } = require('electron');

let $datatable;
let $tableParent;

export var init = function () {
  const $table = $('table');

  $tableParent = $table.parent();
  $tableParent.hide();

  $datatable = $('table').DataTable({
    info: false,
    paging: false,
    autoWidth: false,
  });
};

export var downloadVideoAndAddRowToTable = function (link, onError, onVideoAddedToTable) {
  let $tr;

  downloadVideo(link, onStartDownloading, onProgress, onError, onEnd);

  function onStartDownloading(info) {
    $tr = $(videoToHTML(info));

    $datatable.row.add($tr).draw(false);
    $tableParent.show();

    onVideoAddedToTable();
  }

  function onProgress(percent) {
    $tr.find('td.status').text(Math.round(percent) + '%');

    $tr.css('background-size', percent + '% 1px');
  }

  function onEnd(destinationFilePath) {
    $tr.addClass('done');

    const $button = getShowItemInFolderButton(destinationFilePath);
    $tr.find('td.actions').html($button);
  }
};

function videoToHTML(video) {
    return '
' +
            '' + video.title + '	' +
            '' + video.uploader + '	' +
            '' + video.duration + '	' +
            '' + prettyBytes(video.size) + '	' +
            '0%	' +
            '' +
                '<button title="(Cancel the download and) delete this file" class="btn btn-danger btn-sm">' +
                    '' +
                '</button>' +
            '	' +
        '
';
}

function getShowItemInFolderButton(destinationFilePath) {
    return $('<button title="Open the folder containing this file" class="btn btn-secondary btn-sm">' +
          '' +
      '</button>').on('click', () => shell.showItemInFolder(destinationFilePath))
}
<|endoftext|>