<commit_before>/**
 * roitool.js
 *
 * requirements:
 *   tools.js
 *	 boxselectiontool.js
 *
 */

/**
 * The constructor for the region of interest (ROI) tool. It allows to draw a
 * box selection on the current view and to modify it with the help of text
 * boxes in a tool bar.
 */
function RoiTool()
{
    // call super constructor
    BoxSelectionTool.call( this )

    var self = this;
    this.toolname = "roitool";

	if ( !ui ) ui = new UI();

	// inputs for x, y, width and height of the crop box
	this.box_roi_x = document.getElementById( "box_roi_x" );
	this.box_roi_y = document.getElementById( "box_roi_y" );
	this.box_roi_w = document.getElementById( "box_roi_w" );
	this.box_roi_h = document.getElementById( "box_roi_h" );

	//! mouse catcher
	this.mouseCatcher = document.createElement( "div" );
	this.mouseCatcher.className = "sliceMouseCatcher";
	this.mouseCatcher.style.cursor = "default";

    this.button_roi_apply = document.getElementById( "button_roi_apply" );

    // bind event handlers to current calling context
    this.onmousedown_bound = this.onmousedown.bind(this);
    this.onmouseup_bound = this.onmouseup.bind(this);
    this.onmousemove_pos_bound = this.onmousemove.pos.bind(this);
    this.onmousemove_crop_bound = this.onmousemove.crop.bind(this);
};

// Let the RoiTool inherit from the BoxSelectionTool
extend( RoiTool, BoxSelectionTool );

/**
 * Updates UI elements like the the crop box input boxes.
 */
RoiTool.prototype.updateControls = function()
{
    if ( this.getCropBox() )
    {
        var roiBoxBB = this.getCropBoxBoundingBox();
        this.box_roi_x.value = isNaN(roiBoxBB.left_px) ? "-" : roiBoxBB.left_px;
        this.box_roi_y.value = isNaN(roiBoxBB.top_px) ? "-" : roiBoxBB.top_px;
        this.box_roi_w.value = isNaN(roiBoxBB.width_px) ? "-" : roiBoxBB.width_px;
        this.box_roi_h.value = isNaN(roiBoxBB.height_px) ? "-" : roiBoxBB.height_px;
    }

    return;
};

/**
 * Handles redraw events and calls the redraw method of the base class and
 * triggers an update of UI elements.
 */
RoiTool.prototype.redraw = function()
{
    // call register of super class
    RoiTool.superproto.redraw.call( this );
    this.updateControls();
};

/**
 * Handles resize events by adjusting the mouse catcher.
 */
RoiTool.prototype.resize = function( width, height )
{
    this.mouseCatcher.style.width = width + "px";
    this.mouseCatcher.style.height = height + "px";
    return;
};

/**
 * Handles onchange events in the X input box.
 */
RoiTool.prototype.changeCropBoxXByInput = function( e )
{
    var val = parseInt( this.box_roi_x.value );
    var cropBox = this.getCropBox();

    if ( isNaN( val ) )
    {
        this.value = this.toPx( cropBox.left, this.stack.resolution.x );
    }
    else
    {
        var screen_left = this.getScreenLeft();
        var width_world = cropBox.right - cropBox.left;
        cropBox.left = this.toWorld( val, this.stack.resolution.x ) + screen_left;
        cropBox.right = cropBox.left + width_world;
        this.updateCropBox();
        this.updateControls();
    }
    return;
};

/**
 * Handles onchange events in the Y input box.
 */
RoiTool.prototype.changeCropBoxYByInput = function( e )
{
    var val = parseInt( this.box_roi_y.value );
    var cropBox = this.getCropBox();

    if ( isNaN( val ) )
    {
        this.value = this.toPx( cropBox.left, this.stack.resolution.y );
    }
    else
    {
        var screen_top = this.getScreenTop();
        var height_world = cropBox.bottom - cropBox.top;
        cropBox.top = this.toWorld( val, this.stack.resolution.y ) + screen_top;
        cropBox.bottom = cropBox.top + height_world;
        this.updateCropBox();
        this.updateControls();
    }
    return;
};

/**
 * Handles onchange events in the width input box.
 */
RoiTool.prototype.changeCropBoxWByInput = function( e )
{
    var val = parseInt( this.box_roi_w.value );
    var cropBox = this.getCropBox();

    if ( isNaN( val ) )
    {
        var width_world = cropBox.right - cropBox.left;
        this.value = this.toPx( width_world, this.stack.resolution.x );
    }
    else
    {
        var width_world = this.toWorld( val, this.stack.resolution.x );
        cropBox.right = cropBox.left + width_world;
        this.updateCropBox();
        this.updateControls();
    }
    return;
};

/**
 * Handles onchange events in the height input box.
 */
RoiTool.prototype.changeCropBoxHByInput = function( e )
{
    var val = parseInt( this.box_roi_h.value );
    var cropBox = this.getCropBox();

    if ( isNaN( val ) )
    {
        var height_world = cropBox.bottom - cropBox.top;
        this.value = this.toPx( height_world, this.stack.resolution.y );
    }
    else
    {
        var height_world = this.toWorld( val, this.stack.resolution.y );
        cropBox.bottom = cropBox.top + height_world;
        this.updateCropBox();
        this.updateControls();
    }
    return;
};

/**
 * Handles mouse wheel changes in text input boxes.
 */
RoiTool.prototype.cropBoxMouseWheel = function( e )
{
    var w = ui.getMouseWheel( e );
    if ( w )
    {
        this.value = parseInt( this.value ) - w;
        this.onchange();
    }
    return false
};

/**
 * Handles mousedown events.
 */
RoiTool.prototype.onmousedown = function( e )
{
    var b = ui.getMouseButton( e );
    switch ( b )
    {
    case 2:
        ui.removeEvent( "onmousemove", this.onmousemove_crop_bound );
        ui.removeEvent( "onmouseup", this.onmouseup_bound );
        break;
    default:
        var m = ui.getMouse( e, this.stack.getView() );
        this.createCropBox( m.offsetX, m.offsetY );

        ui.registerEvent( "onmousemove", this.onmousemove_crop_bound );
        ui.registerEvent( "onmouseup", this.onmouseup_bound );
        ui.catchEvents( "crosshair" );
    }
    ui.onmousedown( e );

    //! this is a dirty trick to remove the focus from input elements when clicking the stack views, assumes, that document.body.firstChild is an empty and useless
    document.body.firstChild.focus();

    return false;
};

/**
 * Keeps two mousemove event handlers. The first (pos) shown the current
 * position in the status line and the second one (crop) adjusts the crop box
 * while moving.
 */
RoiTool.prototype.onmousemove = {
    pos : function ( e )
    {
        var xp;
        var yp;
        var m = ui.getMouse( e, this.stack.getView() );
        if ( m )
        {
            var s = this.stack;
            var pos_x = s.translation.x + ( s.x + ( m.offsetX - s.viewWidth / 2 ) / s.scale ) * s.resolution.x;
            var pos_y = s.translation.x + ( s.y + ( m.offsetY - s.viewHeight / 2 ) / s.scale ) * s.resolution.y;
            statusBar.replaceLast( "[" + this.convertWorld( pos_x ).toFixed( 3 ) + ", " + this.convertWorld( pos_y ).toFixed( 3 ) + "]" );
        }
        return false;
    },
    crop : function( e )
    {
        var cropBox = this.getCropBox();

        if ( cropBox )
        {
            // adjust left and rigt component
            cropBox.xdist += ui.diffX;
            var xdist_world = this.toWorld( cropBox.xdist, this.stack.resolution.x );
            if ( cropBox.xdist > 0 )
            {
                cropBox.left = cropBox.xorigin;
                cropBox.right = cropBox.xorigin + xdist_world;
            }
            else
            {
                cropBox.left = cropBox.xorigin + xdist_world;
                cropBox.right = cropBox.xorigin;
            }

            // adjust top and bottom component
            cropBox.ydist += ui.diffY;
            var ydist_world = this.toWorld( cropBox.ydist, this.stack.resolution.y );
            if ( cropBox.ydist > 0 )
            {
                cropBox.top = cropBox.yorigin;
                cropBox.bottom = cropBox.yorigin + ydist_world;
            }
            else
            {
                cropBox.top = cropBox.yorigin + ydist_world;
                cropBox.bottom = cropBox.yorigin;
            }

            this.updateCropBox();
        }
        this.updateControls();
    }
};

RoiTool.prototype.onmouseup = function ( e )
{
    ui.releaseEvents();
    ui.removeEvent( "onmousemove", this.onmousemove_crop_bound );
    ui.removeEvent( "onmouseup", this.onmouseup_bound );
    this.updateControls();
};

RoiTool.prototype.onmousewheel = function( e )
{

};

/**
 * Adds a mouse wheel listener to a component.
 */
RoiTool.prototype.addMousewheelListener = function( component, handler )
{
    try
    {
        component.addEventListener( "DOMMouseScroll", handler, false );
    }
    catch ( error )
    {
        try
        {
            component.onmousewheel = handler;
        }
        catch ( error ) {}
    }
};

/**
 * Removes a mouse wheel listener from a component.
 */
RoiTool.prototype.removeMousewheelListener = function( component, handler )
{
    try
    {
        component.removeEventListener( "DOMMouseScroll", handler, false );
    }
    catch ( error )
    {
        try
        {
            component.onmousewheel = null;
        }
        catch ( error ) {}
    }
};

/**
 * Installs this tool in a stack and registers all GUI control elements and
 * event handlers.
 */
RoiTool.prototype.register = function( parentStack )
{
    // call register of super class (updates also stack member)
    RoiTool.superproto.register.call( this, parentStack );

    // initialize the stacks we offer to crop
    var project = this.stack.getProject();

    this.mouseCatcher.style.cursor = "crosshair";
    // create and remember mouse bindings, bound to the
    // current context (this).
    this.mouseCatcher.onmousedown = this.onmousedown_bound;
    this.mouseCatcher.onmousemove = this.onmousemove_pos_bound;

    var onmounsewheel = this.onmousewheel.bind(this);
    try
    {
        this.mouseCatcher.addEventListener( "DOMMouseScroll", onmousewheel, false );
        /* Webkit takes the event but does not understand it ... */
        this.mouseCatcher.addEventListener( "mousewheel", onmousewheel, false );
    }
    catch ( error )
    {
        try
        {
            this.mouseCatcher.onmousewheel = onmousewheel;
        }
        catch ( error ) {}
    }

    this.stack.getView().appendChild( this.mouseCatcher );

    var cropBoxMouseWheel = this.cropBoxMouseWheel.bind(this);
    this.box_roi_x.onchange = this.changeCropBoxXByInput.bind(this);
    this.addMousewheelListener( this.box_roi_x, this.cropBoxMouseWheel );
    this.box_roi_y.onchange = this.changeCropBoxYByInput.bind(this);
    this.addMousewheelListener( this.box_roi_y, this.cropBoxMouseWheel );
    this.box_roi_w.onchange = this.changeCropBoxWByInput.bind(this);
    this.addMousewheelListener( this.box_roi_w, this.cropBoxMouseWheel );
    this.box_roi_h.onchange = this.changeCropBoxHByInput.bind(this);
    this.addMousewheelListener( this.box_roi_h, this.cropBoxMouseWheel );

    // initialize crop button
    //this.button_roi_apply.onclick = crop;

    document.getElementById( "toolbar_roi" ).style.display = "block";

    this.updateControls();

    return;
};

/**
 * Unregisters all stack related mouse and keyboard controls.
 */
RoiTool.prototype.unregister = function()
{
    if ( this.stack && this.mouseCatcher.parentNode == this.stack.getView() )
        this.stack.getView().removeChild( this.mouseCatcher );

    return;
};

/**
 * Unregisters all project related GUI control connections and event
 * handlers, toggle off tool activity signals (like buttons).
 */
RoiTool.prototype.destroy = function()
{
    this.unregister();

    document.getElementById( "toolbar_roi" ).style.display = "none";

    this.box_roi_x.onchange = null;
    this.removeMousewheelListener( this.box_roi_x, this.cropBoxMouseWheel );
    this.box_roi_y.onchange = null;
    this.removeMousewheelListener( this.box_roi_y, this.cropBoxMouseWheel );
    this.box_roi_w.onchange = null;
    this.removeMousewheelListener( this.box_roi_w, this.cropBoxMouseWheel );
    this.box_roi_h.onchange = null;
    this.removeMousewheelListener( this.box_roi_h, this.cropBoxMouseWheel );

    this.button_roi_apply.onclick = null;

    // call destroy of super class
    RoiTool.superproto.destroy.call( this );

    return;
};

/**
 * This function should return true if there was any action linked to the key
 * code, or false otherwise.
 */
RoiTool.prototype.handleKeyPress = function( e ) {
    return false;
};

<commit_msg>Fix typo in variable name in RoiTool<commit_after>/**
 * roitool.js
 *
 * requirements:
 *   tools.js
 *	 boxselectiontool.js
 *
 */

/**
 * The constructor for the region of interest (ROI) tool. It allows to draw a
 * box selection on the current view and to modify it with the help of text
 * boxes in a tool bar.
 */
function RoiTool()
{
    // call super constructor
    BoxSelectionTool.call( this )

    var self = this;
    this.toolname = "roitool";

	if ( !ui ) ui = new UI();

	// inputs for x, y, width and height of the crop box
	this.box_roi_x = document.getElementById( "box_roi_x" );
	this.box_roi_y = document.getElementById( "box_roi_y" );
	this.box_roi_w = document.getElementById( "box_roi_w" );
	this.box_roi_h = document.getElementById( "box_roi_h" );

	//! mouse catcher
	this.mouseCatcher = document.createElement( "div" );
	this.mouseCatcher.className = "sliceMouseCatcher";
	this.mouseCatcher.style.cursor = "default";

    this.button_roi_apply = document.getElementById( "button_roi_apply" );

    // bind event handlers to current calling context
    this.onmousedown_bound = this.onmousedown.bind(this);
    this.onmouseup_bound = this.onmouseup.bind(this);
    this.onmousemove_pos_bound = this.onmousemove.pos.bind(this);
    this.onmousemove_crop_bound = this.onmousemove.crop.bind(this);
};

// Let the RoiTool inherit from the BoxSelectionTool
extend( RoiTool, BoxSelectionTool );

/**
 * Updates UI elements like the the crop box input boxes.
 */
RoiTool.prototype.updateControls = function()
{
    if ( this.getCropBox() )
    {
        var roiBoxBB = this.getCropBoxBoundingBox();
        this.box_roi_x.value = isNaN(roiBoxBB.left_px) ? "-" : roiBoxBB.left_px;
        this.box_roi_y.value = isNaN(roiBoxBB.top_px) ? "-" : roiBoxBB.top_px;
        this.box_roi_w.value = isNaN(roiBoxBB.width_px) ? "-" : roiBoxBB.width_px;
        this.box_roi_h.value = isNaN(roiBoxBB.height_px) ? "-" : roiBoxBB.height_px;
    }

    return;
};

/**
 * Handles redraw events and calls the redraw method of the base class and
 * triggers an update of UI elements.
 */
RoiTool.prototype.redraw = function()
{
    // call register of super class
    RoiTool.superproto.redraw.call( this );
    this.updateControls();
};

/**
 * Handles resize events by adjusting the mouse catcher.
 */
RoiTool.prototype.resize = function( width, height )
{
    this.mouseCatcher.style.width = width + "px";
    this.mouseCatcher.style.height = height + "px";
    return;
};

/**
 * Handles onchange events in the X input box.
 */
RoiTool.prototype.changeCropBoxXByInput = function( e )
{
    var val = parseInt( this.box_roi_x.value );
    var cropBox = this.getCropBox();

    if ( isNaN( val ) )
    {
        this.value = this.toPx( cropBox.left, this.stack.resolution.x );
    }
    else
    {
        var screen_left = this.getScreenLeft();
        var width_world = cropBox.right - cropBox.left;
        cropBox.left = this.toWorld( val, this.stack.resolution.x ) + screen_left;
        cropBox.right = cropBox.left + width_world;
        this.updateCropBox();
        this.updateControls();
    }
    return;
};

/**
 * Handles onchange events in the Y input box.
 */
RoiTool.prototype.changeCropBoxYByInput = function( e )
{
    var val = parseInt( this.box_roi_y.value );
    var cropBox = this.getCropBox();

    if ( isNaN( val ) )
    {
        this.value = this.toPx( cropBox.left, this.stack.resolution.y );
    }
    else
    {
        var screen_top = this.getScreenTop();
        var height_world = cropBox.bottom - cropBox.top;
        cropBox.top = this.toWorld( val, this.stack.resolution.y ) + screen_top;
        cropBox.bottom = cropBox.top + height_world;
        this.updateCropBox();
        this.updateControls();
    }
    return;
};

/**
 * Handles onchange events in the width input box.
 */
RoiTool.prototype.changeCropBoxWByInput = function( e )
{
    var val = parseInt( this.box_roi_w.value );
    var cropBox = this.getCropBox();

    if ( isNaN( val ) )
    {
        var width_world = cropBox.right - cropBox.left;
        this.value = this.toPx( width_world, this.stack.resolution.x );
    }
    else
    {
        var width_world = this.toWorld( val, this.stack.resolution.x );
        cropBox.right = cropBox.left + width_world;
        this.updateCropBox();
        this.updateControls();
    }
    return;
};

/**
 * Handles onchange events in the height input box.
 */
RoiTool.prototype.changeCropBoxHByInput = function( e )
{
    var val = parseInt( this.box_roi_h.value );
    var cropBox = this.getCropBox();

    if ( isNaN( val ) )
    {
        var height_world = cropBox.bottom - cropBox.top;
        this.value = this.toPx( height_world, this.stack.resolution.y );
    }
    else
    {
        var height_world = this.toWorld( val, this.stack.resolution.y );
        cropBox.bottom = cropBox.top + height_world;
        this.updateCropBox();
        this.updateControls();
    }
    return;
};

/**
 * Handles mouse wheel changes in text input boxes.
 */
RoiTool.prototype.cropBoxMouseWheel = function( e )
{
    var w = ui.getMouseWheel( e );
    if ( w )
    {
        this.value = parseInt( this.value ) - w;
        this.onchange();
    }
    return false
};

/**
 * Handles mousedown events.
 */
RoiTool.prototype.onmousedown = function( e )
{
    var b = ui.getMouseButton( e );
    switch ( b )
    {
    case 2:
        ui.removeEvent( "onmousemove", this.onmousemove_crop_bound );
        ui.removeEvent( "onmouseup", this.onmouseup_bound );
        break;
    default:
        var m = ui.getMouse( e, this.stack.getView() );
        this.createCropBox( m.offsetX, m.offsetY );

        ui.registerEvent( "onmousemove", this.onmousemove_crop_bound );
        ui.registerEvent( "onmouseup", this.onmouseup_bound );
        ui.catchEvents( "crosshair" );
    }
    ui.onmousedown( e );

    //! this is a dirty trick to remove the focus from input elements when clicking the stack views, assumes, that document.body.firstChild is an empty and useless
    document.body.firstChild.focus();

    return false;
};

/**
 * Keeps two mousemove event handlers. The first (pos) shown the current
 * position in the status line and the second one (crop) adjusts the crop box
 * while moving.
 */
RoiTool.prototype.onmousemove = {
    pos : function ( e )
    {
        var xp;
        var yp;
        var m = ui.getMouse( e, this.stack.getView() );
        if ( m )
        {
            var s = this.stack;
            var pos_x = s.translation.x + ( s.x + ( m.offsetX - s.viewWidth / 2 ) / s.scale ) * s.resolution.x;
            var pos_y = s.translation.x + ( s.y + ( m.offsetY - s.viewHeight / 2 ) / s.scale ) * s.resolution.y;
            statusBar.replaceLast( "[" + this.convertWorld( pos_x ).toFixed( 3 ) + ", " + this.convertWorld( pos_y ).toFixed( 3 ) + "]" );
        }
        return false;
    },
    crop : function( e )
    {
        var cropBox = this.getCropBox();

        if ( cropBox )
        {
            // adjust left and rigt component
            cropBox.xdist += ui.diffX;
            var xdist_world = this.toWorld( cropBox.xdist, this.stack.resolution.x );
            if ( cropBox.xdist > 0 )
            {
                cropBox.left = cropBox.xorigin;
                cropBox.right = cropBox.xorigin + xdist_world;
            }
            else
            {
                cropBox.left = cropBox.xorigin + xdist_world;
                cropBox.right = cropBox.xorigin;
            }

            // adjust top and bottom component
            cropBox.ydist += ui.diffY;
            var ydist_world = this.toWorld( cropBox.ydist, this.stack.resolution.y );
            if ( cropBox.ydist > 0 )
            {
                cropBox.top = cropBox.yorigin;
                cropBox.bottom = cropBox.yorigin + ydist_world;
            }
            else
            {
                cropBox.top = cropBox.yorigin + ydist_world;
                cropBox.bottom = cropBox.yorigin;
            }

            this.updateCropBox();
        }
        this.updateControls();
    }
};

RoiTool.prototype.onmouseup = function ( e )
{
    ui.releaseEvents();
    ui.removeEvent( "onmousemove", this.onmousemove_crop_bound );
    ui.removeEvent( "onmouseup", this.onmouseup_bound );
    this.updateControls();
};

RoiTool.prototype.onmousewheel = function( e )
{

};

/**
 * Adds a mouse wheel listener to a component.
 */
RoiTool.prototype.addMousewheelListener = function( component, handler )
{
    try
    {
        component.addEventListener( "DOMMouseScroll", handler, false );
    }
    catch ( error )
    {
        try
        {
            component.onmousewheel = handler;
        }
        catch ( error ) {}
    }
};

/**
 * Removes a mouse wheel listener from a component.
 */
RoiTool.prototype.removeMousewheelListener = function( component, handler )
{
    try
    {
        component.removeEventListener( "DOMMouseScroll", handler, false );
    }
    catch ( error )
    {
        try
        {
            component.onmousewheel = null;
        }
        catch ( error ) {}
    }
};

/**
 * Installs this tool in a stack and registers all GUI control elements and
 * event handlers.
 */
RoiTool.prototype.register = function( parentStack )
{
    // call register of super class (updates also stack member)
    RoiTool.superproto.register.call( this, parentStack );

    // initialize the stacks we offer to crop
    var project = this.stack.getProject();

    this.mouseCatcher.style.cursor = "crosshair";
    // create and remember mouse bindings, bound to the
    // current context (this).
    this.mouseCatcher.onmousedown = this.onmousedown_bound;
    this.mouseCatcher.onmousemove = this.onmousemove_pos_bound;

    var onmousewheel = this.onmousewheel.bind(this);
    try
    {
        this.mouseCatcher.addEventListener( "DOMMouseScroll", onmousewheel, false );
        /* Webkit takes the event but does not understand it ... */
        this.mouseCatcher.addEventListener( "mousewheel", onmousewheel, false );
    }
    catch ( error )
    {
        try
        {
            this.mouseCatcher.onmousewheel = onmousewheel;
        }
        catch ( error ) {}
    }

    this.stack.getView().appendChild( this.mouseCatcher );

    var cropBoxMouseWheel = this.cropBoxMouseWheel.bind(this);
    this.box_roi_x.onchange = this.changeCropBoxXByInput.bind(this);
    this.addMousewheelListener( this.box_roi_x, this.cropBoxMouseWheel );
    this.box_roi_y.onchange = this.changeCropBoxYByInput.bind(this);
    this.addMousewheelListener( this.box_roi_y, this.cropBoxMouseWheel );
    this.box_roi_w.onchange = this.changeCropBoxWByInput.bind(this);
    this.addMousewheelListener( this.box_roi_w, this.cropBoxMouseWheel );
    this.box_roi_h.onchange = this.changeCropBoxHByInput.bind(this);
    this.addMousewheelListener( this.box_roi_h, this.cropBoxMouseWheel );

    // initialize crop button
    //this.button_roi_apply.onclick = crop;

    document.getElementById( "toolbar_roi" ).style.display = "block";

    this.updateControls();

    return;
};

/**
 * Unregisters all stack related mouse and keyboard controls.
 */
RoiTool.prototype.unregister = function()
{
    if ( this.stack && this.mouseCatcher.parentNode == this.stack.getView() )
        this.stack.getView().removeChild( this.mouseCatcher );

    return;
};

/**
 * Unregisters all project related GUI control connections and event
 * handlers, toggle off tool activity signals (like buttons).
 */
RoiTool.prototype.destroy = function()
{
    this.unregister();

    document.getElementById( "toolbar_roi" ).style.display = "none";

    this.box_roi_x.onchange = null;
    this.removeMousewheelListener( this.box_roi_x, this.cropBoxMouseWheel );
    this.box_roi_y.onchange = null;
    this.removeMousewheelListener( this.box_roi_y, this.cropBoxMouseWheel );
    this.box_roi_w.onchange = null;
    this.removeMousewheelListener( this.box_roi_w, this.cropBoxMouseWheel );
    this.box_roi_h.onchange = null;
    this.removeMousewheelListener( this.box_roi_h, this.cropBoxMouseWheel );

    this.button_roi_apply.onclick = null;

    // call destroy of super class
    RoiTool.superproto.destroy.call( this );

    return;
};

/**
 * This function should return true if there was any action linked to the key
 * code, or false otherwise.
 */
RoiTool.prototype.handleKeyPress = function( e ) {
    return false;
};

<|endoftext|>


<commit_before>import { Subject, Observable, CompositeDisposable } from "rxjs";

export default ({ triggerPressEventBefore = 200, triggerLongPressEventAfter = 700 }) => {
	return touches => {
		let touchStart = new Subject();
		let touchMove = new Subject();
		let touchEnd = new Subject();

		let touchPress = touchStart.flatMap(e =>
			touchEnd
				.first(x => x.identifier === e.identifier)
				.timeout(triggerPressEventBefore, Observable.empty())
		);

		let longTouch = touchStart.flatMap(e =>
			Observable
				.return(e)
				.delay(triggerLongPressEventAfter)
				.takeUntil(
					touchMove
						.merge(touchEnd)
						.first(x => x.identifier === e.identifier)
				)
		);

		let touchStartComposite = new CompositeDisposable();
		let touchMoveComposite = new CompositeDisposable();
		let touchEndComposite = new CompositeDisposable();
		let touchPressComposite = new CompositeDisposable();
		let longTouchComposite = new CompositeDisposable();

		touchStartComposite.add(
			touchStart
				.groupBy(e => e.identifier)
				.map(group => {
					return group.map(e => {
						touches.push({
							id: group.key,
							type: "start",
							event: e
						});
					});
				})
				.subscribe(group => {
					touchStartComposite.add(group.subscribe());
				})
		);

		touchMoveComposite.add(
			Observable
				.merge(
					touchStart.map(x => Object.assign(x, { type: "start" })),
					touchMove.map(x => Object.assign(x, { type: "move" })),
					touchEnd.map(x => Object.assign(x, { type: "end" }))
				)
				.groupBy(e => e.identifier)
				.map(group => {
					return group.pairwise().map(([e1, e2]) => {
						if (e1.type !== "end" && e2.type === "move") {
							touches.push({
								id: group.key,
								type: "move",
								event: e2,
								delta: {
									locationX: e2.locationX - e1.locationX,
									locationY: e2.locationY - e1.locationY,
									pageX: e2.pageX - e1.pageX,
									pageY: e2.pageY - e1.pageY,
									timestamp: e2.timestamp - e1.timestamp
								}
							});
						}
					});
				})
				.subscribe(group => {
					touchMoveComposite.add(group.subscribe());
				})
		);

		touchEndComposite.add(
			touchEnd
				.groupBy(e => e.identifier)
				.map(group => {
					return group.map(e => {
						touches.push({ id: group.key, type: "end", event: e });
					});
				})
				.subscribe(group => {
					touchEndComposite.add(group.subscribe());
				})
		);

		touchPressComposite.add(
			touchPress
				.groupBy(e => e.identifier)
				.map(group => {
					return group.map(e => {
						touches.push({
							id: group.key,
							type: "press",
							event: e
						});
					});
				})
				.subscribe(group => {
					touchPressComposite.add(group.subscribe());
				})
		);

		longTouchComposite.add(
			longTouch
				.groupBy(e => e.identifier)
				.map(group => {
					return group.map(e => {
						touches.push({
							id: group.key,
							type: "long-press",
							event: e
						});
					});
				})
				.subscribe(group => {
					longTouchComposite.add(group.subscribe());
				})
		);

		return {
			process(type, event) {
				switch (type) {
					case "start":
						touchStart.onNext(event);
						break;
					case "move":
						touchMove.onNext(event);
						break;
					case "end":
						touchEnd.onNext(event);
						break;
				}
			},
			end() {
				touchStart.dispose();
				touchMove.dispose();
				touchEnd.dispose();

				touchStartComposite.dispose();
				touchMoveComposite.dispose();
				touchEndComposite.dispose();
				touchPressComposite.dispose();
				longTouchComposite.dispose();
			}
		};
	};
};
<commit_msg>Apparently flatMap is now mergeMap - updating references<commit_after>import { Subject, Observable, CompositeDisposable } from "rxjs";

export default ({ triggerPressEventBefore = 200, triggerLongPressEventAfter = 700 }) => {
	return touches => {
		let touchStart = new Subject();
		let touchMove = new Subject();
		let touchEnd = new Subject();

		let touchPress = touchStart.mergeMap(e =>
			touchEnd
				.first(x => x.identifier === e.identifier)
				.timeout(triggerPressEventBefore, Observable.empty())
		);

		let longTouch = touchStart.mergeMap(e =>
			Observable
				.return(e)
				.delay(triggerLongPressEventAfter)
				.takeUntil(
					touchMove
						.merge(touchEnd)
						.first(x => x.identifier === e.identifier)
				)
		);

		let touchStartComposite = new CompositeDisposable();
		let touchMoveComposite = new CompositeDisposable();
		let touchEndComposite = new CompositeDisposable();
		let touchPressComposite = new CompositeDisposable();
		let longTouchComposite = new CompositeDisposable();

		touchStartComposite.add(
			touchStart
				.groupBy(e => e.identifier)
				.map(group => {
					return group.map(e => {
						touches.push({
							id: group.key,
							type: "start",
							event: e
						});
					});
				})
				.subscribe(group => {
					touchStartComposite.add(group.subscribe());
				})
		);

		touchMoveComposite.add(
			Observable
				.merge(
					touchStart.map(x => Object.assign(x, { type: "start" })),
					touchMove.map(x => Object.assign(x, { type: "move" })),
					touchEnd.map(x => Object.assign(x, { type: "end" }))
				)
				.groupBy(e => e.identifier)
				.map(group => {
					return group.pairwise().map(([e1, e2]) => {
						if (e1.type !== "end" && e2.type === "move") {
							touches.push({
								id: group.key,
								type: "move",
								event: e2,
								delta: {
									locationX: e2.locationX - e1.locationX,
									locationY: e2.locationY - e1.locationY,
									pageX: e2.pageX - e1.pageX,
									pageY: e2.pageY - e1.pageY,
									timestamp: e2.timestamp - e1.timestamp
								}
							});
						}
					});
				})
				.subscribe(group => {
					touchMoveComposite.add(group.subscribe());
				})
		);

		touchEndComposite.add(
			touchEnd
				.groupBy(e => e.identifier)
				.map(group => {
					return group.map(e => {
						touches.push({ id: group.key, type: "end", event: e });
					});
				})
				.subscribe(group => {
					touchEndComposite.add(group.subscribe());
				})
		);

		touchPressComposite.add(
			touchPress
				.groupBy(e => e.identifier)
				.map(group => {
					return group.map(e => {
						touches.push({
							id: group.key,
							type: "press",
							event: e
						});
					});
				})
				.subscribe(group => {
					touchPressComposite.add(group.subscribe());
				})
		);

		longTouchComposite.add(
			longTouch
				.groupBy(e => e.identifier)
				.map(group => {
					return group.map(e => {
						touches.push({
							id: group.key,
							type: "long-press",
							event: e
						});
					});
				})
				.subscribe(group => {
					longTouchComposite.add(group.subscribe());
				})
		);

		return {
			process(type, event) {
				switch (type) {
					case "start":
						touchStart.onNext(event);
						break;
					case "move":
						touchMove.onNext(event);
						break;
					case "end":
						touchEnd.onNext(event);
						break;
				}
			},
			end() {
				touchStart.dispose();
				touchMove.dispose();
				touchEnd.dispose();

				touchStartComposite.dispose();
				touchMoveComposite.dispose();
				touchEndComposite.dispose();
				touchPressComposite.dispose();
				longTouchComposite.dispose();
			}
		};
	};
};
<|endoftext|>


<commit_before>import React from 'react';
import { STATISTIC_ORDER } from 'Main/StatisticBox';
import ExpandableStatisticBox from 'Main/ExpandableStatisticBox';
import { formatPercentage } from 'common/format';
import SpellIcon from 'common/SpellIcon';
import SpellLink from 'common/SpellLink';

import SPELLS from 'common/SPELLS';
import Module from 'Parser/Core/Module';
import Combatants from 'Parser/Core/Modules/Combatants';

import SuggestionThresholds from '../../SuggestionThresholds';

const debug = false;

const FLOURISH_EXTENSION_SECONDS = 6;

// These weights are based on the %SP per second the HoT is expected to do, based on tooltip
const WG_WEIGHT = 34;
const CW_WEIGHT = 110;
const REJUV_WEIGHT = 20 * 1.15; // the +15% from first artifact point
const REGROWTH_WEIGHT = 5;
const LB_WEIGHT = 25
const SB_WEIGHT = 10;

// TODO: Idea - Give suggestions on low amount/duration extended with flourish on other HoTs
class Flourish extends Module {
  static dependencies = {
    combatants: Combatants,
  };

  flourishCount = 0;
  wildGrowth = 0;
  wildGrowthCasts = 0;
  rejuvenation = 0;
  regrowth = 0;
  cultivation = 0;
  cenarionWard = 0;
  lifebloom = 0;
  springBlossoms = 0;

  flourishes = [];

  hasCenarionWard = false;

  on_initialized() {
    this.active = this.combatants.selected.hasTalent(SPELLS.FLOURISH_TALENT.id);
    this.hasCenarionWard =  this.combatants.selected.hasTalent(SPELLS.CENARION_WARD_TALENT.id);
  }

  on_byPlayer_cast(event) {
    const spellId = event.ability.guid;
    if (SPELLS.FLOURISH.id !== spellId) {
      return;
    }

    debug && console.log(`Flourish cast #: ${this.flourishCount}`);
    this.flourishCount += 1;

    const wgCount = this._hotCount(SPELLS.WILD_GROWTH.id, event.timestamp);
    this.wildGrowth += wgCount;
    if(wgCount > 0) {
      this.wildGrowthCasts += 1;
    }

    const rejuvCount = this._hotCount(SPELLS.REJUVENATION.id, event.timestamp);
    this.rejuvenation += rejuvCount;

    const germCount = this._hotCount(SPELLS.REJUVENATION_GERMINATION.id, event.timestamp);
    this.rejuvenation += germCount;

    const rgCount = this._hotCount(SPELLS.REGROWTH.id, event.timestamp);
    this.regrowth += rgCount;

    const lbCount = this._hotCount(SPELLS.LIFEBLOOM_HOT_HEAL.id, event.timestamp);
    this.lifebloom += lbCount;

    const sbCount = this._hotCount(SPELLS.SPRING_BLOSSOMS.id, event.timestamp);
    this.springBlossoms += sbCount;

    const cwCount = this._hotCount(SPELLS.CENARION_WARD.id, event.timestamp);
    this.cenarionWard += cwCount;

    const cultCount = this._hotCount(SPELLS.CULTIVATION.id, event.timestamp);
    this.cultivation += cultCount;

    // Due to cultivation's refresh mechanic, we don't count it in total
    const totalCount = wgCount + rejuvCount + germCount + rgCount + lbCount + sbCount + cwCount;
    const weightedPowerPerSecond = (wgCount * WG_WEIGHT) + (rejuvCount * REJUV_WEIGHT) + (germCount * REJUV_WEIGHT) +
        (rgCount * REGROWTH_WEIGHT) + (lbCount * LB_WEIGHT) + (sbCount * SB_WEIGHT) + (cwCount * CW_WEIGHT);
    const weightedPower = weightedPowerPerSecond * FLOURISH_EXTENSION_SECONDS;
    this.flourishes.push({ 'count': totalCount, 'weightedPower': weightedPower });
  }

  _hotCount(hotId, timestamp) {
    return Object.values(this.combatants.players)
        .reduce((total, player) => total += (player.hasBuff(hotId, timestamp, 0, 0) ? 1 : 0), 0);
  }

  suggestions(when) {
    if(this.flourishCount === 0) {
      return;
    }

    const wgsExtended = this.wildGrowthCasts / this.flourishCount;
    when(wgsExtended).isLessThan(SuggestionThresholds.FLOURISH_WG_EXTEND.minor)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest(Your <SpellLink id={SPELLS.FLOURISH.id} /> should always aim to extend a <SpellLink id={SPELLS.WILD_GROWTH.id} />)
          .icon(SPELLS.FLOURISH.icon)
          .actual(`${formatPercentage(this.wildGrowthCasts / this.flourishCount, 0)}% WGs extended.`)
          .recommended(`${formatPercentage(recommended)}% is recommended`)
          .regular(SuggestionThresholds.FLOURISH_WG_EXTEND.regular).major(SuggestionThresholds.FLOURISH_WG_EXTEND.major);
      });

    if(this.hasCenarionWard) {
      const cwsExtended = this.cenarionWard / this.flourishCount;
      when(cwsExtended).isLessThan(SuggestionThresholds.FLOURISH_CW_EXTEND.minor)
        .addSuggestion((suggest, actual, recommended) => {
          return suggest(Your <SpellLink id={SPELLS.FLOURISH.id} /> should always aim to extend a <SpellLink id={SPELLS.CENARION_WARD.id} />)
            .icon(SPELLS.FLOURISH.icon)
            .actual(`${this.cenarionWard}/${this.flourishCount} CWs extended.`)
            .recommended(`${formatPercentage(recommended)}% is recommended`)
            .regular(SuggestionThresholds.FLOURISH_CW_EXTEND.regular).major(SuggestionThresholds.FLOURISH_CW_EXTEND.major);
        });
    }
  }

  statistic() {
    const totalHotsExtended = this.flourishes.reduce((total, flourish) => total += flourish.count, 0);
    const avgHotsExtended = (totalHotsExtended / this.flourishCount) || 0;

    return(
      <ExpandableStatisticBox
        icon={<SpellIcon id={SPELLS.FLOURISH_TALENT.id} />}
        value={`${avgHotsExtended.toFixed(1)}`}
        label="Average HoTs Extended"
        tooltip={
          `The average and per Flourish counts do not include Cultivation due to its refresh mechanic.

          Your ${this.flourishCount} Flourish casts extended:

${this.wildGrowthCasts}/${this.flourishCount} Wild Growth casts (${this.wildGrowth} HoTs)

            ${this.hasCenarionWard
              ? `<li>${this.cenarionWard}/${this.flourishCount} Cenarion Wards</li>`
              : ``
            }
            ${this.rejuvenation > 0
              ? `<li>${this.rejuvenation} Rejuvenations</li>`
              : ``
            }
            ${this.regrowth > 0
              ? `<li>${this.regrowth} Regrowths</li>`
              : ``
            }
            ${this.lifebloom > 0
              ? `<li>${this.lifebloom} Lifebloom</li>`
              : ``
            }
            ${this.springBlossoms > 0
              ? `<li>${this.springBlossoms} Spring Blossoms</li>`
              : ``
            }
            ${this.cultivation > 0
              ? `<li>${this.cultivation} Cultivations (not counted in total)</li>`
              : ``
            }

          The 'HoT Power' column gives an indication of the total power of extending each HoT by 6 seconds, expressed as a percentage of your spell power. This value considers only the base power of each HoT (and the +15% to rejuvenation from the first point in your artifact weapon). It does not factor in overhealing or any of your secondary stats.`
        }
      >

              Cast
              # of HoTs
              HoT Power

            {
              this.flourishes.map((element, index) => (

                  { index + 1 }
                  { element.count }
                  { element.weightedPower }% SP

              ))
            }

      </ExpandableStatisticBox>
    );
  }
  statisticOrder = STATISTIC_ORDER.OPTIONAL();

}

export default Flourish;
<commit_msg>missing semi<commit_after>import React from 'react';
import { STATISTIC_ORDER } from 'Main/StatisticBox';
import ExpandableStatisticBox from 'Main/ExpandableStatisticBox';
import { formatPercentage } from 'common/format';
import SpellIcon from 'common/SpellIcon';
import SpellLink from 'common/SpellLink';

import SPELLS from 'common/SPELLS';
import Module from 'Parser/Core/Module';
import Combatants from 'Parser/Core/Modules/Combatants';

import SuggestionThresholds from '../../SuggestionThresholds';

const debug = false;

const FLOURISH_EXTENSION_SECONDS = 6;

// These weights are based on the %SP per second the HoT is expected to do, based on tooltip
const WG_WEIGHT = 34;
const CW_WEIGHT = 110;
const REJUV_WEIGHT = 20 * 1.15; // the +15% from first artifact point
const REGROWTH_WEIGHT = 5;
const LB_WEIGHT = 25;
const SB_WEIGHT = 10;

// TODO: Idea - Give suggestions on low amount/duration extended with flourish on other HoTs
class Flourish extends Module {
  static dependencies = {
    combatants: Combatants,
  };

  flourishCount = 0;
  wildGrowth = 0;
  wildGrowthCasts = 0;
  rejuvenation = 0;
  regrowth = 0;
  cultivation = 0;
  cenarionWard = 0;
  lifebloom = 0;
  springBlossoms = 0;

  flourishes = [];

  hasCenarionWard = false;

  on_initialized() {
    this.active = this.combatants.selected.hasTalent(SPELLS.FLOURISH_TALENT.id);
    this.hasCenarionWard =  this.combatants.selected.hasTalent(SPELLS.CENARION_WARD_TALENT.id);
  }

  on_byPlayer_cast(event) {
    const spellId = event.ability.guid;
    if (SPELLS.FLOURISH.id !== spellId) {
      return;
    }

    debug && console.log(`Flourish cast #: ${this.flourishCount}`);
    this.flourishCount += 1;

    const wgCount = this._hotCount(SPELLS.WILD_GROWTH.id, event.timestamp);
    this.wildGrowth += wgCount;
    if(wgCount > 0) {
      this.wildGrowthCasts += 1;
    }

    const rejuvCount = this._hotCount(SPELLS.REJUVENATION.id, event.timestamp);
    this.rejuvenation += rejuvCount;

    const germCount = this._hotCount(SPELLS.REJUVENATION_GERMINATION.id, event.timestamp);
    this.rejuvenation += germCount;

    const rgCount = this._hotCount(SPELLS.REGROWTH.id, event.timestamp);
    this.regrowth += rgCount;

    const lbCount = this._hotCount(SPELLS.LIFEBLOOM_HOT_HEAL.id, event.timestamp);
    this.lifebloom += lbCount;

    const sbCount = this._hotCount(SPELLS.SPRING_BLOSSOMS.id, event.timestamp);
    this.springBlossoms += sbCount;

    const cwCount = this._hotCount(SPELLS.CENARION_WARD.id, event.timestamp);
    this.cenarionWard += cwCount;

    const cultCount = this._hotCount(SPELLS.CULTIVATION.id, event.timestamp);
    this.cultivation += cultCount;

    // Due to cultivation's refresh mechanic, we don't count it in total
    const totalCount = wgCount + rejuvCount + germCount + rgCount + lbCount + sbCount + cwCount;
    const weightedPowerPerSecond = (wgCount * WG_WEIGHT) + (rejuvCount * REJUV_WEIGHT) + (germCount * REJUV_WEIGHT) +
        (rgCount * REGROWTH_WEIGHT) + (lbCount * LB_WEIGHT) + (sbCount * SB_WEIGHT) + (cwCount * CW_WEIGHT);
    const weightedPower = weightedPowerPerSecond * FLOURISH_EXTENSION_SECONDS;
    this.flourishes.push({ 'count': totalCount, 'weightedPower': weightedPower });
  }

  _hotCount(hotId, timestamp) {
    return Object.values(this.combatants.players)
        .reduce((total, player) => total += (player.hasBuff(hotId, timestamp, 0, 0) ? 1 : 0), 0);
  }

  suggestions(when) {
    if(this.flourishCount === 0) {
      return;
    }

    const wgsExtended = this.wildGrowthCasts / this.flourishCount;
    when(wgsExtended).isLessThan(SuggestionThresholds.FLOURISH_WG_EXTEND.minor)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest(Your <SpellLink id={SPELLS.FLOURISH.id} /> should always aim to extend a <SpellLink id={SPELLS.WILD_GROWTH.id} />)
          .icon(SPELLS.FLOURISH.icon)
          .actual(`${formatPercentage(this.wildGrowthCasts / this.flourishCount, 0)}% WGs extended.`)
          .recommended(`${formatPercentage(recommended)}% is recommended`)
          .regular(SuggestionThresholds.FLOURISH_WG_EXTEND.regular).major(SuggestionThresholds.FLOURISH_WG_EXTEND.major);
      });

    if(this.hasCenarionWard) {
      const cwsExtended = this.cenarionWard / this.flourishCount;
      when(cwsExtended).isLessThan(SuggestionThresholds.FLOURISH_CW_EXTEND.minor)
        .addSuggestion((suggest, actual, recommended) => {
          return suggest(Your <SpellLink id={SPELLS.FLOURISH.id} /> should always aim to extend a <SpellLink id={SPELLS.CENARION_WARD.id} />)
            .icon(SPELLS.FLOURISH.icon)
            .actual(`${this.cenarionWard}/${this.flourishCount} CWs extended.`)
            .recommended(`${formatPercentage(recommended)}% is recommended`)
            .regular(SuggestionThresholds.FLOURISH_CW_EXTEND.regular).major(SuggestionThresholds.FLOURISH_CW_EXTEND.major);
        });
    }
  }

  statistic() {
    const totalHotsExtended = this.flourishes.reduce((total, flourish) => total += flourish.count, 0);
    const avgHotsExtended = (totalHotsExtended / this.flourishCount) || 0;

    return(
      <ExpandableStatisticBox
        icon={<SpellIcon id={SPELLS.FLOURISH_TALENT.id} />}
        value={`${avgHotsExtended.toFixed(1)}`}
        label="Average HoTs Extended"
        tooltip={
          `The average and per Flourish counts do not include Cultivation due to its refresh mechanic.

          Your ${this.flourishCount} Flourish casts extended:

${this.wildGrowthCasts}/${this.flourishCount} Wild Growth casts (${this.wildGrowth} HoTs)

            ${this.hasCenarionWard
              ? `<li>${this.cenarionWard}/${this.flourishCount} Cenarion Wards</li>`
              : ``
            }
            ${this.rejuvenation > 0
              ? `<li>${this.rejuvenation} Rejuvenations</li>`
              : ``
            }
            ${this.regrowth > 0
              ? `<li>${this.regrowth} Regrowths</li>`
              : ``
            }
            ${this.lifebloom > 0
              ? `<li>${this.lifebloom} Lifebloom</li>`
              : ``
            }
            ${this.springBlossoms > 0
              ? `<li>${this.springBlossoms} Spring Blossoms</li>`
              : ``
            }
            ${this.cultivation > 0
              ? `<li>${this.cultivation} Cultivations (not counted in total)</li>`
              : ``
            }

          The 'HoT Power' column gives an indication of the total power of extending each HoT by 6 seconds, expressed as a percentage of your spell power. This value considers only the base power of each HoT (and the +15% to rejuvenation from the first point in your artifact weapon). It does not factor in overhealing or any of your secondary stats.`
        }
      >

              Cast
              # of HoTs
              HoT Power

            {
              this.flourishes.map((element, index) => (

                  { index + 1 }
                  { element.count }
                  { element.weightedPower }% SP

              ))
            }

      </ExpandableStatisticBox>
    );
  }
  statisticOrder = STATISTIC_ORDER.OPTIONAL();

}

export default Flourish;
<|endoftext|>


<commit_before>export class Originals {
  constructor() {
    this.urls = [['https://dl.dropboxusercontent.com/s/h7xolhpjst6spbd/Dark%20Light.mp3?dl=0', 'Dark Light - Josh & Maria Sherman'],
      ['https://dl.dropboxusercontent.com/s/3tisiordjrgjkoq/Good%20Enough.mp3?dl=0', 'Good Enough - Josh & Maria Sherman'],
      ['https://dl.dropboxusercontent.com/s/ceqa0bk1swccgs2/Don%27t%20Go.mp3?dl=0', 'Don\'t Go - Web Jam Band'],
      ['MRM.mp3', 'Misty Rainy Morning - Web Jam Band'],
      ['AT.mp3', 'Alone Time - Web Jam Band'],
      ['TTGA.mp3', 'Try to Get Along - Web Jam Band'],
      [`https://www.youtube.com/embed/ach2ubW21h4?origin=http://${document.location.host}`, 'Boogie Board Rash - Josh Sherman'],
      [`https://www.youtube.com/embed/mCvUBjuzfo8?origin=http://${document.location.host}`, 'Hey Red - Josh Sherman'],
      ['https://soundcloud.com/joshandmariamusic/good-enough', 'Good Enough - Josh & Maria Sherman']];

    this._urls = [['https://dl.dropboxusercontent.com/s/h7xolhpjst6spbd/Dark%20Light.mp3?dl=0', 'Dark Light - Josh & Maria Sherman'],
      ['https://dl.dropboxusercontent.com/s/3tisiordjrgjkoq/Good%20Enough.mp3?dl=0', 'Good Enough - Josh & Maria Sherman'],
      ['https://dl.dropboxusercontent.com/s/ceqa0bk1swccgs2/Don%27t%20Go.mp3?dl=0', 'Don\'t Go - Web Jam Band'],
      ['MRM.mp3', 'Misty Rainy Morning - Web Jam Band'],
      ['AT.mp3', 'Alone Time - Web Jam Band'],
      ['TTGA.mp3', 'Try to Get Along - Web Jam Band'],
      [`https://www.youtube.com/embed/ach2ubW21h4?origin=http://${document.location.host}`, 'Boogie Board Rash - Josh Sherman'],
      [`https://www.youtube.com/embed/mCvUBjuzfo8?origin=http://${document.location.host}`, 'Hey Red - Josh Sherman'],
      ['https://soundcloud.com/joshandmariamusic/good-enough', 'Good Enough - Josh & Maria Sherman']];
  }
}
<commit_msg>originals music sourced from config<commit_after>import data from '../../config.json';

export class Originals {
  urls = data.original.urls;
  _urls = data.original._urls;
}
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import _ from 'lodash';
import { Dropdown, Icon } from 'semantic-ui-react';
import { useTranslation } from 'react-i18next';
import { compose, withHandlers, withProps } from 'recompose';
import { sendPaused } from '../../../../mpris';
import styles from './styles.scss';

const addTrackToPlaylist = (updatePlaylist, playlist, track) => {
  if (track.name) {
    playlist.tracks.push(track);
    updatePlaylist(playlist);
  }
};

export const QueueMenuMore = ({
  disabled,
  clearQueue,
  resetPlayer,
  savePlaylistDialog,
  addFavoriteTrack,
  updatePlaylist,
  playlists,
  currentItem,
  sendPaused,
  handleAddToDownloads,
  handleAddFavoriteTrack
}) => {
  const { t } = useTranslation('queue');

  return (
    <Dropdown item icon='ellipsis vertical' className={styles.queue_menu_more} disabled={disabled}>
      <Dropdown.Menu>
        <Dropdown.Header>{t('header')}</Dropdown.Header>
        <Dropdown.Item onClick={() => {clearQueue();resetPlayer(sendPaused);}}>
          <Icon name='trash' />
          {t('clear')}
        </Dropdown.Item>
        {savePlaylistDialog}
        <Dropdown.Divider />

        <Dropdown.Header>{t('header-track')}</Dropdown.Header>
        <Dropdown.Item>
          <Dropdown text={t('playlist-add')} className='left'>
            <Dropdown.Menu className={cx('left', styles.playlists_menu)}>
              {_.map(playlists, (playlist, i) => {
                return (
                  <Dropdown.Item
                    key={i}
                    onClick={() =>
                      addTrackToPlaylist(updatePlaylist, playlist, currentItem)
                    }
                  >
                    <Icon name='music' />
                    {playlist.name}
                  </Dropdown.Item>
                );
              })}
            </Dropdown.Menu>
          </Dropdown>
        </Dropdown.Item>
        <Dropdown.Item
          onClick={handleAddFavoriteTrack}
        >
          <Icon name='star' />
          {t('favorite-add')}
        </Dropdown.Item>
        <Dropdown.Item onClick={handleAddToDownloads}>
          <Icon name='download' />
          {t('download')}
        </Dropdown.Item>
      </Dropdown.Menu>
    </Dropdown>
  );
};

QueueMenuMore.propTypes = {
  disabled: PropTypes.bool,
  clearQueue: PropTypes.func,
  addFavoriteTrack: PropTypes.func,
  addToDownloads: PropTypes.func,
  updatePlaylist: PropTypes.func,
  savePlaylistDialog: PropTypes.node,
  playlists: PropTypes.array,
  currentItem: PropTypes.object
};

QueueMenuMore.defaultProps = {
  disabled: true,
  clearQueue: () => {},
  addFavoriteTrack: () => {},
  addToDownloads: () => {},
  updatePlaylist: () => {},
  savePlaylistDialog: null,
  playlists: [],
  currentItem: {}
};

export default compose(
  withHandlers({
    handleAddToDownloads: ({addToDownloads, currentItem}) => () => addToDownloads(currentItem),
    handleAddFavoriteTrack: ({addFavoriteTrack, currentItem}) => () => {
      if (currentItem.name) {
        addFavoriteTrack({
          artist: {
            name: currentItem.artist
          },
          name: currentItem.name,
          image: [
            {
              '#text': currentItem.thumbnail
            }
          ]
        });
      }
    }
  }),
  withProps({
    sendPaused: sendPaused
  })
)(QueueMenuMore);
<commit_msg>updated component defaults<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import _ from 'lodash';
import { Dropdown, Icon } from 'semantic-ui-react';
import { useTranslation } from 'react-i18next';
import { compose, withHandlers, withProps } from 'recompose';
import { sendPaused } from '../../../../mpris';
import styles from './styles.scss';

const addTrackToPlaylist = (updatePlaylist, playlist, track) => {
  if (track.name) {
    playlist.tracks.push(track);
    updatePlaylist(playlist);
  }
};

export const QueueMenuMore = ({
  disabled,
  clearQueue,
  resetPlayer,
  savePlaylistDialog,
  addFavoriteTrack,
  updatePlaylist,
  playlists,
  currentItem,
  sendPaused,
  handleAddToDownloads,
  handleAddFavoriteTrack
}) => {
  const { t } = useTranslation('queue');

  return (
    <Dropdown item icon='ellipsis vertical' className={styles.queue_menu_more} disabled={disabled}>
      <Dropdown.Menu>
        <Dropdown.Header>{t('header')}</Dropdown.Header>
        <Dropdown.Item onClick={() => {clearQueue();resetPlayer(sendPaused);}}>
          <Icon name='trash' />
          {t('clear')}
        </Dropdown.Item>
        {savePlaylistDialog}
        <Dropdown.Divider />

        <Dropdown.Header>{t('header-track')}</Dropdown.Header>
        <Dropdown.Item>
          <Dropdown text={t('playlist-add')} className='left'>
            <Dropdown.Menu className={cx('left', styles.playlists_menu)}>
              {_.map(playlists, (playlist, i) => {
                return (
                  <Dropdown.Item
                    key={i}
                    onClick={() =>
                      addTrackToPlaylist(updatePlaylist, playlist, currentItem)
                    }
                  >
                    <Icon name='music' />
                    {playlist.name}
                  </Dropdown.Item>
                );
              })}
            </Dropdown.Menu>
          </Dropdown>
        </Dropdown.Item>
        <Dropdown.Item
          onClick={handleAddFavoriteTrack}
        >
          <Icon name='star' />
          {t('favorite-add')}
        </Dropdown.Item>
        <Dropdown.Item onClick={handleAddToDownloads}>
          <Icon name='download' />
          {t('download')}
        </Dropdown.Item>
      </Dropdown.Menu>
    </Dropdown>
  );
};

QueueMenuMore.propTypes = {
  disabled: PropTypes.bool,
  clearQueue: PropTypes.func,
  resetPlayer: PropTypes.func,
  addFavoriteTrack: PropTypes.func,
  addToDownloads: PropTypes.func,
  updatePlaylist: PropTypes.func,
  savePlaylistDialog: PropTypes.node,
  playlists: PropTypes.array,
  currentItem: PropTypes.object
};

QueueMenuMore.defaultProps = {
  disabled: true,
  clearQueue: () => {},
  resetPlayer: () => {},
  addFavoriteTrack: () => {},
  addToDownloads: () => {},
  updatePlaylist: () => {},
  savePlaylistDialog: null,
  playlists: [],
  currentItem: {}
};

export default compose(
  withHandlers({
    handleAddToDownloads: ({addToDownloads, currentItem}) => () => addToDownloads(currentItem),
    handleAddFavoriteTrack: ({addFavoriteTrack, currentItem}) => () => {
      if (currentItem.name) {
        addFavoriteTrack({
          artist: {
            name: currentItem.artist
          },
          name: currentItem.name,
          image: [
            {
              '#text': currentItem.thumbnail
            }
          ]
        });
      }
    }
  }),
  withProps({
    sendPaused: sendPaused
  })
)(QueueMenuMore);
<|endoftext|>


<commit_before>/*
 * This program is part of the OpenLMIS logistics management information system platform software.
 * Copyright © 2017 VillageReach
 *
 * This program 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, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details. You should have received a copy of
 * the GNU Affero General Public License along with this program. If not, see
 * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.
 */

(function() {

    'use strict';

    /**
     * @ngdoc service
     * @name openlmis-repository.OpenlmisResource
     *
     * @description
     * Generic repository for communicating with the OpenLMIS RESTful endpoints.
     */
    angular
        .module('openlmis-repository')
        .factory('OpenlmisResource', OpenlmisResource);

    OpenlmisResource.$inject = ['$resource', '$q', 'MAX_URI_LENGTH', 'openlmisUrlFactory', 'ParameterSplitter'];

    function OpenlmisResource($resource, $q, MAX_URI_LENGTH, openlmisUrlFactory, ParameterSplitter) {

        OpenlmisResource.prototype.query = query;
        OpenlmisResource.prototype.get = get;
        OpenlmisResource.prototype.getAll = getAll;
        OpenlmisResource.prototype.update = update;
        OpenlmisResource.prototype.create = create;
        OpenlmisResource.prototype.delete = deleteObject;
        OpenlmisResource.prototype.search = search;
        OpenlmisResource.prototype.throwMethodNotSupported = throwMethodNotSupported;

        return OpenlmisResource;

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name OpenlmisResource
         * @constructor
         *
         * @description
         * Creates an instance of the OpenlmisResource class.
         *
         * Configuration options:
         * - paginated - flag defining whether response returned by the query request is paginated; defaults to true
         *
         * @param {String} uri    the URI pointing to the resource
         * @param {Object} config the optional configuration object, modifies the default behavior making this class
         *                        more flexible
         */
        function OpenlmisResource(uri, config) {
            this.uri = uri;
            this.config = config;
            var resourceUrl = uri;

            if (uri.slice(-1) === '/') {
                resourceUrl = uri.slice(0, -1);
            }

            resourceUrl = openlmisUrlFactory(resourceUrl);
            this.resourceUrl = resourceUrl;
            this.lastModified = undefined;

            this.resource = $resource(resourceUrl + '/:id', {}, {
                query: {
                    url: resourceUrl,
                    isArray: !isPaginated(config)
                },
                update: {
                    method: 'PUT'
                }
            });

            this.idParam = getIdParam(config);
            this.splitter = new ParameterSplitter();
            this.isVersioned = isVersioned(config);
        }

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name get
         *
         * @description
         * Retrieves an object with the given ID.
         *
         * @param  {string}  id            the ID of the object
         * @param  {string}  versionNumber the optional version number of the object
         * @return {Promise}               the promise resolving to server response,
         *                                 rejects if ID is not given or if the request fails
         */
        function get(id, versionNumber) {
            if (id) {
                var resourceUrl = this.resourceUrl,
                    params = {
                        versionNumber: versionNumber
                    },
                    resource = this.resource;

                if (this.config && this.config.cache) {
                    resource = generateResource(resourceUrl, this.lastModified, this.config, params);
                }
                return resource.get({
                    id: id
                }).$promise;
            }
            return $q.reject();
        }

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name getAll
         *
         * @description
         * Return the response of the GET request in a form of a list. Passes the given object as request parameters.
         *
         * @param  {Object}  params the map of request parameters
         * @return {Promise}        the promise resolving to the server response, rejected if request fails
         */
        function getAll(params) {
            var config = this.config;
            return this.query(params)
                .then(function(response) {
                    return isPaginated(config) ? response.content : response;
                });
        }

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name query
         *
         * @description
         * Return the response of the GET request. Passes the given object as request parameters.
         *
         * @param  {Object}  params the map of request parameters
         * @return {Promise}        the promise resolving to the server response, rejected if request fails
         */
        function query(params) {
            var requests = [],
                resourceUrl = this.resourceUrl,
                config = this.config,
                resource = this.resource,
                lastModified = this.lastModified;

            this.splitter.split(this.uri, params)
                .forEach(function(params) {
                    if (config && config.cache && (!params || !isPageAndSortParams(params))) {
                        resource = generateResource(resourceUrl, lastModified, config, params);
                    }
                    requests.push(resource.query(params).$promise);
                });

            return $q.all(requests)
                .then(function(responses) {
                    if (responses[0] instanceof Array) {
                        return responses.reduce(function(left, right) {
                            right.forEach(function(item) {
                                left.push(item);
                            });
                            return left;
                        });
                    }

                    return responses.reduce(function(left, right) {
                        if (left.content.content) {
                            left.content = shortenMerge(left.content, right.content);
                        } else {
                            left = shortenMerge(left, right);
                        }
                        return left;
                    });
                });
        }

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name update
         *
         * @description
         * Saves the given object on the OpenLMIS server. Uses PUT method.
         *
         * @param  {Object}  object the object to be saved on the server
         * @return {Promise}        the promise resolving to the server response, rejected if request fails or object is
         *                          undefined or if the ID is undefined
         */
        function update(object) {
            if (object) {
                var resourceUrl = this.resourceUrl,
                    resource = this.resource;

                if (this.config && this.config.cache) {
                    resource = generateResource(resourceUrl, undefined, this.config, undefined);
                }
                return resource.update({
                    id: object[this.idParam]
                }, object).$promise;
            }
            return $q.reject();
        }

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name create
         *
         * @description
         * Creates the given object on the OpenLMIS server. Uses POST method.
         *
         * @param  {Object}  object        the object to be created on the server
         * @param  {Object}  customParams  the custom parameters to be passed to the request
         * @return {Promise}               the promise resolving to the server response, rejected if request fails
         */
        function create(object, customParams) {
            var resourceUrl = this.resourceUrl,
                resource = this.resource;

            if (this.config && this.config.cache) {
                resource = generateResource(resourceUrl, undefined, this.config, customParams);
            }
            return resource.save(customParams, object).$promise;
        }

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name search
         *
         * @description
         * Search the given objects on the OpenLMIS server. Uses POST method.
         *
         * @param  {Object}  objectsList   the objects list to be searched on the server
         * @return {Promise}               the promise resolving to the server response, rejected if request fails
         */
        function search(objectsList) {
            if (objectsList) {
                var resourceUrl = this.resourceUrl;
                var resource = generateResource(resourceUrl, undefined, this.config, undefined);

                return resource.search(undefined, objectsList).$promise;
            }
            return $q.reject();
        }

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name delete
         *
         * @description
         * Deletes the object on the OpenLMIS server.
         *
         * @param  {Object}  object the object to be deleted from the server
         * @return {Promise}        the promise resolving to the server response, rejected if request fails or object is
         *                          undefined or if the ID is undefined
         */
        function deleteObject(object) {
            var resourceUrl = this.resourceUrl,
                resource = this.resource;

            if (this.config && this.config.cache) {
                resource = generateResource(resourceUrl, {}, this.config, undefined);
            }
            if (object && object[this.idParam]) {
                return resource.delete({
                    id: object[this.idParam]
                }).$promise;
            }
            return $q.reject();
        }

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name throwMethodNotSupported
         *
         * @description
         * Throws 'Method not supported' exception. Useful for overriding methods which are not supported by specific
         * endpoint.
         */
        function throwMethodNotSupported() {
            throw 'Method not supported';
        }

        function isPaginated(config) {
            return !config || config.paginated || config.paginated === undefined;
        }

        function getIdParam(config) {
            if (config) {
                return config.idParam || 'id';
            }
            return 'id';
        }

        function generateResource(resourceUrl, headerValue, config, params) {
            var header = {};

            if (headerValue) {
                header['If-Modified-Since'] = headerValue;
            }

            return $resource(resourceUrl + '/:id', params, {
                get: {
                    method: 'GET',
                    transformResponse: function(data, headers, status) {
                        return transformResponse(data, headers, status, config);
                    },
                    headers: header,
                    isArray: !isPaginated(config)
                },
                query: {
                    url: resourceUrl,
                    method: 'GET',
                    transformResponse: function(data, headers, status) {
                        return transformResponse(data, headers, status, config);
                    },
                    headers: header,
                    isArray: !isPaginated(config)
                },
                update: {
                    method: 'PUT',
                    transformResponse: function(data, headers, status) {
                        return transformResponse(data, headers, status, config);
                    }
                },
                save: {
                    method: 'POST',
                    transformResponse: function(data, headers, status) {
                        return transformResponse(data, headers, status, config);
                    }
                },
                search: {
                    method: 'POST',
                    transformResponse: function(data, headers, status) {
                        return transformResponse(data, headers, status, config);
                    }
                },
                delete: {
                    method: 'DELETE',
                    transformResponse: function(data, headers, status) {
                        return transformResponse(data, headers, status, config);
                    }
                }
            });
        }

        function transformJson(data) {
            if (data) {
                return angular.fromJson(data);
            }
            return data;
        }

        function transformResponse(data, headers, status, config) {
            var response = {};
            data = transformJson(data);
            response.content = data;

            if (response.content) {
                if (response.content.content) {
                    response.content.content.forEach(function(doc) {
                        doc.lastModified = headers('last-modified');
                        doc._id = isVersioned(config) ? doc.id + '/' + doc.meta.versionNumber : doc.id;
                    });
                } else if (response.content instanceof Array) {
                    response.content.forEach(function(doc) {
                        doc.lastModified = headers('last-modified');
                        doc._id = isVersioned(config) ? doc.id + '/'
                            + doc.meta.versionNumber : doc.id;
                    });
                } else {
                    response.content.lastModified = headers('last-modified');
                    response.content._id = isVersioned(config) ? response.content.id + '/'
                        + response.content.meta.versionNumber : response.content.id;
                }
                response.lastModified = headers('last-modified');
            }
            response.status = status;
            return response;
        }

        function isVersioned(config) {
            return config && config.versioned;
        }

        function isPageAndSortParams(params) {
            return params.sort !== undefined && params.page !== undefined;
        }

        function shortenMerge(left, right) {
            left.content = left.content.concat(right.content);
            left.numberOfElements += right.numberOfElements;
            left.totalElements += right.totalElements;
            return left;
        }
    }
})();
<commit_msg>Revert "Revert "OLMIS-6949: Reactored mergeResponse function""<commit_after>/*
 * This program is part of the OpenLMIS logistics management information system platform software.
 * Copyright © 2017 VillageReach
 *
 * This program 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, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details. You should have received a copy of
 * the GNU Affero General Public License along with this program. If not, see
 * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.
 */

(function() {

    'use strict';

    /**
     * @ngdoc service
     * @name openlmis-repository.OpenlmisResource
     *
     * @description
     * Generic repository for communicating with the OpenLMIS RESTful endpoints.
     */
    angular
        .module('openlmis-repository')
        .factory('OpenlmisResource', OpenlmisResource);

    OpenlmisResource.$inject = ['$resource', '$q', 'MAX_URI_LENGTH', 'openlmisUrlFactory', 'ParameterSplitter'];

    function OpenlmisResource($resource, $q, MAX_URI_LENGTH, openlmisUrlFactory, ParameterSplitter) {

        OpenlmisResource.prototype.query = query;
        OpenlmisResource.prototype.get = get;
        OpenlmisResource.prototype.getAll = getAll;
        OpenlmisResource.prototype.update = update;
        OpenlmisResource.prototype.create = create;
        OpenlmisResource.prototype.delete = deleteObject;
        OpenlmisResource.prototype.search = search;
        OpenlmisResource.prototype.throwMethodNotSupported = throwMethodNotSupported;

        return OpenlmisResource;

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name OpenlmisResource
         * @constructor
         *
         * @description
         * Creates an instance of the OpenlmisResource class.
         *
         * Configuration options:
         * - paginated - flag defining whether response returned by the query request is paginated; defaults to true
         *
         * @param {String} uri    the URI pointing to the resource
         * @param {Object} config the optional configuration object, modifies the default behavior making this class
         *                        more flexible
         */
        function OpenlmisResource(uri, config) {
            this.uri = uri;
            this.config = config;
            var resourceUrl = uri;

            if (uri.slice(-1) === '/') {
                resourceUrl = uri.slice(0, -1);
            }

            resourceUrl = openlmisUrlFactory(resourceUrl);
            this.resourceUrl = resourceUrl;
            this.lastModified = undefined;

            this.resource = $resource(resourceUrl + '/:id', {}, {
                query: {
                    url: resourceUrl,
                    isArray: !isPaginated(config)
                },
                update: {
                    method: 'PUT'
                }
            });

            this.idParam = getIdParam(config);
            this.splitter = new ParameterSplitter();
            this.isVersioned = isVersioned(config);
        }

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name get
         *
         * @description
         * Retrieves an object with the given ID.
         *
         * @param  {string}  id            the ID of the object
         * @param  {string}  versionNumber the optional version number of the object
         * @return {Promise}               the promise resolving to server response,
         *                                 rejects if ID is not given or if the request fails
         */
        function get(id, versionNumber) {
            if (id) {
                var resourceUrl = this.resourceUrl,
                    params = {
                        versionNumber: versionNumber
                    },
                    resource = this.resource;

                if (this.config && this.config.cache) {
                    resource = generateResource(resourceUrl, this.lastModified, this.config, params);
                }
                return resource.get({
                    id: id
                }).$promise;
            }
            return $q.reject();
        }

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name getAll
         *
         * @description
         * Return the response of the GET request in a form of a list. Passes the given object as request parameters.
         *
         * @param  {Object}  params the map of request parameters
         * @return {Promise}        the promise resolving to the server response, rejected if request fails
         */
        function getAll(params) {
            var config = this.config;
            return this.query(params)
                .then(function(response) {
                    return isPaginated(config) ? response.content : response;
                });
        }

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name query
         *
         * @description
         * Return the response of the GET request. Passes the given object as request parameters.
         *
         * @param  {Object}  params the map of request parameters
         * @return {Promise}        the promise resolving to the server response, rejected if request fails
         */
        function query(params) {
            var requests = [],
                resourceUrl = this.resourceUrl,
                config = this.config,
                resource = this.resource,
                lastModified = this.lastModified;

            this.splitter.split(this.uri, params)
                .forEach(function(params) {
                    if (config && config.cache && (!params || !isPageAndSortParams(params))) {
                        resource = generateResource(resourceUrl, lastModified, config, params);
                    }
                    requests.push(resource.query(params).$promise);
                });

            return $q.all(requests)
                .then(function(responses) {
                    if (responses[0] instanceof Array) {
                        return responses.reduce(function(left, right) {
                            right.forEach(function(item) {
                                left.push(item);
                            });
                            return left;
                        });
                    }

                    return responses.reduce(function(left, right) {
                        if (left.content.content) {
                            mergeResponses(left.content, right.content);
                        } else {
                            mergeResponses(left, right);
                        }
                        return left;
                    });
                });
        }

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name update
         *
         * @description
         * Saves the given object on the OpenLMIS server. Uses PUT method.
         *
         * @param  {Object}  object the object to be saved on the server
         * @return {Promise}        the promise resolving to the server response, rejected if request fails or object is
         *                          undefined or if the ID is undefined
         */
        function update(object) {
            if (object) {
                var resourceUrl = this.resourceUrl,
                    resource = this.resource;

                if (this.config && this.config.cache) {
                    resource = generateResource(resourceUrl, undefined, this.config, undefined);
                }
                return resource.update({
                    id: object[this.idParam]
                }, object).$promise;
            }
            return $q.reject();
        }

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name create
         *
         * @description
         * Creates the given object on the OpenLMIS server. Uses POST method.
         *
         * @param  {Object}  object        the object to be created on the server
         * @param  {Object}  customParams  the custom parameters to be passed to the request
         * @return {Promise}               the promise resolving to the server response, rejected if request fails
         */
        function create(object, customParams) {
            var resourceUrl = this.resourceUrl,
                resource = this.resource;

            if (this.config && this.config.cache) {
                resource = generateResource(resourceUrl, undefined, this.config, customParams);
            }
            return resource.save(customParams, object).$promise;
        }

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name search
         *
         * @description
         * Search the given objects on the OpenLMIS server. Uses POST method.
         *
         * @param  {Object}  objectsList   the objects list to be searched on the server
         * @return {Promise}               the promise resolving to the server response, rejected if request fails
         */
        function search(objectsList) {
            if (objectsList) {
                var resourceUrl = this.resourceUrl;
                var resource = generateResource(resourceUrl, undefined, this.config, undefined);

                return resource.search(undefined, objectsList).$promise;
            }
            return $q.reject();
        }

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name delete
         *
         * @description
         * Deletes the object on the OpenLMIS server.
         *
         * @param  {Object}  object the object to be deleted from the server
         * @return {Promise}        the promise resolving to the server response, rejected if request fails or object is
         *                          undefined or if the ID is undefined
         */
        function deleteObject(object) {
            var resourceUrl = this.resourceUrl,
                resource = this.resource;

            if (this.config && this.config.cache) {
                resource = generateResource(resourceUrl, {}, this.config, undefined);
            }
            if (object && object[this.idParam]) {
                return resource.delete({
                    id: object[this.idParam]
                }).$promise;
            }
            return $q.reject();
        }

        /**
         * @ngdoc method
         * @methodOf openlmis-repository.OpenlmisResource
         * @name throwMethodNotSupported
         *
         * @description
         * Throws 'Method not supported' exception. Useful for overriding methods which are not supported by specific
         * endpoint.
         */
        function throwMethodNotSupported() {
            throw 'Method not supported';
        }

        function isPaginated(config) {
            return !config || config.paginated || config.paginated === undefined;
        }

        function getIdParam(config) {
            if (config) {
                return config.idParam || 'id';
            }
            return 'id';
        }

        function generateResource(resourceUrl, headerValue, config, params) {
            var header = {};

            if (headerValue) {
                header['If-Modified-Since'] = headerValue;
            }

            return $resource(resourceUrl + '/:id', params, {
                get: {
                    method: 'GET',
                    transformResponse: function(data, headers, status) {
                        return transformResponse(data, headers, status, config);
                    },
                    headers: header,
                    isArray: !isPaginated(config)
                },
                query: {
                    url: resourceUrl,
                    method: 'GET',
                    transformResponse: function(data, headers, status) {
                        return transformResponse(data, headers, status, config);
                    },
                    headers: header,
                    isArray: !isPaginated(config)
                },
                update: {
                    method: 'PUT',
                    transformResponse: function(data, headers, status) {
                        return transformResponse(data, headers, status, config);
                    }
                },
                save: {
                    method: 'POST',
                    transformResponse: function(data, headers, status) {
                        return transformResponse(data, headers, status, config);
                    }
                },
                search: {
                    method: 'POST',
                    transformResponse: function(data, headers, status) {
                        return transformResponse(data, headers, status, config);
                    }
                },
                delete: {
                    method: 'DELETE',
                    transformResponse: function(data, headers, status) {
                        return transformResponse(data, headers, status, config);
                    }
                }
            });
        }

        function transformJson(data) {
            if (data) {
                return angular.fromJson(data);
            }
            return data;
        }

        function transformResponse(data, headers, status, config) {
            var response = {};
            data = transformJson(data);
            response.content = data;

            if (response.content) {
                if (response.content.content) {
                    response.content.content.forEach(function(doc) {
                        doc.lastModified = headers('last-modified');
                        doc._id = isVersioned(config) ? doc.id + '/' + doc.meta.versionNumber : doc.id;
                    });
                } else if (response.content instanceof Array) {
                    response.content.forEach(function(doc) {
                        doc.lastModified = headers('last-modified');
                        doc._id = isVersioned(config) ? doc.id + '/'
                            + doc.meta.versionNumber : doc.id;
                    });
                } else {
                    response.content.lastModified = headers('last-modified');
                    response.content._id = isVersioned(config) ? response.content.id + '/'
                        + response.content.meta.versionNumber : response.content.id;
                }
                response.lastModified = headers('last-modified');
            }
            response.status = status;
            return response;
        }

        function isVersioned(config) {
            return config && config.versioned;
        }

        function isPageAndSortParams(params) {
            return params.sort !== undefined && params.page !== undefined;
        }

        function mergeResponses(left, right) {
            left.content = left.content.concat(right.content);
            left.numberOfElements += right.numberOfElements;
            left.totalElements += right.totalElements;
        }
    }
})();
<|endoftext|>