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

/**
 * 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);
        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);
    });
    $('#zoominbtn').on('click', function() {
        zoom(1, 10);
    });

    //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);
        }
    });

    //Add the slider
    var slider = $('#minimap').find('.slider');
    $(slider)
        .draggable({
            containment: "parent",
            drag: 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').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) {
    var ratio = $('#zoomWindow').width() / Object.keys(nodes).length;
    var left = nodes[Object.keys(nodes)[0]].x - 1;

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

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

    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) {
            //Todo: Matthijs, alles hieronder uncommenten.

            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
 */
function zoom(direction, zoomAmount) {
    var slider = $('.slider', currentHover);
    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 = currentMousePos.x - minimap.position().left;
        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 slider = $('#minimap .slider');
        var totalWidth = $('#minimap').width();
        var sliderLeft = pxToInt(slider.css('left'));
        var minimapNodeSize = Object.keys(minimapNodes).length;
        var left = Math.floor(sliderLeft / totalWidth * minimapNodeSize);
        var width = Math.floor(slider.width() / totalWidth * minimapNodeSize);
        var zoom = Math.round(totalWidth / slider.width());
        var boundingBox = computeBoundingBox(left, width, zoom);
        getNodes(boundingBox, drawZoom);
    }
}

// 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 = {};

    var left = nodes[0].id;
    var ratio = $('#minimap').width() / (nodes[nodes.length-1].x - left);

    $.each(nodes, function(key, value) {
        result[value.id] = {
            x: value.x - left,
            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);
}

/**
 * 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);
        screenResize();
    });
}<commit_msg>Removed unneeded comment<commit_after>
var animationSpeed = 1000;
var currentHover = null;
var zoomTimeout = null;
var url = 'http://localhost:9998/';
var ratio = 0;
var minHeight = 300;
var yZoom = 1;
var screenWidth = $(window).width();
var screenHeight = $(window).height();
var screenResizeTimeout, treeRedrawTimeout;
var zoomWidth = 100;
var minimapNodes;
var currentMousePos = { x: -1, y: -1 };
var counter = 1;

/**
 * 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);
        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);
    });
    $('#zoominbtn').on('click', function() {
        zoom(1, 10);
    });

    //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);
        }
    });

    //Add the slider
    var slider = $('#minimap').find('.slider');
    $(slider)
        .draggable({
            containment: "parent",
            drag: 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').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) {
    var ratio = $('#zoomWindow').width() / Object.keys(nodes).length;
    var left = nodes[Object.keys(nodes)[0]].x - 1;

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

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

    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
 */
function zoom(direction, zoomAmount) {
    var slider = $('.slider', currentHover);
    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 = currentMousePos.x - minimap.position().left;
        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 slider = $('#minimap .slider');
        var totalWidth = $('#minimap').width();
        var sliderLeft = pxToInt(slider.css('left'));
        var minimapNodeSize = Object.keys(minimapNodes).length;
        var left = Math.floor(sliderLeft / totalWidth * minimapNodeSize);
        var width = Math.floor(slider.width() / totalWidth * minimapNodeSize);
        var zoom = Math.round(totalWidth / slider.width());
        var boundingBox = computeBoundingBox(left, width, zoom);
        getNodes(boundingBox, drawZoom);
    }
}

// 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 = {};

    var left = nodes[0].id;
    var ratio = $('#minimap').width() / (nodes[nodes.length-1].x - left);

    $.each(nodes, function(key, value) {
        result[value.id] = {
            x: value.x - left,
            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);
}

/**
 * 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);
        screenResize();
    });
}<|endoftext|>


<commit_before>import Base from './base';
import EventedMixin from '../mixin/evented';
import EpubCfi from '../lib/epubcfi';
import { debounce } from '../tools';

const COLUMN_GAP = 20;

const epubCfi = new EpubCfi();

export default class Page extends EventedMixin(Base) {

  constructor(element) {
    super(...arguments);

    this._frame = createFrame();

    this._element.classList.add('page');
    this._element.appendChild(this._frame);

    window.addEventListener('resize', debounce(() => {
      fitContent.call(this, this._frame);
    }, 100), false);
  }

  display(book, cfi = null) {
    super.display(book);
    if (cfi) {
      displaySpineFromCfi.call(this, cfi);
      return;
    }
    displaySpine.call(this, 0);
  }

  previous() {
    if (this._frame.contentWindow.scrollX <= 0) {
      previousSpine.call(this, 100);
      return;
    }
    this._frame.contentWindow.scrollBy(-1 * (this._contentHtml.clientWidth + COLUMN_GAP), 0);
    this._position = computePosition(this._currentSpineItemCfi, this._frame);
  }

  next() {
    if (this._frame.contentWindow.scrollX + this._contentHtml.clientWidth >= this._contentHtml.scrollWidth) {
      nextSpine.call(this);
      return;
    }
    this._frame.contentWindow.scrollBy(this._contentHtml.clientWidth + COLUMN_GAP, 0);
    this._position = computePosition(this._currentSpineItemCfi, this._frame);
  }
}

/**
 *
 * @returns {Element}
 */
function createFrame() {
  const frame = document.createElement('iframe');
  frame.id = 'beer-epub-frame';

  return frame;
}

/**
 *
 * @param spineItemIndex
 * @param position
 */
function displaySpine(spineItemIndex, position = 0) {
  this._currentSpineItemIndex = spineItemIndex;
  this._position = position;

  const spineItem = this._book.getSpineItem(spineItemIndex);
  this._currentSpineItemCfi = spineItem.cfi;

  if (!spineItem) {
    return Promise.resolve();
  }

  return loadFrame.call(this, spineItem.href).then(frame => {
    this._contentHtml = frame.contentDocument.querySelector('html');
    this._frame.contentWindow.scrollBy(Math.round(this._contentHtml.scrollWidth * position / 100), 0);
    frame.style['opacity'] = '1';

    computeCfi(this._currentSpineItemCfi, this._contentHtml);
  });
}

/**
 *
 * @param cfi
 */
function displaySpineFromCfi(cfi) {
  const cfiObject = epubCfi.parse(cfi);

  this._currentSpineItemIndex = cfiObject.spinePos;

  const spineItem = this._book.getSpineItem(this._currentSpineItemIndex);
  this._currentSpineItemCfi = spineItem.cfi;

  if (!spineItem) {
    return Promise.resolve();
  }

  return loadFrame.call(this, spineItem.href).then(frame => {
    this._contentHtml = frame.contentDocument.querySelector('html');

    const htmlQuery = epubCfi.generateQueryFromSteps(cfiObject.steps);
    const elementToDisplay = this._contentHtml.querySelector(htmlQuery);
    const position = 100 * elementToDisplay.getClientRects()[0].left / this._contentHtml.scrollWidth;

    this._frame.contentWindow.scrollBy(Math.round(this._contentHtml.scrollWidth * position / 100), 0);
    frame.style['opacity'] = '1';

    computeCfi(this._currentSpineItemCfi, this._contentHtml);
  });
}

/**
 * @param href The relative URL to a .html file inside the epub
 */
function loadFrame(href) {
  return new Promise(resolve => {
    this._frame.style['opacity'] = '0';
    this._frame.setAttribute('src', `___/${this._book.hash}/${href}`);

    const self = this;

    function frameOnLoad() {
      self.trigger('load', self._frame.contentDocument);
      self._frame.removeEventListener('load', frameOnLoad, true);

      fitContent.call(self, self._frame);

      resolve(self._frame);
    }

    this._frame.addEventListener('load', frameOnLoad, true);
  });
}

/**
 * @param frame
 */
function fitContent(frame) {
  const html = frame.contentDocument.querySelector('html');
  html.style['column-count'] = '2';
  html.style['column-gap'] = `${COLUMN_GAP}px`;
  html.style['break-inside'] = 'avoid';
  html.style['height'] = `${frame.clientHeight}px`;
  html.style['overflow'] = 'hidden';

  // position is not quite good yet
  const rawScrollLeft = html.scrollWidth * this._position / 100;
  frame.contentWindow.scrollTo(rawScrollLeft - (rawScrollLeft % (html.clientWidth + COLUMN_GAP)), 0);
}

/**
 *
 */
function previousSpine(position = 0) {
  if (this._currentSpineItemIndex > 0) {
    this._currentSpineItemIndex -= 1;
    displaySpine.call(this, this._currentSpineItemIndex, position);
  }
}

/**
 *
 */
function nextSpine(position = 0) {
  if (this._currentSpineItemIndex < this._book.spineItemsCount - 1) {
    this._currentSpineItemIndex += 1;
    displaySpine.call(this, this._currentSpineItemIndex, position);
  }
}

function computePosition(cfiBase, frame) {
  const displayHtml = frame.contentDocument.querySelector('html');
  const cfi = computeCfi(cfiBase, displayHtml);
  console.debug(epubCfi.parse(cfi));
  return 100 * (frame.contentDocument.body.scrollLeft + displayHtml.clientWidth) / (displayHtml.scrollWidth + displayHtml.clientWidth);
}

function computeCfi(cfiBase, content) {
  const firstVisibleElement = Array.from(content.querySelectorAll('body > *')).reduce((first, current) => {
    if (first) return first;
    if (current.getClientRects()[0].right > 0) return current;
  }, false);

  return epubCfi.generateCfiFromElement(firstVisibleElement, cfiBase);
}
<commit_msg>Use contentWindow.scroolX instead of scrollLeft<commit_after>import Base from './base';
import EventedMixin from '../mixin/evented';
import EpubCfi from '../lib/epubcfi';
import { debounce } from '../tools';

const COLUMN_GAP = 20;

const epubCfi = new EpubCfi();

export default class Page extends EventedMixin(Base) {

  constructor(element) {
    super(...arguments);

    this._frame = createFrame();

    this._element.classList.add('page');
    this._element.appendChild(this._frame);

    window.addEventListener('resize', debounce(() => {
      fitContent.call(this, this._frame);
    }, 100), false);
  }

  display(book, cfi = null) {
    super.display(book);
    if (cfi) {
      displaySpineFromCfi.call(this, cfi);
      return;
    }
    displaySpine.call(this, 0);
  }

  previous() {
    if (this._frame.contentWindow.scrollX <= 0) {
      previousSpine.call(this, 100);
      return;
    }
    this._frame.contentWindow.scrollBy(-1 * (this._contentHtml.clientWidth + COLUMN_GAP), 0);
    this._position = computePosition(this._currentSpineItemCfi, this._frame);
  }

  next() {
    if (this._frame.contentWindow.scrollX + this._contentHtml.clientWidth >= this._contentHtml.scrollWidth) {
      nextSpine.call(this);
      return;
    }
    this._frame.contentWindow.scrollBy(this._contentHtml.clientWidth + COLUMN_GAP, 0);
    this._position = computePosition(this._currentSpineItemCfi, this._frame);
  }
}

/**
 *
 * @returns {Element}
 */
function createFrame() {
  const frame = document.createElement('iframe');
  frame.id = 'beer-epub-frame';

  return frame;
}

/**
 *
 * @param spineItemIndex
 * @param position
 */
function displaySpine(spineItemIndex, position = 0) {
  this._currentSpineItemIndex = spineItemIndex;
  this._position = position;

  const spineItem = this._book.getSpineItem(spineItemIndex);
  this._currentSpineItemCfi = spineItem.cfi;

  if (!spineItem) {
    return Promise.resolve();
  }

  return loadFrame.call(this, spineItem.href).then(frame => {
    this._contentHtml = frame.contentDocument.querySelector('html');
    this._frame.contentWindow.scrollBy(Math.round(this._contentHtml.scrollWidth * position / 100), 0);
    frame.style['opacity'] = '1';

    computeCfi(this._currentSpineItemCfi, this._contentHtml);
  });
}

/**
 *
 * @param cfi
 */
function displaySpineFromCfi(cfi) {
  const cfiObject = epubCfi.parse(cfi);

  this._currentSpineItemIndex = cfiObject.spinePos;

  const spineItem = this._book.getSpineItem(this._currentSpineItemIndex);
  this._currentSpineItemCfi = spineItem.cfi;

  if (!spineItem) {
    return Promise.resolve();
  }

  return loadFrame.call(this, spineItem.href).then(frame => {
    this._contentHtml = frame.contentDocument.querySelector('html');

    const htmlQuery = epubCfi.generateQueryFromSteps(cfiObject.steps);
    const elementToDisplay = this._contentHtml.querySelector(htmlQuery);
    const position = 100 * elementToDisplay.getClientRects()[0].left / this._contentHtml.scrollWidth;

    this._frame.contentWindow.scrollBy(Math.round(this._contentHtml.scrollWidth * position / 100), 0);
    frame.style['opacity'] = '1';

    computeCfi(this._currentSpineItemCfi, this._contentHtml);
  });
}

/**
 * @param href The relative URL to a .html file inside the epub
 */
function loadFrame(href) {
  return new Promise(resolve => {
    this._frame.style['opacity'] = '0';
    this._frame.setAttribute('src', `___/${this._book.hash}/${href}`);

    const self = this;

    function frameOnLoad() {
      self.trigger('load', self._frame.contentDocument);
      self._frame.removeEventListener('load', frameOnLoad, true);

      fitContent.call(self, self._frame);

      resolve(self._frame);
    }

    this._frame.addEventListener('load', frameOnLoad, true);
  });
}

/**
 * @param frame
 */
function fitContent(frame) {
  const html = frame.contentDocument.querySelector('html');
  html.style['column-count'] = '2';
  html.style['column-gap'] = `${COLUMN_GAP}px`;
  html.style['break-inside'] = 'avoid';
  html.style['height'] = `${frame.clientHeight}px`;
  html.style['overflow'] = 'hidden';

  // position is not quite good yet
  const rawScrollLeft = html.scrollWidth * this._position / 100;
  frame.contentWindow.scrollTo(rawScrollLeft - (rawScrollLeft % (html.clientWidth + COLUMN_GAP)), 0);
}

/**
 *
 */
function previousSpine(position = 0) {
  if (this._currentSpineItemIndex > 0) {
    this._currentSpineItemIndex -= 1;
    displaySpine.call(this, this._currentSpineItemIndex, position);
  }
}

/**
 *
 */
function nextSpine(position = 0) {
  if (this._currentSpineItemIndex < this._book.spineItemsCount - 1) {
    this._currentSpineItemIndex += 1;
    displaySpine.call(this, this._currentSpineItemIndex, position);
  }
}

function computePosition(cfiBase, frame) {
  const displayHtml = frame.contentDocument.querySelector('html');
  const cfi = computeCfi(cfiBase, displayHtml);
  console.debug(epubCfi.parse(cfi));
  return 100 * frame.contentWindow.scrollX / displayHtml.scrollWidth;
}

function computeCfi(cfiBase, content) {
  const firstVisibleElement = Array.from(content.querySelectorAll('body > *')).reduce((first, current) => {
    if (first) return first;
    if (current.getClientRects()[0].right > 0) return current;
  }, false);

  return epubCfi.generateCfiFromElement(firstVisibleElement, cfiBase);
}
<|endoftext|>


<commit_before>"use strict";

var path = require("path"),
    gpfPath = path.resolve(__dirname, "../.."),
    useFileAccess = false,
    args = process.argv.slice(2),
    browser = args[0],
    version,
    errorCount,
    buildWebDriverFor = require("../../seleniumDriverFactory.js"),
    By = require("selenium-webdriver").By,
    until = require("selenium-webdriver").until,
    driver = buildWebDriverFor(browser);

if ("-release" === args[1]) {
    version = "?release";
} else if ("-debug" === args[1]) {
    version = "?debug";
} else {
    version = "";
}

if ("-file" === args[2]) {
    useFileAccess = true;
}

if (useFileAccess) {
    driver.get("file://" + gpfPath + "/test/host/web.html" + version);
} else {
    driver.get("http://localhost:8000/test/host/web.html" + version);
}

driver.wait(until.titleIs("GPF Tests - done"), 10000);
driver.findElements(By.id("status"))
    .then(function (elements) {
        return elements[0].getText();
    })
    .then(function (text) {
        console.log(text);
        if (0 === text.indexOf("OK")) {
            errorCount = 0;
        } else {
            // Search for failure: <number> <timespent>
            errorCount = parseInt(text.split("failure: ")[1].split(" ")[0], 10);
        }
        return driver.quit();
    })
    .then(function () {
        process.exit(errorCount);
    });
<commit_msg>Allows HTTP port configuration<commit_after>"use strict";

var path = require("path"),
    gpfPath = path.resolve(__dirname, "../.."),
    useFileAccess = false,
    httpPort = "8000",
    args = process.argv.slice(2),
    browser = args[0],
    version = "",
    errorCount,
    buildWebDriverFor = require("../../seleniumDriverFactory.js"),
    By = require("selenium-webdriver").By,
    until = require("selenium-webdriver").until,
    driver = buildWebDriverFor(browser);

args.slice(1).forEach(function (arg) {
    if ("-release" === arg) {
        version = "?release";
    } else if ("-debug" === arg) {
        version = "?debug";
    } else if ("-file" === arg) {
        useFileAccess = true;
    } else if (0 === arg.indexOf("-port:")) {
        httpPort = arg.substr(6);
    }
});

if (useFileAccess) {
    driver.get("file://" + gpfPath + "/test/host/web.html" + version);
} else {
    driver.get("http://localhost:" + httpPort + "/test/host/web.html" + version);
}

driver.wait(until.titleIs("GPF Tests - done"), 10000);
driver.findElements(By.id("status"))
    .then(function (elements) {
        return elements[0].getText();
    })
    .then(function (text) {
        console.log(text);
        if (0 === text.indexOf("OK")) {
            errorCount = 0;
        } else {
            // Search for failure: <number> <timespent>
            errorCount = parseInt(text.split("failure: ")[1].split(" ")[0], 10);
        }
        return driver.quit();
    })
    .then(function () {
        process.exit(errorCount);
    });
<|endoftext|>


<commit_before>(function() {
    'use strict';

    angular
        .module('<%=angularAppName%>')
        .filter('capitalize', capitalize);

    function capitalize() {
        return capitalizeFilter;

        function capitalizeFilter (input) {
            if (input !== null) {
                input = input.toLowerCase();
                input = input.substring(0, 1).toUpperCase() + input.substring(1)
            }
            return input;
        }
    }
})();
<commit_msg>Fix eslint error [ci skip]<commit_after>(function() {
    'use strict';

    angular
        .module('<%=angularAppName%>')
        .filter('capitalize', capitalize);

    function capitalize() {
        return capitalizeFilter;

        function capitalizeFilter (input) {
            if (input !== null) {
                input = input.toLowerCase();
                input = input.substring(0, 1).toUpperCase() + input.substring(1);
            }
            return input;
        }
    }
})();
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2017 Kagilum SAS.
 *
 * This file is part of iceScrum.
 *
 * iceScrum is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License.
 *
 * iceScrum is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with iceScrum.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *
 * Vincent Barrier (vbarrier@kagilum.com)
 * Nicolas Noullet (nnoullet@kagilum.com)
 *
 */

controllers.controller('projectChartWidgetCtrl', ['$scope', 'ProjectService', 'ReleaseService', 'SprintService', '$controller', '$element', function($scope, ProjectService, ReleaseService, SprintService, $controller, $element) {
    $controller('chartWidgetCtrl', {$scope: $scope, $element: $element});
    var widget = $scope.widget; // $scope.widget is inherited
    // Functions
    $scope.widgetReady = function(widget) {
        // Hack to force to keep sync values after drag & drop
        widget.width = widget.settings.width;
        widget.height = widget.settings.height;
        return !!(widget.settings && widget.settings.project && widget.settings.chart);
    };
    $scope.getTitle = function() {
        return $scope.widgetReady(widget) && $scope.holder.title ? widget.settings.project.pkey + ' - ' + $scope.holder.title : '';
    };
    $scope.getUrl = function() {
        return $scope.widgetReady(widget) ? 'p/' + widget.settings.project.pkey + '/#/' + widget.settings.chart.view : '';
    };
    $scope.refreshProjects = function(term) {
        if (widget.settings && widget.settings.project && !$scope.holder.projectResolved) {
            ProjectService.get(widget.settings.project.id).then(function(project) {
                $scope.holder.projectResolved = true;
                $scope.holder.project = project;
                //$scope.project = project; // Required for projectChartCtrl
            });
        }
        ProjectService.listByUser({term: term, paginate: true}).then(function(projectsAndCount) {
            $scope.projects = projectsAndCount.projects;
            if (!term && widget.settings && widget.settings.project && !_.find($scope.projects, {id: widget.settings.project.id})) {
                $scope.projects.unshift(widget.settings.project);
            }
        });
    };
    $scope.initChartTypeSelected = function(projectChartEntries) {
        if (!$scope.holder.chartResolved) {
            $scope.holder.chartResolved = true;
            if (widget.settings && widget.settings.chart) {
                $scope.holder.chart = _.find(projectChartEntries, widget.settings.chart);
            }
        }
    };
    $scope.projectChanged = function() { // Use callback instead of direct model linking to filter data before sending it
        widget.settings.project = _.pick($scope.holder.project, ['id', 'pkey']);
        //$scope.project = $scope.holder.project; // Required for projectChartCtrl
        widget.type = 'project';
        widget.typeId = $scope.holder.project.id;
    };
    $scope.chartChanged = function() { // Use callback instead of direct model linking to filter data before sending it
        widget.settings.chart = _.pick($scope.holder.chart, ['id', 'type', 'group', 'view']);
    };
    var addTitleAndCaption = function(data) {
        $scope.holder.title = data.options.title.text;
    };
    $scope.display = function(widget) {
        if ($scope.widgetReady(widget)) {
            var chartWidgetOptions = _.merge($scope.getChartWidgetOptions(widget), {
                chart: {
                    height: function($element) {
                        return $element ? $element.find('.panel-body')[0].getBoundingClientRect().height : 0;
                    }
                },
                title: {
                    enable: false
                },
                caption: {
                    enable: false
                }
            });
            switch (widget.settings.chart.type) {
                case 'release':
                    ReleaseService.getCurrentOrNextRelease(widget.settings.project).then(function(release) {
                        if (release && release.id) {
                            $scope.openChart('release', widget.settings.chart.id, release, chartWidgetOptions).then(addTitleAndCaption);
                        }
                    });
                    break;
                case 'sprint':
                    SprintService.getCurrentOrLastSprint(widget.settings.project).then(function(sprint) {
                        if (sprint && sprint.id) {
                            sprint.parentRelease.parentProject = widget.settings.project;
                            $scope.openChart('sprint', widget.settings.chart.id, sprint, chartWidgetOptions).then(addTitleAndCaption);
                        }
                    });
                    break;
                default:
                    $scope.openChart('project', widget.settings.chart.id, widget.settings.project, chartWidgetOptions).then(addTitleAndCaption);
                    break;
            }
        }
    };
    // Init
    if (!widget.settings) {
        widget.settings = {};
    }
    if (!widget.settings.height) {
        widget.settings.height = 2;
    }
    if (!widget.settings.width) {
        widget.settings.width = 2;
    }
    $scope.holder = {};
    $scope.projects = [];
    $scope.widths = [1, 2, 3];
    $scope.heights = [1, 2, 3];
}]);
<commit_msg>Fix again: allow using availability chart in chart widget<commit_after>/*
 * Copyright (c) 2017 Kagilum SAS.
 *
 * This file is part of iceScrum.
 *
 * iceScrum is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License.
 *
 * iceScrum is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with iceScrum.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *
 * Vincent Barrier (vbarrier@kagilum.com)
 * Nicolas Noullet (nnoullet@kagilum.com)
 *
 */

controllers.controller('projectChartWidgetCtrl', ['$scope', 'ProjectService', 'ReleaseService', 'SprintService', '$controller', '$element', function($scope, ProjectService, ReleaseService, SprintService, $controller, $element) {
    $controller('chartWidgetCtrl', {$scope: $scope, $element: $element});
    var widget = $scope.widget; // $scope.widget is inherited
    // Functions
    $scope.widgetReady = function(widget) {
        // Hack to force to keep sync values after drag & drop
        widget.width = widget.settings.width;
        widget.height = widget.settings.height;
        return !!(widget.settings && widget.settings.project && widget.settings.chart);
    };
    $scope.getTitle = function() {
        return $scope.widgetReady(widget) && $scope.holder.title ? widget.settings.project.pkey + ' - ' + $scope.holder.title : '';
    };
    $scope.getUrl = function() {
        return $scope.widgetReady(widget) ? 'p/' + widget.settings.project.pkey + '/#/' + widget.settings.chart.view : '';
    };
    $scope.refreshProjects = function(term) {
        if (widget.settings && widget.settings.project && !$scope.holder.projectResolved) {
            ProjectService.get(widget.settings.project.id).then(function(project) {
                $scope.holder.projectResolved = true;
                $scope.holder.project = project;
                $scope.project = project; // Required for projectChartCtrl
            });
        }
        ProjectService.listByUser({term: term, paginate: true}).then(function(projectsAndCount) {
            $scope.projects = projectsAndCount.projects;
            if (!term && widget.settings && widget.settings.project && !_.find($scope.projects, {id: widget.settings.project.id})) {
                $scope.projects.unshift(widget.settings.project);
            }
        });
    };
    $scope.initChartTypeSelected = function(projectChartEntries) {
        if (!$scope.holder.chartResolved) {
            $scope.holder.chartResolved = true;
            if (widget.settings && widget.settings.chart) {
                $scope.holder.chart = _.find(projectChartEntries, widget.settings.chart);
            }
        }
    };
    $scope.projectChanged = function() { // Use callback instead of direct model linking to filter data before sending it
        widget.settings.project = _.pick($scope.holder.project, ['id', 'pkey']);
        $scope.project = $scope.holder.project; // Required for projectChartCtrl
        widget.type = 'project';
        widget.typeId = $scope.holder.project.id;
    };
    $scope.chartChanged = function() { // Use callback instead of direct model linking to filter data before sending it
        widget.settings.chart = _.pick($scope.holder.chart, ['id', 'type', 'group', 'view']);
    };
    var addTitleAndCaption = function(data) {
        $scope.holder.title = data.options.title.text;
    };
    $scope.display = function(widget) {
        if ($scope.widgetReady(widget)) {
            var chartWidgetOptions = _.merge($scope.getChartWidgetOptions(widget), {
                chart: {
                    height: function($element) {
                        return $element ? $element.find('.panel-body')[0].getBoundingClientRect().height : 0;
                    }
                },
                title: {
                    enable: false
                },
                caption: {
                    enable: false
                }
            });
            switch (widget.settings.chart.type) {
                case 'release':
                    ReleaseService.getCurrentOrNextRelease(widget.settings.project).then(function(release) {
                        if (release && release.id) {
                            $scope.openChart('release', widget.settings.chart.id, release, chartWidgetOptions).then(addTitleAndCaption);
                        }
                    });
                    break;
                case 'sprint':
                    SprintService.getCurrentOrLastSprint(widget.settings.project).then(function(sprint) {
                        if (sprint && sprint.id) {
                            sprint.parentRelease.parentProject = widget.settings.project;
                            $scope.openChart('sprint', widget.settings.chart.id, sprint, chartWidgetOptions).then(addTitleAndCaption);
                        }
                    });
                    break;
                default:
                    $scope.openChart('project', widget.settings.chart.id, widget.settings.project, chartWidgetOptions).then(addTitleAndCaption);
                    break;
            }
        }
    };
    // Init
    if (!widget.settings) {
        widget.settings = {};
    }
    if (!widget.settings.height) {
        widget.settings.height = 2;
    }
    if (!widget.settings.width) {
        widget.settings.width = 2;
    }
    $scope.holder = {};
    $scope.projects = [];
    $scope.widths = [1, 2, 3];
    $scope.heights = [1, 2, 3];
}]);
<|endoftext|>


<commit_before>import React from 'react'

import './gxaGradient.css'

const LegendRow = ({lowValueColour, highValueColour, lowExpressionLevel, highExpressionLevel}) => {

  const spanStyle = {
    backgroundImage: `linear-gradient(to right, ${lowValueColour}, ${highValueColour})`
  };

  return (
    lowExpressionLevel || highExpressionLevel
      ? <div style={{display: `table-row`}}>
        <div className={`gxaGradientLevel gxaGradientLevelMin`}>{lowExpressionLevel}

        <div style={{display: `table-cell`}}>
          <span className={`gxaGradientColour`} style={spanStyle} />

        <div className={`gxaGradientLevel gxaGradientLevelMax`}>{highExpressionLevel}

      : null
  )
}

LegendRow.propTypes = {
  lowValueColour: PropTypes.string.isRequired,
  highValueColour: PropTypes.string.isRequired,
  lowExpressionLevel: PropTypes.element.isRequired,
  highExpressionLevel: PropTypes.element.isRequired
}

export default LegendRow
<commit_msg>Fix broken dependency<commit_after>import React from 'react'
import PropTypes from 'prop-types'

import './gxaGradient.css'

const LegendRow = ({lowValueColour, highValueColour, lowExpressionLevel, highExpressionLevel}) => {

  const spanStyle = {
    backgroundImage: `linear-gradient(to right, ${lowValueColour}, ${highValueColour})`
  };

  return (
    lowExpressionLevel || highExpressionLevel
      ? <div style={{display: `table-row`}}>
        <div className={`gxaGradientLevel gxaGradientLevelMin`}>{lowExpressionLevel}

        <div style={{display: `table-cell`}}>
          <span className={`gxaGradientColour`} style={spanStyle} />

        <div className={`gxaGradientLevel gxaGradientLevelMax`}>{highExpressionLevel}

      : null
  )
}

LegendRow.propTypes = {
  lowValueColour: PropTypes.string.isRequired,
  highValueColour: PropTypes.string.isRequired,
  lowExpressionLevel: PropTypes.element.isRequired,
  highExpressionLevel: PropTypes.element.isRequired
}

export default LegendRow
<|endoftext|>


<commit_before>/* global AuthorshipGroup, Document, DocumentOrigins, GruntTaskRunner, SqlService, Promise, Group */
'use strict';

/**
 * Group.js
 *
 * @description :: TODO: You might write a short summary of how this model works and what it represents here.
 * @docs        :: http://sailsjs.org/#!documentation/models
 */

const _ = require('lodash');
const ResearchEntity = require('../lib/ResearchEntity');

module.exports = _.merge({}, ResearchEntity, {
    DEFAULT_SORTING: {
        name: 'asc',
        updatedAt: 'desc'
    },
    searchKey: 'slug',
    attributes: {
        name: 'STRING',
        slug: {
            type: 'STRING'
        },
        shortname: 'TEXT',
        description: 'TEXT',
        publicationsAcronym: 'TEXT',
        type: 'STRING',
        collaborations: {
            collection: 'collaboration',
            via: 'group'
        },
        administrators: {
            collection: 'user',
            via: 'administratedGroups',
            through: 'groupadministrator'
        },
        members: {
            collection: 'User',
            via: 'memberships',
            through: 'membership'
        },
        memberships: {
            collection: 'membership',
            via: 'group'
        },
        drafts: {
            collection: 'Document',
            via: 'draftGroupCreator'
        },
        documents: {
            collection: 'document',
            via: 'groups',
            through: 'authorshipgroup'
        },
        publicDocuments: {
            collection: 'document',
            via: 'researchEntity',
            through: 'publicauthorshipgroup'
        },
        publications: {
            collection: 'Document',
            via: 'researchEntity',
            through: 'publicationgroup'
        },
        disseminationTalks: {
            collection: 'Document',
            via: 'researchEntity',
            through: 'disseminationtalkgroup'
        },
        scientificTalks: {
            collection: 'Document',
            via: 'researchEntity',
            through: 'scientifictalkgroup'
        },
        authorships: {
            collection: 'authorshipGroup',
            via: 'researchEntity',
        },
        discardedDocuments: {
            collection: 'Document',
            via: 'discardedGroups',
            through: 'discardedgroup'
        },
        suggestedDocuments: {
            collection: 'document',
            via: 'groups',
            through: 'documentsuggestiongroup'
        },
        externalDocuments: {
            collection: 'document',
            via: 'researchEntity',
            through: 'externaldocumentgroup'
        },
        getType: function () {
            return 'group';
        },
        scopusId: {
            type: 'STRING'
        },
        institute: {
            model: 'institute'
        },
        attributes: {
            collection: 'Attribute',
            through: 'groupattribute'
        }
    },
    getAuthorshipsData: async function (document, groupId, newAffiliationData = {}) {
        return {
            isVerifiable: true,
            document: document,
            synchronize: !_.isNil(newAffiliationData.synchronize) ? newAffiliationData.synchronize : document.synchronized,
            public: true
        };
    },
    doVerifyDocument: async function (document, researchEntityId, authorshipData) {
        const authorship = {
            researchEntity: researchEntityId,
            document: document.id,
            synchronize: authorshipData.synchronize,
            public: authorshipData.public,
        };
        await AuthorshipGroup.create(authorship);
        return document;
    },
    getDefaultGroup: function () {
        //TODO: id must be read from settings
        const defaultInstituteName = sails.config.scientilla.institute.name;
        return Group.findOneByName(defaultInstituteName)
            .populate('members')
            .populate('administrators');
    },
    addMember: function (group, user) {
        group.members.add(user);
        return group.savePromise();
    },
    addAdministrator: function (group, user) {
        if (group.administrators.length == 0)
            group.administrators.add(user);
        return group.savePromise();
    },
    addUserToDefaultGroup: function (user) {
        return Group.getDefaultGroup()
            .then(group => Group.addAdministrator(group, user))
            .then(group => Group.addMember(group, user))
            .then(() => user);
    },
    getAuthorshipModel: function () {
        return AuthorshipGroup;
    },
    updateProfile: async function(groupId, groupData){
        delete groupData.memberships;
        delete groupData.members;
        const oldResearchEntity = await Group.findOne({id: groupId});
        const res = await Group.update({id: groupId}, groupData);
        const newResearchEntity = res[0];

        const command = 'import:external:group:' + newResearchEntity.id;
        if (newResearchEntity.scopusId !== oldResearchEntity.scopusId)
            GruntTaskRunner.run(command + ':' + DocumentOrigins.SCOPUS);
        if (newResearchEntity.publicationsAcronym !== oldResearchEntity.publicationsAcronym)
            GruntTaskRunner.run(command + ':' + DocumentOrigins.PUBLICATIONS);

        return newResearchEntity;
    }
});
<commit_msg>Added 'CDR' and 'active' fields<commit_after>/* global AuthorshipGroup, Document, DocumentOrigins, GruntTaskRunner, SqlService, Promise, Group */
'use strict';

/**
 * Group.js
 *
 * @description :: TODO: You might write a short summary of how this model works and what it represents here.
 * @docs        :: http://sailsjs.org/#!documentation/models
 */

const _ = require('lodash');
const ResearchEntity = require('../lib/ResearchEntity');

module.exports = _.merge({}, ResearchEntity, {
    DEFAULT_SORTING: {
        name: 'asc',
        updatedAt: 'desc'
    },
    searchKey: 'slug',
    attributes: {
        name: 'STRING',
        slug: {
            type: 'STRING'
        },
        shortname: 'TEXT',
        description: 'TEXT',
        publicationsAcronym: 'TEXT',
        type: 'STRING',
        cdr: 'STRING',
        active: 'BOOLEAN',
        collaborations: {
            collection: 'collaboration',
            via: 'group'
        },
        administrators: {
            collection: 'user',
            via: 'administratedGroups',
            through: 'groupadministrator'
        },
        members: {
            collection: 'User',
            via: 'memberships',
            through: 'membership'
        },
        memberships: {
            collection: 'membership',
            via: 'group'
        },
        drafts: {
            collection: 'Document',
            via: 'draftGroupCreator'
        },
        documents: {
            collection: 'document',
            via: 'groups',
            through: 'authorshipgroup'
        },
        publicDocuments: {
            collection: 'document',
            via: 'researchEntity',
            through: 'publicauthorshipgroup'
        },
        publications: {
            collection: 'Document',
            via: 'researchEntity',
            through: 'publicationgroup'
        },
        disseminationTalks: {
            collection: 'Document',
            via: 'researchEntity',
            through: 'disseminationtalkgroup'
        },
        scientificTalks: {
            collection: 'Document',
            via: 'researchEntity',
            through: 'scientifictalkgroup'
        },
        authorships: {
            collection: 'authorshipGroup',
            via: 'researchEntity',
        },
        discardedDocuments: {
            collection: 'Document',
            via: 'discardedGroups',
            through: 'discardedgroup'
        },
        suggestedDocuments: {
            collection: 'document',
            via: 'groups',
            through: 'documentsuggestiongroup'
        },
        externalDocuments: {
            collection: 'document',
            via: 'researchEntity',
            through: 'externaldocumentgroup'
        },
        getType: function () {
            return 'group';
        },
        scopusId: {
            type: 'STRING'
        },
        institute: {
            model: 'institute'
        },
        attributes: {
            collection: 'Attribute',
            through: 'groupattribute'
        }
    },
    getAuthorshipsData: async function (document, groupId, newAffiliationData = {}) {
        return {
            isVerifiable: true,
            document: document,
            synchronize: !_.isNil(newAffiliationData.synchronize) ? newAffiliationData.synchronize : document.synchronized,
            public: true
        };
    },
    doVerifyDocument: async function (document, researchEntityId, authorshipData) {
        const authorship = {
            researchEntity: researchEntityId,
            document: document.id,
            synchronize: authorshipData.synchronize,
            public: authorshipData.public,
        };
        await AuthorshipGroup.create(authorship);
        return document;
    },
    getDefaultGroup: function () {
        //TODO: id must be read from settings
        const defaultInstituteName = sails.config.scientilla.institute.name;
        return Group.findOneByName(defaultInstituteName)
            .populate('members')
            .populate('administrators');
    },
    addMember: function (group, user) {
        group.members.add(user);
        return group.savePromise();
    },
    addAdministrator: function (group, user) {
        if (group.administrators.length == 0)
            group.administrators.add(user);
        return group.savePromise();
    },
    addUserToDefaultGroup: function (user) {
        return Group.getDefaultGroup()
            .then(group => Group.addAdministrator(group, user))
            .then(group => Group.addMember(group, user))
            .then(() => user);
    },
    getAuthorshipModel: function () {
        return AuthorshipGroup;
    },
    updateProfile: async function(groupId, groupData){
        delete groupData.memberships;
        delete groupData.members;
        const oldResearchEntity = await Group.findOne({id: groupId});
        const res = await Group.update({id: groupId}, groupData);
        const newResearchEntity = res[0];

        const command = 'import:external:group:' + newResearchEntity.id;
        if (newResearchEntity.scopusId !== oldResearchEntity.scopusId)
            GruntTaskRunner.run(command + ':' + DocumentOrigins.SCOPUS);
        if (newResearchEntity.publicationsAcronym !== oldResearchEntity.publicationsAcronym)
            GruntTaskRunner.run(command + ':' + DocumentOrigins.PUBLICATIONS);

        return newResearchEntity;
    }
});
<|endoftext|>


<commit_before>// Log.js - in api/services

"use strict";

const _ = require('lodash');
const fs = require('fs');
const path = require('path');
const {promisify} = require('util');
const readdirPromise = promisify(fs.readdir);
const moment = require('moment');
const readline = require('readline');
const stream = require('stream');
const Convert = require('ansi-to-html');
const baseFolder = path.join('logs');

moment.locale('en');

module.exports = {
    getTasks,
    read
};

function getTasks() {
    return new Promise(async (resolve, reject) => {
        await readdirPromise(baseFolder).then(fileNames => {
            let files = fileNames.filter(name => {
                return name !== '.gitkeep';
            });

            let tasks = [];

            files.map(name => {
                const fileInfo = name.split('_');
                const taskName = fileInfo.shift();
                const date = fileInfo.pop().replace('.log', '');

                const index = tasks.findIndex((task => task.taskName === taskName));
                if (index === -1) {
                    tasks.push({
                        taskName: taskName,
                        dates: [date]
                    });
                } else {
                    tasks[index].dates.push(date);

                    tasks[index].dates = _.orderBy(tasks[index].dates, date => {
                        return new moment(date).format('YYYMMDD');
                    }, ['desc']);
                }
            });

            resolve(tasks);
        }).catch(err => {
            reject(err);
        });
    }).then(tasks => {
        return {
            type: 'success',
            tasks: tasks
        };
    }).catch(err => {
        return {
            type: 'failed',
            message: err
        };
    });
}

function read(req) {
    return new Promise(async (resolve, reject) => {
        const taskName = req.params.taskName;
        const date = req.params.date;

        await readdirPromise(baseFolder).then(async fileNames => {
            let files = fileNames.filter(name => {
                return name !== '.gitkeep';
            });

            files = files.map(name => {
                const fileInfo = name.split('_');
                return {
                    fileName: name,
                    taskName: fileInfo.shift(),
                    date: fileInfo.pop().replace('.log', '')
                };
            });

            files = files.filter(file => {
                return file.taskName === taskName;
            });

            files = _.orderBy(files, file => {
                return new moment(file.date).format('YYYMMDD');
            }, ['desc']);

            if (date) {
                files = files.filter(file => {
                    return file.date === date;
                });
            }
            const file = _.head(files);
            const filePath = path.join(baseFolder, file.fileName);
            const instream = fs.createReadStream(filePath);
            const outstream = new (stream)();
            const rl = readline.createInterface(instream, outstream);
            const output = [];

            rl.on('line', line => {
                const convert = new Convert();
                const lineHtml = convert.toHtml(line)
                    .replace(/style="color:#A00"/g, 'class="red"')
                    .replace(/style="color:#FFF"/g, 'class="white"')
                    .replace(/style="color:#0A0"/g, 'class="green"');

                output.push(lineHtml);
            });

            rl.on('close', () => {
                resolve(output.join('
'));
            });
        }).catch(err => {
            reject(err);
        });
    }).then(logs => {
        return {
            type: 'success',
            logs: logs
        };
    }).catch(err => {
        return {
            type: 'failed',
            message: err
        };
    });
}<commit_msg>Add regular expression to test log files<commit_after>// Log.js - in api/services

"use strict";

const _ = require('lodash');
const fs = require('fs');
const path = require('path');
const {promisify} = require('util');
const readdirPromise = promisify(fs.readdir);
const moment = require('moment');
const readline = require('readline');
const stream = require('stream');
const Convert = require('ansi-to-html');
const baseFolder = path.join('logs');

moment.locale('en');

module.exports = {
    getTasks,
    read
};

function getTasks() {
    return new Promise(async (resolve, reject) => {
        await readdirPromise(baseFolder).then(fileNames => {
            let files = fileNames.filter(name => {
                return name !== '.gitkeep';
            });

            let tasks = [];

            files.map(name => {
                if (name.match(/_[0-9]{8}.log/g)) {
                    const fileInfo = name.split('_');
                    const taskName = fileInfo.shift();
                    const date = fileInfo.pop().replace('.log', '');

                    const index = tasks.findIndex((task => task.taskName === taskName));
                    if (index === -1) {
                        tasks.push({
                            taskName: taskName,
                            dates: [date]
                        });
                    } else {
                        tasks[index].dates.push(date);

                        tasks[index].dates = _.orderBy(tasks[index].dates, date => {
                            return new moment(date).format('YYYMMDD');
                        }, ['desc']);
                    }
                }
            });

            resolve(tasks);
        }).catch(err => {
            reject(err);
        });
    }).then(tasks => {
        return {
            type: 'success',
            tasks: tasks
        };
    }).catch(err => {
        return {
            type: 'failed',
            message: err
        };
    });
}

function read(req) {
    return new Promise(async (resolve, reject) => {
        const taskName = req.params.taskName;
        const date = req.params.date;

        await readdirPromise(baseFolder).then(async fileNames => {
            let files = fileNames.filter(name => {
                return name !== '.gitkeep';
            });

            files = files.map(name => {
                const fileInfo = name.split('_');
                return {
                    fileName: name,
                    taskName: fileInfo.shift(),
                    date: fileInfo.pop().replace('.log', '')
                };
            });

            files = files.filter(file => {
                return file.taskName === taskName;
            });

            files = _.orderBy(files, file => {
                return new moment(file.date).format('YYYMMDD');
            }, ['desc']);

            if (date) {
                files = files.filter(file => {
                    return file.date === date;
                });
            }
            const file = _.head(files);
            const filePath = path.join(baseFolder, file.fileName);
            const instream = fs.createReadStream(filePath);
            const outstream = new (stream)();
            const rl = readline.createInterface(instream, outstream);
            const output = [];

            rl.on('line', line => {
                const convert = new Convert();
                const lineHtml = convert.toHtml(line)
                    .replace(/style="color:#A00"/g, 'class="red"')
                    .replace(/style="color:#FFF"/g, 'class="white"')
                    .replace(/style="color:#0A0"/g, 'class="green"');

                output.push(lineHtml);
            });

            rl.on('close', () => {
                resolve(output.join('
'));
            });
        }).catch(err => {
            reject(err);
        });
    }).then(logs => {
        return {
            type: 'success',
            logs: logs
        };
    }).catch(err => {
        return {
            type: 'failed',
            message: err
        };
    });
}<|endoftext|>


<commit_before>var express = require('express'),
	bodyParser = require('body-parser'),
    authenticate = require('azure-mobile-apps/src/express/middleware/authenticate'),
    authorize = require('azure-mobile-apps/src/express/middleware/authorize');

var sql = require('mssql');
var util = require('../util');

/* { 'SQL types': {
	'String': 'sql.NVarChar', 'Number': 'sql.Int', 'Boolean': 'sql.Bit', 'Date': 'sql.DateTime', 'Buffer': 'sql.VarBinary', 'sql.Table': 'sql.TVP'
} } */
module.exports = function (configuration) {
    var router = express.Router();

    router.get('/', (req, res, next) => {
		console.log('in GET /api/tertulias');
		var route = '/tertulias';
	    sql.connect(util.sqlConfiguration)
	    .then(function() {
			new sql.Request()
	    	.input('sid', sql.NVarChar(40), req.azureMobile.user.id)
	    	.query('SELECT' +
					' tr_id         AS id,' +
					' tr_name       AS name,' +
					' tr_subject    AS subject,' +
					' ev_targetdate AS nextEventDate,' +
					' lo_name       AS nextEventLocation,' +
					' no_count      AS messages,' +
					' nv_name       AS role ' +
				' FROM Tertulias' +
					' INNER JOIN Members ON mb_tertulia = tr_id' +
					' INNER JOIN Enumvalues ON mb_role = nv_id' +
					' INNER JOIN Users ON mb_user = us_id' +
					' LEFT JOIN' +
						' (SELECT * FROM' +
							' (SELECT RANK() OVER(PARTITION BY ev_tertulia ORDER BY ev_targetdate DESC) AS "rank", * FROM Events' +
						' INNER JOIN Locations on ev_location = lo_id WHERE ev_targetdate > GETDATE()) AS a WHERE a.rank = 1) AS b' +
							' ON ev_tertulia = tr_id' +
						' LEFT JOIN (SELECT no_tertulia, count(*) AS no_count FROM Notifications WHERE no_id NOT IN' +
							' (SELECT no_id FROM Notifications INNER JOIN Readnotifications ON rn_notification = no_id' +
						' INNER JOIN Users ON rn_user = us_id WHERE us_sid = @sid)' +
				' GROUP BY no_tertulia) AS c ON no_tertulia = tr_id' +
				' WHERE tr_is_cancelled = 0 AND us_sid = @sid')
	    	.then(function(recordset) {
	    		console.log(recordset);
			    var links = '[ ' +
						'{ "rel": "self", "method": "GET", "href": "' + route + '" }, ' +
						'{ "rel": "create", "method": "POST", "href": "' + route + '" }, ' +
						'{ "rel": "searchPublic", "method": "GET", "href": "' + route + '/publicsearch" } ' +
					']';
			    var itemLinks = '[ ' +
						'{ "rel": "self", "method": "GET", "href": "' + route + '/:id" }, ' +
						'{ "rel": "update", "method": "PATCH", "href": "' + route + '/:id" }, ' +
						'{ "rel": "delete", "method": "DELETE", "href": "' + route + '/:id" }, ' +
						'{ "rel": "unsubscribe", "method": "DELETE", "href": "' + route + '/:id/unsubscribe" } ' +
						'{ "rel": "members", "method": "GET", "href": "' + route + '/:id/members" } ' +
						'{ "rel": "event", "method": "POST", "href": "' + route + '/:id/event" } ' +
					']';
				res.type('application/json');
                recordset.forEach(function(elem) {
                	elem['links'] = JSON.parse(itemLinks.replace(/:id/g, elem.id));
        		});
                var results = {};
            	results['tertulias'] = recordset;
                results['links'] = JSON.parse(links);
                console.log(results);
                res.json(results);
                res.sendStatus(200);
                return next();
	    	})
	    });
    });

    router.get('/publicSearch', (req, res, next) => {
		console.log('in GET /api/tertulias/publicsearch');
		var route = '/tertulias';
		var point = 'POINT(' + req.query.latitude + ' ' + req.query.longitude + ')';
	    sql.connect(util.sqlConfiguration)
	    .then(function() {
			new sql.Request()
	    	.input('sid', sql.NVarChar(40), req.azureMobile.user.id)
	    	.input('query', sql.NVarChar, '%' + req.query.query + '%')
	    	.input('latitude', sql.NVarChar, req.query.latitude)
	    	.input('longitude', sql.NVarChar, req.query.longitude)
	    	.input('point', sql.NVarChar, point)
	    	.query('SELECT TOP 25' +
		    		' tr_id AS id,' +
		    		' tr_name AS name,' +
		    		' tr_subject AS subject,' +
		    		' lo_name AS location' +
	    		' FROM Tertulias' +
	    			' INNER JOIN Locations ON tr_location = lo_id' +
	    		' WHERE (tr_name LIKE @query OR tr_subject LIKE @query OR lo_name LIKE @query)' +
	    			' AND tr_is_cancelled = 0 AND tr_is_private = 0' +
		    		' AND tr_id NOT IN' +
		    			' (SELECT mb_tertulia FROM Tertulias' +
		    			' INNER JOIN Members ON mb_tertulia = tr_id' +
		    			' INNER JOIN Users ON mb_user = us_id WHERE us_sid = @sid)' +
				' ORDER BY lo_geography.STDistance( @point )')
				// ' ORDER BY lo_geography.STDistance(\'POINT( @latitude @longitude )\')')
				// ' ORDER BY lo_geography.STDistance(\'POINT( 38.11 -9.1123113 )\')')
	    	.then(function(recordset) {
                var links = '[ { "rel": "self", "method": "GET", "href": "' + route + '/publicSearch" } ]';
                var itemLinks = '[ ' +
            	    	'{ "rel": "self", "method": "GET", "href": "' + route + '/:id" }, ' +
						'{ "rel": "subscribe", "method": "POST", "href": "' + route + '/:id/subscribe" }' +
					']';
                res.type('application/json');
                recordset.forEach(function(elem) {
                	elem['links'] = JSON.parse(itemLinks.replace(/:id/g, elem.id));
        		});
                var results = {};
            	results['tertulias'] = recordset;
                results['links'] = JSON.parse(links);
                res.json(results);
                res.sendStatus(200);
                return next();
            })
	    });
	});

	router.get('/:tr_id', (req, res, next) => {
		console.log('in GET /api/tertulias/:tr_id');
		var tr_id = req.params.tr_id;
		var route = '/tertulias/' + tr_id;
		if (isNaN(tr_id))
			return next();

	    sql.connect(util.sqlConfiguration)
	    .then(function() {
			new sql.Request()
			.input('tertulia', sql.Int, tr_id)
			.input('sid', sql.NVarChar(40), req.azureMobile.user.id)
			.query('SELECT' +
					' tr_id AS id,' + // Tertulia
					' tr_name AS name,' +
					' tr_subject AS subject,' +
					' lo_name AS location,' + // Location
					' lo_address AS address,' +
					' lo_zip AS zip,' +
					' lo_city AS city,' +
					' lo_country AS country,' +
					' lo_latitude AS latitude,' +
					' lo_longitude AS longitude,' +
					' sc_type AS scheduleId,' + // Schedule
					' _Schedule.nv_name AS scheduleName,' +
					' _Schedule.nv_description AS scheduleDescription,' +
					' tr_is_private AS private, ' +  // Private
					' _Member.nv_name AS role' + // Role
				' FROM Tertulias' +
					' INNER JOIN Locations ON tr_location = lo_id' +
					' INNER JOIN Schedules ON tr_schedule = sc_id' +
					' LEFT JOIN Members ON mb_tertulia = tr_id' +
					' LEFT JOIN Users ON mb_user = us_id' +
					' LEFT JOIN EnumValues AS _Member ON mb_role = _Member.nv_id' +
					' INNER JOIN EnumValues AS _Schedule ON sc_type = _Schedule.nv_id' +
				' WHERE tr_is_cancelled = 0 AND (us_sid = @sid OR tr_is_private = 0)' +
					' AND tr_id = @tertulia')
			.then(function(recordset) {
				var links = '[ ' +
						'{ "rel": "self", "method": "GET", "href": "' + route + '" }, ' +
						'{ "rel": "update", "method": "PATCH", "href": "' + route + '" }, ' +
						'{ "rel": "delete", "method": "DELETE", "href": "' + route + '" }, ' +
						'{ "rel": "subscribe", "method": "POST", "href": "' + route + '/subscribe" }, ' +
						'{ "rel": "unsubscribe", "method": "DELETE", "href": "' + route + '/unsubscribe" } ' +
					']';
                res.type('application/json');
                var results = {};
            	results['tertulia'] = recordset[0];
                results['links'] = JSON.parse(links);
                res.json(results);
                res.sendStatus(200);
                return next();
			})
		});
	});

	router.post('/', (req, res, next) => {
		console.log('in POST /api/tertulias');
	    sql.connect(util.sqlConfiguration)
	    .then(function() {
			new sql.Request()
			.input('name', sql.NVarChar(40), req.body.name)
			.input('subject', sql.NVarChar(80), req.body.subject)
			.input('locationName', sql.NVarChar(40), req.body.location)
			.input('locationAddress', sql.NVarChar(80), req.body.address)
			.input('locationZip', sql.NVarChar(40), req.body.zip)
			.input('locationCity', sql.NVarChar(40), req.body.city)
			.input('locationCountry', sql.NVarChar(40), req.body.country)
			.input('locationLatitude', sql.NVarChar(12), req.body.latitude)
			.input('locationLongitude', sql.NVarChar(12), req.body.longitude)
			.input('weekDay', sql.NVarChar(20), 'Tuesday')
			.input('weekNr', sql.Int, 1)
			.input('fromStart', sql.BIT, 1)
			.input('skip', sql.Int, 0)
			.input('isPrivate', sql.Int, req.body.isPrivate ? 1 : 0)
			.input('userSid', sql.NVarChar(40), req.azureMobile.user.id)
			.execute('sp_insertTertulia_MonthlyW_sid')
			.then((recordsets) => {
				if (recordsets.length == 0) {
					res.status(201)	// 201: Created
						.type('application/json')
						.json( { result: 'Ok' } );
					return next();
				} else {
					res.status(409)	// 409: Conflict, 422: Unprocessable Entity (WebDAV; RFC 4918)
						.type('application/json')
						.json( { result: 'Duplicate' } );
					return next('409');
				}
				next();
			})
			.catch(function(err) {
				next(err);
			});
		})
		.catch(function(err) {
			return next(err);
		});
	});

	router.post('/:tr_id/subscribe', (req, res, next) => {
		console.log('in POST /api/tertulias/:tr_id/subscribe');
	    sql.connect(util.sqlConfiguration)
	    .then(function() {
			new sql.Request()
			.input('sid', sql.NVarChar(40), req.azureMobile.user.id)
			.input('tertulia', sql.Int, req.params.tr_id)
			.execute('spSubscribe')
			.then((recordset) => {
				if (recordset.returnValue = 1) {
					res.sendStatus(200);
				} else {
					res.sendStatus(409);
				}
				return next();
			})
			.catch(function(err) {
				next(err);
			});
		})
		.catch(function(err) {
			return next(err);
		});
	});

	var completeError = function(err, res) {
	    if (err) {
	    	console.log("Error:");
	        console.error(err);
	        if (res) res.sendStatus(500);
	    }
	};

	var goGet = function(req, res, next) {
		console.log('in GoGet');
		var query = req.tertulias.query;
		var resultsTag = req.tertulias.resultsTag;
	    var paramsTypes = req.tertulias.paramsTypes;
	    var paramsValues = req.tertulias.paramsValues;
	    var links = req.tertulias.links;
	    var itemLinks = req.tertulias.itemLinks;

		var connection = new sql.Connection(util.sqlConfiguration);
	    connection.connect(function(err) {
	        var preparedStatement = new sql.PreparedStatement(connection);
	        for (var key in paramsTypes)
	        	preparedStatement.input(key, paramsTypes[key]);
	        preparedStatement.prepare(query, function(err) {
	            if (err) {
	            	completeError(err, res);
	            	return next(err);
	            }
	            preparedStatement.execute(paramsValues,
	                function(err, recordset, affected) {
	                    if (err) {
	                    	completeError(err, res);
	                    	return next(err);
	                    }
	                    res.type('application/json');
                    	if (typeof itemLinks !== typeof undefined) {
		                    recordset.forEach(function(elem) {
		                    	elem['links'] = JSON.parse(itemLinks.replace(/:id/g, elem.id));
                    		});
	                    };
	                    preparedStatement.unprepare();
	                    var results = {};
	                    if (req.tertulias.jsonType == "array")
	                    	results[resultsTag] = recordset;
	                    else
	                    	results[resultsTag] = recordset.length == 0 ? {} : recordset[0];
	                    results['links'] = JSON.parse(links);
	                    res.json(results);
	                    return next();
	                }
	            );
	        });
	    });
	};

    return router;

}
<commit_msg>wip save<commit_after>var express = require('express'),
	bodyParser = require('body-parser'),
    authenticate = require('azure-mobile-apps/src/express/middleware/authenticate'),
    authorize = require('azure-mobile-apps/src/express/middleware/authorize');

var sql = require('mssql');
var util = require('../util');

/* { 'SQL types': {
	'String': 'sql.NVarChar', 'Number': 'sql.Int', 'Boolean': 'sql.Bit', 'Date': 'sql.DateTime', 'Buffer': 'sql.VarBinary', 'sql.Table': 'sql.TVP'
} } */
module.exports = function (configuration) {
    var router = express.Router();

    router.get('/', (req, res, next) => {
		console.log('in GET /api/tertulias');
		var route = '/tertulias';
	    sql.connect(util.sqlConfiguration)
	    .then(function() {
			new sql.Request()
	    	.input('sid', sql.NVarChar(40), req.azureMobile.user.id)
	    	.query('SELECT' +
					' tr_id         AS id,' +
					' tr_name       AS name,' +
					' tr_subject    AS subject,' +
					' ev_targetdate AS nextEventDate,' +
					' lo_name       AS nextEventLocation,' +
					' no_count      AS messages,' +
					' nv_name       AS role ' +
				' FROM Tertulias' +
					' INNER JOIN Members ON mb_tertulia = tr_id' +
					' INNER JOIN Enumvalues ON mb_role = nv_id' +
					' INNER JOIN Users ON mb_user = us_id' +
					' LEFT JOIN' +
						' (SELECT * FROM' +
							' (SELECT RANK() OVER(PARTITION BY ev_tertulia ORDER BY ev_targetdate DESC) AS "rank", * FROM Events' +
						' INNER JOIN Locations on ev_location = lo_id WHERE ev_targetdate > GETDATE()) AS a WHERE a.rank = 1) AS b' +
							' ON ev_tertulia = tr_id' +
						' LEFT JOIN (SELECT no_tertulia, count(*) AS no_count FROM Notifications WHERE no_id NOT IN' +
							' (SELECT no_id FROM Notifications INNER JOIN Readnotifications ON rn_notification = no_id' +
						' INNER JOIN Users ON rn_user = us_id WHERE us_sid = @sid)' +
				' GROUP BY no_tertulia) AS c ON no_tertulia = tr_id' +
				' WHERE tr_is_cancelled = 0 AND us_sid = @sid')
	    	.then(function(recordset) {
			    var links = '[ ' +
						'{ "rel": "self", "method": "GET", "href": "' + route + '" }, ' +
						'{ "rel": "create", "method": "POST", "href": "' + route + '" }, ' +
						'{ "rel": "searchPublic", "method": "GET", "href": "' + route + '/publicsearch" } ' +
					']';
	    		console.log(links);
			    var itemLinks = '[ ' +
						'{ "rel": "self", "method": "GET", "href": "' + route + '/:id" }, ' +
						'{ "rel": "update", "method": "PATCH", "href": "' + route + '/:id" }, ' +
						'{ "rel": "delete", "method": "DELETE", "href": "' + route + '/:id" }, ' +
						'{ "rel": "unsubscribe", "method": "DELETE", "href": "' + route + '/:id/unsubscribe" } ' +
						'{ "rel": "members", "method": "GET", "href": "' + route + '/:id/members" } ' +
						'{ "rel": "event", "method": "POST", "href": "' + route + '/:id/event" } ' +
					']';
	    		console.log(itemLinks);
				res.type('application/json');
	    		console.log('aqui 1');
                recordset.forEach(function(elem) {
                	elem['links'] = JSON.parse(itemLinks.replace(/:id/g, elem.id));
        		});
	    		console.log('aqui 2');
                var results = {};
            	results['tertulias'] = recordset;
                results['links'] = JSON.parse(links);
                console.log(results);
                res.json(results);
                res.sendStatus(200);
                return next();
	    	})
	    });
    });

    router.get('/publicSearch', (req, res, next) => {
		console.log('in GET /api/tertulias/publicsearch');
		var route = '/tertulias';
		var point = 'POINT(' + req.query.latitude + ' ' + req.query.longitude + ')';
	    sql.connect(util.sqlConfiguration)
	    .then(function() {
			new sql.Request()
	    	.input('sid', sql.NVarChar(40), req.azureMobile.user.id)
	    	.input('query', sql.NVarChar, '%' + req.query.query + '%')
	    	.input('latitude', sql.NVarChar, req.query.latitude)
	    	.input('longitude', sql.NVarChar, req.query.longitude)
	    	.input('point', sql.NVarChar, point)
	    	.query('SELECT TOP 25' +
		    		' tr_id AS id,' +
		    		' tr_name AS name,' +
		    		' tr_subject AS subject,' +
		    		' lo_name AS location' +
	    		' FROM Tertulias' +
	    			' INNER JOIN Locations ON tr_location = lo_id' +
	    		' WHERE (tr_name LIKE @query OR tr_subject LIKE @query OR lo_name LIKE @query)' +
	    			' AND tr_is_cancelled = 0 AND tr_is_private = 0' +
		    		' AND tr_id NOT IN' +
		    			' (SELECT mb_tertulia FROM Tertulias' +
		    			' INNER JOIN Members ON mb_tertulia = tr_id' +
		    			' INNER JOIN Users ON mb_user = us_id WHERE us_sid = @sid)' +
				' ORDER BY lo_geography.STDistance( @point )')
				// ' ORDER BY lo_geography.STDistance(\'POINT( @latitude @longitude )\')')
				// ' ORDER BY lo_geography.STDistance(\'POINT( 38.11 -9.1123113 )\')')
	    	.then(function(recordset) {
                var links = '[ { "rel": "self", "method": "GET", "href": "' + route + '/publicSearch" } ]';
                var itemLinks = '[ ' +
            	    	'{ "rel": "self", "method": "GET", "href": "' + route + '/:id" }, ' +
						'{ "rel": "subscribe", "method": "POST", "href": "' + route + '/:id/subscribe" }' +
					']';
                res.type('application/json');
                recordset.forEach(function(elem) {
                	elem['links'] = JSON.parse(itemLinks.replace(/:id/g, elem.id));
        		});
                var results = {};
            	results['tertulias'] = recordset;
                results['links'] = JSON.parse(links);
                res.json(results);
                res.sendStatus(200);
                return next();
            })
	    });
	});

	router.get('/:tr_id', (req, res, next) => {
		console.log('in GET /api/tertulias/:tr_id');
		var tr_id = req.params.tr_id;
		var route = '/tertulias/' + tr_id;
		if (isNaN(tr_id))
			return next();

	    sql.connect(util.sqlConfiguration)
	    .then(function() {
			new sql.Request()
			.input('tertulia', sql.Int, tr_id)
			.input('sid', sql.NVarChar(40), req.azureMobile.user.id)
			.query('SELECT' +
					' tr_id AS id,' + // Tertulia
					' tr_name AS name,' +
					' tr_subject AS subject,' +
					' lo_name AS location,' + // Location
					' lo_address AS address,' +
					' lo_zip AS zip,' +
					' lo_city AS city,' +
					' lo_country AS country,' +
					' lo_latitude AS latitude,' +
					' lo_longitude AS longitude,' +
					' sc_type AS scheduleId,' + // Schedule
					' _Schedule.nv_name AS scheduleName,' +
					' _Schedule.nv_description AS scheduleDescription,' +
					' tr_is_private AS private, ' +  // Private
					' _Member.nv_name AS role' + // Role
				' FROM Tertulias' +
					' INNER JOIN Locations ON tr_location = lo_id' +
					' INNER JOIN Schedules ON tr_schedule = sc_id' +
					' LEFT JOIN Members ON mb_tertulia = tr_id' +
					' LEFT JOIN Users ON mb_user = us_id' +
					' LEFT JOIN EnumValues AS _Member ON mb_role = _Member.nv_id' +
					' INNER JOIN EnumValues AS _Schedule ON sc_type = _Schedule.nv_id' +
				' WHERE tr_is_cancelled = 0 AND (us_sid = @sid OR tr_is_private = 0)' +
					' AND tr_id = @tertulia')
			.then(function(recordset) {
				var links = '[ ' +
						'{ "rel": "self", "method": "GET", "href": "' + route + '" }, ' +
						'{ "rel": "update", "method": "PATCH", "href": "' + route + '" }, ' +
						'{ "rel": "delete", "method": "DELETE", "href": "' + route + '" }, ' +
						'{ "rel": "subscribe", "method": "POST", "href": "' + route + '/subscribe" }, ' +
						'{ "rel": "unsubscribe", "method": "DELETE", "href": "' + route + '/unsubscribe" } ' +
					']';
                res.type('application/json');
                var results = {};
            	results['tertulia'] = recordset[0];
                results['links'] = JSON.parse(links);
                res.json(results);
                res.sendStatus(200);
                return next();
			})
		});
	});

	router.post('/', (req, res, next) => {
		console.log('in POST /api/tertulias');
	    sql.connect(util.sqlConfiguration)
	    .then(function() {
			new sql.Request()
			.input('name', sql.NVarChar(40), req.body.name)
			.input('subject', sql.NVarChar(80), req.body.subject)
			.input('locationName', sql.NVarChar(40), req.body.location)
			.input('locationAddress', sql.NVarChar(80), req.body.address)
			.input('locationZip', sql.NVarChar(40), req.body.zip)
			.input('locationCity', sql.NVarChar(40), req.body.city)
			.input('locationCountry', sql.NVarChar(40), req.body.country)
			.input('locationLatitude', sql.NVarChar(12), req.body.latitude)
			.input('locationLongitude', sql.NVarChar(12), req.body.longitude)
			.input('weekDay', sql.NVarChar(20), 'Tuesday')
			.input('weekNr', sql.Int, 1)
			.input('fromStart', sql.BIT, 1)
			.input('skip', sql.Int, 0)
			.input('isPrivate', sql.Int, req.body.isPrivate ? 1 : 0)
			.input('userSid', sql.NVarChar(40), req.azureMobile.user.id)
			.execute('sp_insertTertulia_MonthlyW_sid')
			.then((recordsets) => {
				if (recordsets.length == 0) {
					res.status(201)	// 201: Created
						.type('application/json')
						.json( { result: 'Ok' } );
					return next();
				} else {
					res.status(409)	// 409: Conflict, 422: Unprocessable Entity (WebDAV; RFC 4918)
						.type('application/json')
						.json( { result: 'Duplicate' } );
					return next('409');
				}
				next();
			})
			.catch(function(err) {
				next(err);
			});
		})
		.catch(function(err) {
			return next(err);
		});
	});

	router.post('/:tr_id/subscribe', (req, res, next) => {
		console.log('in POST /api/tertulias/:tr_id/subscribe');
	    sql.connect(util.sqlConfiguration)
	    .then(function() {
			new sql.Request()
			.input('sid', sql.NVarChar(40), req.azureMobile.user.id)
			.input('tertulia', sql.Int, req.params.tr_id)
			.execute('spSubscribe')
			.then((recordset) => {
				if (recordset.returnValue = 1) {
					res.sendStatus(200);
				} else {
					res.sendStatus(409);
				}
				return next();
			})
			.catch(function(err) {
				next(err);
			});
		})
		.catch(function(err) {
			return next(err);
		});
	});

	var completeError = function(err, res) {
	    if (err) {
	    	console.log("Error:");
	        console.error(err);
	        if (res) res.sendStatus(500);
	    }
	};

	var goGet = function(req, res, next) {
		console.log('in GoGet');
		var query = req.tertulias.query;
		var resultsTag = req.tertulias.resultsTag;
	    var paramsTypes = req.tertulias.paramsTypes;
	    var paramsValues = req.tertulias.paramsValues;
	    var links = req.tertulias.links;
	    var itemLinks = req.tertulias.itemLinks;

		var connection = new sql.Connection(util.sqlConfiguration);
	    connection.connect(function(err) {
	        var preparedStatement = new sql.PreparedStatement(connection);
	        for (var key in paramsTypes)
	        	preparedStatement.input(key, paramsTypes[key]);
	        preparedStatement.prepare(query, function(err) {
	            if (err) {
	            	completeError(err, res);
	            	return next(err);
	            }
	            preparedStatement.execute(paramsValues,
	                function(err, recordset, affected) {
	                    if (err) {
	                    	completeError(err, res);
	                    	return next(err);
	                    }
	                    res.type('application/json');
                    	if (typeof itemLinks !== typeof undefined) {
		                    recordset.forEach(function(elem) {
		                    	elem['links'] = JSON.parse(itemLinks.replace(/:id/g, elem.id));
                    		});
	                    };
	                    preparedStatement.unprepare();
	                    var results = {};
	                    if (req.tertulias.jsonType == "array")
	                    	results[resultsTag] = recordset;
	                    else
	                    	results[resultsTag] = recordset.length == 0 ? {} : recordset[0];
	                    results['links'] = JSON.parse(links);
	                    res.json(results);
	                    return next();
	                }
	            );
	        });
	    });
	};

    return router;

}
<|endoftext|>


<commit_before>const sinon = require('sinon');
const { expect } = require('chai');
const { describe, it, beforeEach, afterEach, before, after } = require('mocha');

module.exports = {
    it,
    sinon,
    expect,
    describe,
    before,
    after,
    beforeEach,
    afterEach
};<commit_msg>Chai-as-promised<commit_after>const sinon = require('sinon');
const chai = require('chai');
const expect = chai.expect;
chai.use(require('chai-as-promised'));

const { describe, it, beforeEach, afterEach, before, after } = require('mocha');

module.exports = {
    it,
    sinon,
    expect,
    describe,
    before,
    after,
    beforeEach,
    afterEach
};<|endoftext|>


<commit_before>var path = require('path');
var filename = path.basename(__filename);
var benchmarket = require('benchmarket');
var expect = require('expect.js');
var hooks = require('./lib/hooks');

var clusterSize = 10;
var happnSecure = false;

describe(filename, function () {

  this.timeout(30000);

  before(function () {
    this.logLevel = process.env.LOG_LEVEL;
    process.env.LOG_LEVEL = 'off';
  });

  benchmarket.start();

  hooks.startCluster({
    size: clusterSize,
    happnSecure: happnSecure
  });

  it('each server stabilised with all 10 peers', function (done) {
    var self = this;

    var peerCounts = [];
    self.servers.forEach(function (server) {
      var count = Object.keys(server.services.orchestrator.peers).length;
      peerCounts.push(count);
    });

    expect(peerCounts).to.eql([10, 10, 10, 10, 10, 10, 10, 10, 10, 10]);
    done();
  });

  hooks.stopCluster();

  after(function () {
    process.env.LOG_LEVEL = this.logLevel;
  });

  after(benchmarket.store());
  benchmarket.stop();

});
<commit_msg>1 more functional test<commit_after>var path = require('path');
var filename = path.basename(__filename);
var benchmarket = require('benchmarket');
var expect = require('expect.js');
var hooks = require('./lib/hooks');

var clusterSize = 10;
var happnSecure = false;

describe.only(filename, function () {

  this.timeout(30000);

  before(function () {
    this.logLevel = process.env.LOG_LEVEL;
    process.env.LOG_LEVEL = 'off';
  });

  benchmarket.start();

  hooks.startCluster({
    size: clusterSize,
    happnSecure: happnSecure
  });

  it('each server stabilised with all 10 peers', function (done) {
    var self = this;

    var peerCounts = [];
    self.servers.forEach(function (server) {
      var count = Object.keys(server.services.orchestrator.peers).length;
      peerCounts.push(count);
    });

    expect(peerCounts).to.eql([10, 10, 10, 10, 10, 10, 10, 10, 10, 10]);
    done();
  });

  hooks.stopCluster();

  after(function () {
    process.env.LOG_LEVEL = this.logLevel;
  });

  after(benchmarket.store());
  benchmarket.stop();

});
<|endoftext|>