<commit_before>'use strict';
/* global d3:false */

/**
 * UI controller for interactive plots
 * @Class
 * @Param {object} config
 * @Param {object} config.buttons    nested arrays of grouped buttons to initialize
 * @Param {object} config.container  container div to append modebar
 * @Param {object} config.Plotly     main plotly namespace module
 * @Param {object} config.graphInfo  primary plot object containing data and layout
 */
function ModeBar (config) {

    if (!(this instanceof ModeBar)) return new ModeBar();

    var _this = this;

    this.Plotly = config.Plotly;
    this.graphInfo = config.graphInfo;
    this.element = document.createElement('div');

    if(this.graphInfo._context.displayModeBar === 'hover') {
        this.element.className = 'modebar modebar--hover';
    } else {
        this.element.className = 'modebar';
    }

    this.buttons = config.buttons;
    this.buttonElements = [];

    this.buttons.forEach( function (buttonGroup) {
        var group = _this.createGroup();

        buttonGroup.forEach( function (buttonName) {
            var buttonConfig = _this.config()[buttonName];

            if (!buttonConfig) {
                throw new Error(buttonName + ' not specfied in modebar configuration');
            }

            var button = _this.createButton(buttonConfig);

            _this.buttonElements.push(button);
            group.appendChild(button);
        });

        _this.element.appendChild(group);
    });

    if (this.graphInfo._context.displaylogo) {
        this.element.appendChild(this.getLogo());
    }

    config.container.appendChild(this.element);

    this.updateActiveButton();

}

/**
 * Empty div for containing a group of buttons
 * @Return {HTMLelement}
 */
ModeBar.prototype.createGroup = function () {
    var group = document.createElement('div');
    group.className = 'modebar-group';

    return group;
};

/**
 * Create a new button div and set constant and configurable attributes
 * @Param {object} config
 * @Param {string} config.attr
 * @Param {string} config.val
 * @Param {string} config.title
 * @Param {function} config.click
 * @Return {HTMLelement}
 */
ModeBar.prototype.createButton = function (config) {
    var button = document.createElement('a');
    var icon = document.createElement('i');
    var _this = this;
    button.setAttribute('rel', 'tooltip');
    button.className = 'modebar-btn plotlyjsicon';

    button.setAttribute('data-attr', config.attr);
    button.setAttribute('data-val', config.val);
    button.setAttribute('data-title', config.title);
    button.setAttribute('data-gravity', config.gravity || 'n');
    button.addEventListener('click', function () {
            config.click.apply(_this, arguments);
        });

    icon.className = config.icon;
    button.appendChild(icon);

    return button;
};

/**
 * Updates active button with attribute specified in layout
 * @Param {object} graphInfo plot object containing data and layout
 * @Return {HTMLelement}
 */
ModeBar.prototype.updateActiveButton = function () {
    var graphInfo = this.graphInfo;
    this.buttonElements.forEach( function (button) {
        var thisval = button.getAttribute('data-val') || true,
            dataAttr = button.getAttribute('data-attr'),
            curval = graphInfo._fullLayout[dataAttr];

        d3.select(button).classed('active', curval===thisval);
    });
};

/**
 * Check if modebar is configured as button configuration argument
 * @Param {object} buttons 2d array of grouped button names
 * @Return {boolean}
 */
ModeBar.prototype.hasButtons = function (buttons) {
    var currentButtons = this.buttons;

    if (buttons.length !== currentButtons.length) return false;

    for (var i = 0; i < buttons.length; ++i) {
        if (buttons[i].length !== currentButtons[i].length) return false;
        for (var j = 0; j < buttons[i].length; j++) {
            if (buttons[i][j] !== currentButtons[i][j]) return false;
        }
    }

    return true;
};

/**
 * @return {HTMLDivElement} The logo image wrapped in a group
 */
ModeBar.prototype.getLogo = function(){
    var group = this.createGroup(),
        a = document.createElement('a'),
        i = document.createElement('i');

    a.href = 'https://plot.ly/';
    a.target = '_blank';
    a.setAttribute('data-title', 'Produced with Plotly');
    a.className = 'modebar-btn plotlyjsicon modebar-btn--logo';

    i.className = 'plotlyjsicon-plotlylogo';
    a.appendChild(i);
    group.appendChild(a);
    return group;
};

/**
 * Apply D3 cartesian mode attributes to layout to update hover functionality
 * @Param {object} ev event object
 */
ModeBar.prototype.handleCartesian = function(ev) {
    var button = ev.currentTarget,
        astr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        _this = this,
        graphInfo = this.graphInfo,
        fullLayout = this.graphInfo._fullLayout,
        Plotly = this.Plotly,
        aobj = {};

    if(astr === 'zoom') {
        var xr = fullLayout.xaxis.range,
            yr = fullLayout.yaxis.range,
            mag = (val==='in') ? 0.5 : 2,
            r0 = (1+mag)/2, r1 = (1-mag)/2;
        aobj = {
            'xaxis.range[0]': r0*xr[0] + r1*xr[1],
            'xaxis.range[1]': r0*xr[1] + r1*xr[0],
            'yaxis.range[0]': r0*yr[0] + r1*yr[1],
            'yaxis.range[1]': r0*yr[1] + r1*yr[0]
        };
    }

    // if ALL traces have orientation 'h', 'hovermode': 'x' otherwise: 'y'
    if (astr==='hovermode' && (val==='x' || val==='y')) {
        val = fullLayout._isHoriz ? 'y' : 'x';
        button.setAttribute('data-val', val);
    }

    aobj[astr] = val;

    Plotly.relayout(graphInfo, aobj).then( function() {
        _this.updateActiveButton();
        if(astr === 'dragmode') {
            Plotly.Fx.setCursor(
                fullLayout._paper.select('.nsewdrag'),
                {pan:'move', zoom:'crosshair'}[val]
            );
            Plotly.Fx.supplyLayoutDefaults(graphInfo.layout, fullLayout,
                graphInfo._fullData);
        }
    });
};

/**
 * Toggle the data hover mode
 * @Param {object} ev event object
 */
ModeBar.prototype.handleHover3d = function(ev) {
    var button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        _this = this,
        Plotly = this.Plotly,
        graphInfo = this.graphInfo,
        fullLayout = graphInfo._fullLayout,
        sceneLayouts = Plotly.Lib.getSceneLayouts(fullLayout),
        layoutUpdate = {};

    // 3D has only 1 hover mode; toggle it
    var val = fullLayout[attr]!=='closest' ? 'closest' : false;
    layoutUpdate[attr] = val;

    // Apply to all scenes
    for (var i = 0;  i < sceneLayouts.length; ++i) {
        var sceneLayout = sceneLayouts[i],
            scene = sceneLayout._scene;

        scene.spikeEnable = !scene.spikeEnable;
        scene.container.focus();
    }

    Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
    });
};

/**
 * Reconfigure keyboard bindings for webgl3D camera control on drag
 * @Param {object} ev event object
 */
ModeBar.prototype.handleDrag3d = function(ev) {
    var button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        _this = this,
        Plotly = this.Plotly,
        graphInfo = this.graphInfo,
        fullLayout = graphInfo._fullLayout,
        sceneLayouts = Plotly.Lib.getSceneLayouts(fullLayout),
        layoutUpdate = {};

    // set dragmode to given value
    layoutUpdate[attr] = val;

    // Update the webgl3D key binding of all scenes
    for (var i = 0;  i < sceneLayouts.length; ++i) {
        var sceneLayout = sceneLayouts[i],
            scene = sceneLayout._scene;

        if ('camera' in scene) {
            scene.camera.keyBindingMode = val;
            scene.container.focus();
        }
    }

    Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
    });
};

/**
 * Reset the position of the webgl3D camera
 * @Param {object} ev event object
 */
ModeBar.prototype.handleCamera3d = function(ev) {
    var button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        Plotly = this.Plotly,
        graphInfo = this.graphInfo,
        fullLayout = graphInfo._fullLayout,
        sceneLayouts = Plotly.Lib.getSceneLayouts(fullLayout);

    // Reset camera of all scenes
    for (var i = 0;  i < sceneLayouts.length; ++i) {
        var sceneLayout = sceneLayouts[i],
            scene = sceneLayout._scene;

        if (attr === 'resetDefault') {
            // Reset camera position to default
            scene.setCameraToDefault();
        } else if (attr === 'resetLastSave') {
            // Reset camera back to the position at the last save
            var cameraPositionLastSave = scene.cameraPositionLastSave;
            scene.setCameraPosition(cameraPositionLastSave);
        }
    }

    /* TODO have a sceneLastTouched in _fullLayout to only
     * update the camera of the scene last touched by the user
     */
};

ModeBar.prototype.cleanup = function(){
    this.element.innerHTML = '';
    var modebarParent = this.element.parentNode;
    modebarParent.removeChild(this.element);
};

/**
 *
 * @Property config specification hash of button parameters
 */
ModeBar.prototype.config = function config() {
    return {
        zoom2d: {
            title: 'Zoom',
            attr: 'dragmode',
            val: 'zoom',
            icon: 'plotlyjsicon-zoombox',
            click: this.handleCartesian
        },
        pan2d: {
            title: 'Pan',
            attr: 'dragmode',
            val: 'pan',
            icon: 'plotlyjsicon-pan',
            click: this.handleCartesian
        },
        zoomIn2d: {
            title: 'Zoom in',
            attr: 'zoom',
            val: 'in',
            icon: 'plotlyjsicon-zoom_plus',
            click: this.handleCartesian
        },
        zoomOut2d: {
            title: 'Zoom out',
            attr: 'zoom',
            val: 'out',
            icon: 'plotlyjsicon-zoom_minus',
            click: this.handleCartesian
        },
        autoScale2d: {
            title: 'Autoscale',
            attr: 'allaxes.autorange',
            val: '',
            icon: 'plotlyjsicon-autoscale',
            click: this.handleCartesian
        },
        hoverClosest2d: {
            title: 'Show closest data on hover',
            attr: 'hovermode',
            val: 'closest',
            icon: 'plotlyjsicon-tooltip_basic',
            gravity: 'ne',
            click: this.handleCartesian
        },
        hoverCompare2d: {
            title: 'Compare data on hover',
            attr: 'hovermode',
            val: this.graphInfo._fullLayout._isHoriz ? 'y' : 'x',
            icon: 'plotlyjsicon-tooltip_compare',
            gravity: 'ne',
            click: this.handleCartesian
        },
        zoom3d: {
            title: 'Zoom',
            attr: 'dragmode',
            val: 'zoom',
            icon: 'plotlyjsicon-zoombox',
            click: this.handleDrag3d
        },
        pan3d: {
            title: 'Pan',
            attr: 'dragmode',
            val: 'pan',
            icon: 'plotlyjsicon-pan',
            click: this.handleDrag3d
        },
        rotate3d: {
            title: 'Rotate',
            attr: 'dragmode',
            val: 'rotate',
            icon: 'plotlyjsicon-undo',
            click: this.handleDrag3d
        },
        resetCameraDefault3d: {
            title: 'Reset camera to default',
            attr: 'resetDefault',
            val: false,
            icon: 'plotlyjsicon-home',
            click: this.handleCamera3d
        },
        resetCameraLastSave3d: {
            title: 'Reset camera to last save',
            attr: 'resetLastSave',
            val: false,
            icon: 'plotlyjsicon-camera-retro',
            click: this.handleCamera3d
        },
        hoverClosest3d: {
            title: 'Toggle show closest data on hover',
            attr: 'hovermode',
            val: 'closest',
            icon: 'plotlyjsicon-tooltip_basic',
            gravity: 'ne',
            click: this.handleHover3d
        }
    };
};

module.exports = ModeBar;
<commit_msg>check for parent in modebar before removing<commit_after>'use strict';
/* global d3:false */

/**
 * UI controller for interactive plots
 * @Class
 * @Param {object} config
 * @Param {object} config.buttons    nested arrays of grouped buttons to initialize
 * @Param {object} config.container  container div to append modebar
 * @Param {object} config.Plotly     main plotly namespace module
 * @Param {object} config.graphInfo  primary plot object containing data and layout
 */
function ModeBar (config) {

    if (!(this instanceof ModeBar)) return new ModeBar();

    var _this = this;

    this.Plotly = config.Plotly;
    this.graphInfo = config.graphInfo;
    this.element = document.createElement('div');

    if(this.graphInfo._context.displayModeBar === 'hover') {
        this.element.className = 'modebar modebar--hover';
    } else {
        this.element.className = 'modebar';
    }

    this.buttons = config.buttons;
    this.buttonElements = [];

    this.buttons.forEach( function (buttonGroup) {
        var group = _this.createGroup();

        buttonGroup.forEach( function (buttonName) {
            var buttonConfig = _this.config()[buttonName];

            if (!buttonConfig) {
                throw new Error(buttonName + ' not specfied in modebar configuration');
            }

            var button = _this.createButton(buttonConfig);

            _this.buttonElements.push(button);
            group.appendChild(button);
        });

        _this.element.appendChild(group);
    });

    if (this.graphInfo._context.displaylogo) {
        this.element.appendChild(this.getLogo());
    }

    config.container.appendChild(this.element);

    this.updateActiveButton();

}

/**
 * Empty div for containing a group of buttons
 * @Return {HTMLelement}
 */
ModeBar.prototype.createGroup = function () {
    var group = document.createElement('div');
    group.className = 'modebar-group';

    return group;
};

/**
 * Create a new button div and set constant and configurable attributes
 * @Param {object} config
 * @Param {string} config.attr
 * @Param {string} config.val
 * @Param {string} config.title
 * @Param {function} config.click
 * @Return {HTMLelement}
 */
ModeBar.prototype.createButton = function (config) {
    var button = document.createElement('a');
    var icon = document.createElement('i');
    var _this = this;
    button.setAttribute('rel', 'tooltip');
    button.className = 'modebar-btn plotlyjsicon';

    button.setAttribute('data-attr', config.attr);
    button.setAttribute('data-val', config.val);
    button.setAttribute('data-title', config.title);
    button.setAttribute('data-gravity', config.gravity || 'n');
    button.addEventListener('click', function () {
            config.click.apply(_this, arguments);
        });

    icon.className = config.icon;
    button.appendChild(icon);

    return button;
};

/**
 * Updates active button with attribute specified in layout
 * @Param {object} graphInfo plot object containing data and layout
 * @Return {HTMLelement}
 */
ModeBar.prototype.updateActiveButton = function () {
    var graphInfo = this.graphInfo;
    this.buttonElements.forEach( function (button) {
        var thisval = button.getAttribute('data-val') || true,
            dataAttr = button.getAttribute('data-attr'),
            curval = graphInfo._fullLayout[dataAttr];

        d3.select(button).classed('active', curval===thisval);
    });
};

/**
 * Check if modebar is configured as button configuration argument
 * @Param {object} buttons 2d array of grouped button names
 * @Return {boolean}
 */
ModeBar.prototype.hasButtons = function (buttons) {
    var currentButtons = this.buttons;

    if (buttons.length !== currentButtons.length) return false;

    for (var i = 0; i < buttons.length; ++i) {
        if (buttons[i].length !== currentButtons[i].length) return false;
        for (var j = 0; j < buttons[i].length; j++) {
            if (buttons[i][j] !== currentButtons[i][j]) return false;
        }
    }

    return true;
};

/**
 * @return {HTMLDivElement} The logo image wrapped in a group
 */
ModeBar.prototype.getLogo = function(){
    var group = this.createGroup(),
        a = document.createElement('a'),
        i = document.createElement('i');

    a.href = 'https://plot.ly/';
    a.target = '_blank';
    a.setAttribute('data-title', 'Produced with Plotly');
    a.className = 'modebar-btn plotlyjsicon modebar-btn--logo';

    i.className = 'plotlyjsicon-plotlylogo';
    a.appendChild(i);
    group.appendChild(a);
    return group;
};

/**
 * Apply D3 cartesian mode attributes to layout to update hover functionality
 * @Param {object} ev event object
 */
ModeBar.prototype.handleCartesian = function(ev) {
    var button = ev.currentTarget,
        astr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        _this = this,
        graphInfo = this.graphInfo,
        fullLayout = this.graphInfo._fullLayout,
        Plotly = this.Plotly,
        aobj = {};

    if(astr === 'zoom') {
        var xr = fullLayout.xaxis.range,
            yr = fullLayout.yaxis.range,
            mag = (val==='in') ? 0.5 : 2,
            r0 = (1+mag)/2, r1 = (1-mag)/2;
        aobj = {
            'xaxis.range[0]': r0*xr[0] + r1*xr[1],
            'xaxis.range[1]': r0*xr[1] + r1*xr[0],
            'yaxis.range[0]': r0*yr[0] + r1*yr[1],
            'yaxis.range[1]': r0*yr[1] + r1*yr[0]
        };
    }

    // if ALL traces have orientation 'h', 'hovermode': 'x' otherwise: 'y'
    if (astr==='hovermode' && (val==='x' || val==='y')) {
        val = fullLayout._isHoriz ? 'y' : 'x';
        button.setAttribute('data-val', val);
    }

    aobj[astr] = val;

    Plotly.relayout(graphInfo, aobj).then( function() {
        _this.updateActiveButton();
        if(astr === 'dragmode') {
            Plotly.Fx.setCursor(
                fullLayout._paper.select('.nsewdrag'),
                {pan:'move', zoom:'crosshair'}[val]
            );
            Plotly.Fx.supplyLayoutDefaults(graphInfo.layout, fullLayout,
                graphInfo._fullData);
        }
    });
};

/**
 * Toggle the data hover mode
 * @Param {object} ev event object
 */
ModeBar.prototype.handleHover3d = function(ev) {
    var button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        _this = this,
        Plotly = this.Plotly,
        graphInfo = this.graphInfo,
        fullLayout = graphInfo._fullLayout,
        sceneLayouts = Plotly.Lib.getSceneLayouts(fullLayout),
        layoutUpdate = {};

    // 3D has only 1 hover mode; toggle it
    var val = fullLayout[attr]!=='closest' ? 'closest' : false;
    layoutUpdate[attr] = val;

    // Apply to all scenes
    for (var i = 0;  i < sceneLayouts.length; ++i) {
        var sceneLayout = sceneLayouts[i],
            scene = sceneLayout._scene;

        scene.spikeEnable = !scene.spikeEnable;
        scene.container.focus();
    }

    Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
    });
};

/**
 * Reconfigure keyboard bindings for webgl3D camera control on drag
 * @Param {object} ev event object
 */
ModeBar.prototype.handleDrag3d = function(ev) {
    var button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        _this = this,
        Plotly = this.Plotly,
        graphInfo = this.graphInfo,
        fullLayout = graphInfo._fullLayout,
        sceneLayouts = Plotly.Lib.getSceneLayouts(fullLayout),
        layoutUpdate = {};

    // set dragmode to given value
    layoutUpdate[attr] = val;

    // Update the webgl3D key binding of all scenes
    for (var i = 0;  i < sceneLayouts.length; ++i) {
        var sceneLayout = sceneLayouts[i],
            scene = sceneLayout._scene;

        if ('camera' in scene) {
            scene.camera.keyBindingMode = val;
            scene.container.focus();
        }
    }

    Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
    });
};

/**
 * Reset the position of the webgl3D camera
 * @Param {object} ev event object
 */
ModeBar.prototype.handleCamera3d = function(ev) {
    var button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        Plotly = this.Plotly,
        graphInfo = this.graphInfo,
        fullLayout = graphInfo._fullLayout,
        sceneLayouts = Plotly.Lib.getSceneLayouts(fullLayout);

    // Reset camera of all scenes
    for (var i = 0;  i < sceneLayouts.length; ++i) {
        var sceneLayout = sceneLayouts[i],
            scene = sceneLayout._scene;

        if (attr === 'resetDefault') {
            // Reset camera position to default
            scene.setCameraToDefault();
        } else if (attr === 'resetLastSave') {
            // Reset camera back to the position at the last save
            var cameraPositionLastSave = scene.cameraPositionLastSave;
            scene.setCameraPosition(cameraPositionLastSave);
        }
    }

    /* TODO have a sceneLastTouched in _fullLayout to only
     * update the camera of the scene last touched by the user
     */
};

ModeBar.prototype.cleanup = function(){
    this.element.innerHTML = '';
    var modebarParent = this.element.parentNode;
    if (modebarParent) modebarParent.removeChild(this.element);
};

/**
 *
 * @Property config specification hash of button parameters
 */
ModeBar.prototype.config = function config() {
    return {
        zoom2d: {
            title: 'Zoom',
            attr: 'dragmode',
            val: 'zoom',
            icon: 'plotlyjsicon-zoombox',
            click: this.handleCartesian
        },
        pan2d: {
            title: 'Pan',
            attr: 'dragmode',
            val: 'pan',
            icon: 'plotlyjsicon-pan',
            click: this.handleCartesian
        },
        zoomIn2d: {
            title: 'Zoom in',
            attr: 'zoom',
            val: 'in',
            icon: 'plotlyjsicon-zoom_plus',
            click: this.handleCartesian
        },
        zoomOut2d: {
            title: 'Zoom out',
            attr: 'zoom',
            val: 'out',
            icon: 'plotlyjsicon-zoom_minus',
            click: this.handleCartesian
        },
        autoScale2d: {
            title: 'Autoscale',
            attr: 'allaxes.autorange',
            val: '',
            icon: 'plotlyjsicon-autoscale',
            click: this.handleCartesian
        },
        hoverClosest2d: {
            title: 'Show closest data on hover',
            attr: 'hovermode',
            val: 'closest',
            icon: 'plotlyjsicon-tooltip_basic',
            gravity: 'ne',
            click: this.handleCartesian
        },
        hoverCompare2d: {
            title: 'Compare data on hover',
            attr: 'hovermode',
            val: this.graphInfo._fullLayout._isHoriz ? 'y' : 'x',
            icon: 'plotlyjsicon-tooltip_compare',
            gravity: 'ne',
            click: this.handleCartesian
        },
        zoom3d: {
            title: 'Zoom',
            attr: 'dragmode',
            val: 'zoom',
            icon: 'plotlyjsicon-zoombox',
            click: this.handleDrag3d
        },
        pan3d: {
            title: 'Pan',
            attr: 'dragmode',
            val: 'pan',
            icon: 'plotlyjsicon-pan',
            click: this.handleDrag3d
        },
        rotate3d: {
            title: 'Rotate',
            attr: 'dragmode',
            val: 'rotate',
            icon: 'plotlyjsicon-undo',
            click: this.handleDrag3d
        },
        resetCameraDefault3d: {
            title: 'Reset camera to default',
            attr: 'resetDefault',
            val: false,
            icon: 'plotlyjsicon-home',
            click: this.handleCamera3d
        },
        resetCameraLastSave3d: {
            title: 'Reset camera to last save',
            attr: 'resetLastSave',
            val: false,
            icon: 'plotlyjsicon-camera-retro',
            click: this.handleCamera3d
        },
        hoverClosest3d: {
            title: 'Toggle show closest data on hover',
            attr: 'hovermode',
            val: 'closest',
            icon: 'plotlyjsicon-tooltip_basic',
            gravity: 'ne',
            click: this.handleHover3d
        }
    };
};

module.exports = ModeBar;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import {
    View,
    Text,
    Animated,
    Easing
} from 'react-native';

class TimerProgressBar extends Component {

    constructor(props) {
        super(props);
        this.state = {
            barAnimations: new Animated.Value(0),
        };
    }

    shouldComponentUpdate(nextProps) {
        const differentProjectedEndSetTime = nextProps.projectedEndSetTime !== this.props.projectedEndSetTime;
        const differentTimerDuration = nextProps.timerDuration !== this.props.timerDuration;
        const differentTimerRemaining = nextProps.timerRemaining !== this.props.timerRemaining;
        const differentTimerStatus = nextProps.timerStatus !== this.props.timerStatus;

        if (differentProjectedEndSetTime || differentTimerDuration || differentTimerRemaining || differentTimerStatus) {
            // set initial position
            const value = (nextProps.timerDuration-nextProps.timerRemaining)/nextProps.timerDuration;
            this.state.barAnimations.setValue(value);

            if (nextProps.timerStatus === 'stopped' || nextProps.timerStatus === 'paused') {
                // pause it
                this.state.barAnimations.stopAnimation();
            } else {
                // start it
                Animated.timing(
                    this.state.barAnimations,
                    {
                        toValue: 1.0, // can i make this a percent? for now just have it work <_>
                        duration: nextProps.timerRemaining,
                        easing: Easing.linear
                    }
                ).start();
            }
            return true;
        }

        return false;
    }

    render() {
        const barAnimation = this.state.barAnimations;

        return (
            <View style={[{flex:1, flexDirection: 'row', alignItems:'stretch', backgroundColor:'white'}]}>
                <Animated.View style={{flex: barAnimation, height: 2, backgroundColor: 'green'}}></Animated.View>
            </View>
        );
    }

}

export default TimerProgressBar;
<commit_msg>removed unused comment<commit_after>import React, { Component } from 'react';
import {
    View,
    Text,
    Animated,
    Easing
} from 'react-native';

class TimerProgressBar extends Component {

    constructor(props) {
        super(props);
        this.state = {
            barAnimations: new Animated.Value(0),
        };
    }

    shouldComponentUpdate(nextProps) {
        const differentProjectedEndSetTime = nextProps.projectedEndSetTime !== this.props.projectedEndSetTime;
        const differentTimerDuration = nextProps.timerDuration !== this.props.timerDuration;
        const differentTimerRemaining = nextProps.timerRemaining !== this.props.timerRemaining;
        const differentTimerStatus = nextProps.timerStatus !== this.props.timerStatus;

        if (differentProjectedEndSetTime || differentTimerDuration || differentTimerRemaining || differentTimerStatus) {
            // set initial position
            const value = (nextProps.timerDuration-nextProps.timerRemaining)/nextProps.timerDuration;
            this.state.barAnimations.setValue(value);

            if (nextProps.timerStatus === 'stopped' || nextProps.timerStatus === 'paused') {
                // pause it
                this.state.barAnimations.stopAnimation();
            } else {
                // start it
                Animated.timing(
                    this.state.barAnimations,
                    {
                        toValue: 1.0,
                        duration: nextProps.timerRemaining,
                        easing: Easing.linear
                    }
                ).start();
            }
            return true;
        }

        return false;
    }

    render() {
        const barAnimation = this.state.barAnimations;

        return (
            <View style={[{flex:1, flexDirection: 'row', alignItems:'stretch', backgroundColor:'white'}]}>
                <Animated.View style={{flex: barAnimation, height: 2, backgroundColor: 'green'}}></Animated.View>
            </View>
        );
    }

}

export default TimerProgressBar;
<|endoftext|>


<commit_before>/**
 * ListingEditor
 * Visualizes and allow edition of a listing of the current user
 *
 * TODO: create components and combine with listing activity
 *
 * @module activities/listing-editor
 *
 * FIXME: Complete jsdocs, description
 * TODO: Quick460 Must complete refactoring
 */

import '../../kocomponents/badge/viewer';
import * as activities from '../index';
import { byListing as badgesByListing, expandUserBadges } from '../../data/userBadges';
import $ from 'jquery';
import Activity from '../../components/Activity';
import AlertLink from '../../viewmodels/AlertLink';
import PublicUser from '../../models/PublicUser';
import PublicUserJobTitle from '../../models/PublicUserJobTitle';
import UserJobTitle from '../../models/UserJobTitle';
import UserType from '../../enums/UserType';
import clipboard from '../../utils/clipboard';
import ko from 'knockout';
import shell from '../../app.shell';
import { show as showConfirm } from '../../modals/confirm';
import { show as showError } from '../../modals/error';
import { show as showNotification } from '../../modals/notification';
import style from './style.styl';
import template from './template.html';
import { data as user } from '../../data/userProfile';
import userLicensesCertifications from '../../data/userLicensesCertifications';
import { item as userListingItem } from '../../data/userListings';
import users from '../../data/users';

const ROUTE_NAME = 'listing-editor';

export default class ListingEditor extends Activity {

    static get template() { return template; }

    static get style() { return style; }

    constructor($activity, app) {
        super($activity, app);

        this.accessLevel = UserType.serviceProfessional;
        this.helpLink = '/help/relatedArticles/202034083-managing-your-marketplace-profile';
        this.navBar = Activity.createSubsectionNavBar('Your listings', {
            backLink: '/listings',
            helpLink: this.helpLink
        });
        this.title = ko.pureComputed(() => {
            const user = this.user();
            if (user) {
                const title = user.selectedJobTitle() && user.selectedJobTitle().title();
                return `Edit your ${title} listing`;
            }
            else {
                return 'Edit your listing';
            }
        });

        this.__defViewProperties();
        this.__defViewMethods();

        // DOM Event handler
        this.registerHandler({
            event: 'layoutUpdate',
            target: $(window),
            handler: () => this.refreshTs(new Date())
        });
    }

    __defViewProperties() {
        this.isLoading = ko.observable(false);
        this.user = ko.observable(null);
        this.jobTitleID = ko.observable(0);
        this.userJobTitle = ko.observable(null);
        this.userBadges = ko.observableArray([]);
        this.listingTitle = ko.observable();
        /**
         * Time zone name displayed to the user.
         * We used the standard TZID with a special case for ones defined for
         * USA using the special prefix 'US/', where we strip that prefix.
         * @member {KnockoutComputed<string>}
         */
        this.timeZone = ko.pureComputed(() => {
            const tz = this.user() && this.user().weeklySchedule() &&
                this.user().weeklySchedule().timeZone();
            return tz && tz.replace('US/', '') || '';
        });
        // Just a timestamp to notice that a request to refresh UI happens
        // Is updated on 'show' and layoutUpdate (when inside this UI) currently
        // just to notify app-address-map elements
        this.refreshTs = ko.observable(new Date());
        this.returnLinkGeneralActivity = ko.pureComputed(() => {
            const jobTitleID = this.user() && this.selectedJobTitle() && this.selectedJobTitle().jobTitleID();
            if (jobTitleID) {
                return `?mustReturn=listing-editor/${jobTitleID}&returnText=Edit listing`;
            }
            else {
                return '';
            }
        });
        /**
         * Generates the final path to link a jobTitleID based URL, adding parameters
         * for the 'return back' link so points to this activity with proper labeling.
         * @member {KnockoutComputed<string>}
         */
        this.returnLinkJobTitleActivity = ko.pureComputed(() => {
            const jobTitleID = this.user() && this.selectedJobTitle() && this.selectedJobTitle().jobTitleID();
            if (jobTitleID) {
                return `${jobTitleID}?mustReturn=listing-editor/${jobTitleID}&returnText=Edit listing`;
            }
            else {
                return '';
            }
        });
        /**
         * Generates a relative URL to the public listing page for current user and listing
         * @member {KnockoutComputed<string>}
         */
        this.publicListingUrl = ko.pureComputed(() => {
            const userID = user.userID();
            const jobTitleID = this.user() && this.selectedJobTitle() && this.selectedJobTitle().jobTitleID();
            if (userID && jobTitleID) {
                return `#!/listing/${userID}/${jobTitleID}`;
            }
            else {
                return '';
            }
        });
         /// Related models information
         this.submittedUserLicensesCertifications = ko.observableArray([]);

         this.hasServicesOverview = ko.pureComputed(() => {
            var jobTitle = this.user() && this.user().selectedJobTitle();
            var hasIntro = jobTitle && jobTitle.hasIntro();
            var hasAttributes = jobTitle && jobTitle.serviceAttributes().hasAttributes();
            return hasIntro || hasAttributes;
        });
        this.selectedJobTitle = ko.pureComputed(() => this.user() && this.user().selectedJobTitle() || new PublicUserJobTitle());
        this.isToggleReady = ko.pureComputed(() => this.userJobTitle() && this.userJobTitle().isComplete());
        this.isActiveStatus = ko.pureComputed({
            read: () => {
                var j = this.userJobTitle();
                return j && j.statusID() === UserJobTitle.status.on || false;
            },
            write: (v) => {
                var status = this.userJobTitle() && this.userJobTitle().statusID();
                if (v === true && status === UserJobTitle.status.off) {
                    this.userJobTitle().statusID(UserJobTitle.status.on);
                    // Push change to back-end
                    userListingItem(this.jobTitleID()).reactivate()
                    .catch((error) => {
                        showError({
                            title: 'Error enabling your listing',
                            error
                        });
                    });
                }
                else if (v === false && status === UserJobTitle.status.on) {
                    this.userJobTitle().statusID(UserJobTitle.status.off);
                    // Push change to back-end
                    userListingItem(this.jobTitleID()).deactivate()
                    .catch((error) => {
                        showError({
                            title: 'Error disabling your listing',
                            error
                        });
                    });
                    // Per #1001, notify user about availability of bookMeNow button even with public marketplace profile
                    // disabled/hidden
                    showNotification({
                        message: 'Clients will no longer be able to find you in the marketplace. However, any "book me now" links you have posted will still be active.',
                        buttonText: 'Got it!'
                    });
                }
            }
        });
        this.statusLabel = ko.pureComputed(() => {
            var statusID = this.userJobTitle() && this.userJobTitle().statusID();
            switch (statusID) {
                case UserJobTitle.status.on:
                    return 'This listing is active';
                case UserJobTitle.status.off:
                    return 'This listing is inactive';
                //case UserJobTitle.status.incomplete:
                default:
                    return "You're almost there!";
            }
        });
        this.requiredAlertLinks = ko.pureComputed(() => {
            var userJobTitle = this.userJobTitle();
            var jobTitleID = userJobTitle && userJobTitle.jobTitleID();
            var requiredAlerts = (userJobTitle && userJobTitle.requiredAlerts()) || [];
            return requiredAlerts.map((profileAlert) => AlertLink.fromProfileAlert(profileAlert, { jobTitleID: jobTitleID }));
        });

         this.__defWorkPhotosMembers();
         this.__defAddressMembers();
         this.__defSocialLinksMembers();
    }

    __defWorkPhotosMembers() {
        var DEFAULT_WORKPHOTOS_LIMIT = 2;
        this.isShowingAllPhotos = ko.observable(false);
        this.workPhotos = ko.pureComputed(() => {
            var u = this.user();
            var ph = u && u.selectedJobTitle() && u.selectedJobTitle().workPhotos();
            if (!ph) {
                return [];
            }
            else if (this.isShowingAllPhotos()) {
                return ph;
            }
            else {
                // Filter by 2 first photos:
                var firsts = [];
                ph.some((p, i) => {
                    if (i > DEFAULT_WORKPHOTOS_LIMIT - 1) {
                        return true;
                    }
                    firsts.push(p);
                });
                return firsts;
            }
        });
        this.viewMoreWorkPhotosLabel = ko.pureComputed(() => {
            var imgCount = this.user() && this.user().selectedJobTitle() && this.user().selectedJobTitle().workPhotos();
            imgCount = imgCount && imgCount.length || 0;
            if (this.isShowingAllPhotos() || imgCount === 0 || imgCount <= DEFAULT_WORKPHOTOS_LIMIT) {
                return '';
            }
            else {
                return 'View all ' + imgCount + ' images';
            }
        });
        this.viewAllPhotos = () => {
            this.isShowingAllPhotos(true);
        };
    }

    __defAddressMembers() {
        this.serviceAddresses = ko.pureComputed(() => {
            var u = this.user();
            var adds = u && u.selectedJobTitle() && u.selectedJobTitle().serviceAddresses();
            return adds || [];
        });
        this.changeJobTitle = (jobTitle, event) => {
            this.user().selectedJobTitleID(jobTitle.jobTitleID());
            if (event) {
                event.preventDefault();
                event.stopImmediatePropagation();
            }
            var url = event.target.getAttribute('href');
            shell.pushState(null, null, url);
        };
    }

    __defSocialLinksMembers() {
        this.getEmailLink = ko.pureComputed(() => {
            var u = this.user();
            if (!u) return '';
            var url = encodeURIComponent(u.profile().serviceProfessionalProfileUrl());
            return 'mailto:?body=' + encodeURIComponent(u.profile().fullName() + ': ') + url;
        });
        this.getFacebookLink = ko.pureComputed(() => {
            var u = this.user();
            if (!u) return '';
            var url = encodeURIComponent(u.profile().serviceProfessionalProfileUrl());
            return 'http://www.facebook.com/share.php?u=' + url + '&t=' + encodeURIComponent(u.profile().fullName());
        });
        this.getTwitterLink = ko.pureComputed(() => {
            var u = this.user();
            if (!u) return '';
            var url = encodeURIComponent(u.profile().serviceProfessionalProfileUrl());
            return 'http://twitter.com/intent/tweet?text=' + encodeURIComponent(u.profile().fullName() + ': ' + url);
        });
        this.getGooglePlusLink = ko.pureComputed(() => {
            var u = this.user();
            if (!u) return '';
            var url = encodeURIComponent(u.profile().serviceProfessionalProfileUrl());
            return 'https://plus.google.com/share?url=' + url;
        });
        this.getPinterestLink = ko.pureComputed(() => {
            var u = this.user();
            if (!u) return '';
            var url = encodeURIComponent(u.profile().serviceProfessionalProfileUrl());
            var photo = encodeURIComponent(u.profile().photoUrl());
            return 'http://pinterest.com/pin/create/button/?url=' + url + '&media=' + photo + '&description=' + encodeURIComponent(u.profile().fullName() + ': ' + url);
        });
        this.getBookLink = ko.pureComputed(() => {
            var u = this.user();
            if (!u) return '';
            return '#!booking/' + u.profile().userID() + '/' + u.selectedJobTitleID();
        });
        this.getSendMessageLink = ko.pureComputed(() => {
            var u = this.user();
            if (!u) return '';
            return '#!inbox/new/' + u.profile().userID();
        });
    }

    __defViewMethods() {
        this.reset = () => {
            this.user(null);
            this.listingTitle('Job Title');
        };
        this.copyPublicListingUrl = () => {
            const url = window.location.origin + this.publicListingUrl();
            // Try to copy
            const error = clipboard.copy(url);
            if (error) {
                showError({
                    title: 'Copying listing URL',
                    error: `Automatic copy failed, but you can select and copy it here: ${url}`
                });
            }
            else {
                showNotification({
                    title: 'Copying listing URL',
                    message: `Copied: ${url}`,
                    buttonText: 'Done'
                });
            }
            // Do not prevent default:
            return true;
        };
        this.deleteJobTitle = () => {
            var jid = this.jobTitleID();
            var jname = this.listingTitle();
            if (jid) {
                showConfirm({
                    title: 'Delete ' + jname + ' listing',
                    message: 'Are you sure you really want to delete your ' + jname +' listing?',
                    yes: 'Delete',
                    no: 'Keep'
                })
                .then(() => userListingItem(jid).delete())
                .then(() => shell.go('/listings'))
                .catch((error) => {
                    if (error) {
                        showError({
                            error,
                            title: 'Error while deleting your listing'
                        });
                    }
                });
            }
        };
        /**
         * Returns a URL to where to edit the badge assigned to the user, with a return
         * link to the listing editor.
         * @param {rest/UserBadge} userBadge record for a badge assigned to a user (AKA 'assertion' in OpenBadges naming)
         * @returns {string}
         */
        this.getBadgeEditURL = (userBadge) => {
            if (userBadge.createdBy !== 'user') return null;
            else return `/badge-edit/${userBadge.userBadgeID}?jobTitleID=${this.jobTitleID()}&mustReturn=listing-editor/${this.jobTitleID()}&returnText=${encodeURIComponent('Listing Editor')}`;
        };
        /**
         * Returns a URL to where to view details of the badge assigned to the user, with a return
         * link to the listing editor.
         * @param {OpenBadgesV2/Assertion} assertion data for an assertion
         * @returns {string}
         */
        this.getBadgeDetailsURL = (assertion) => `/badge-view/${encodeURIComponent(assertion.id)}?mustReturn=listing-editor/${this.jobTitleID()}&returnText=${encodeURIComponent('Listing Editor')}`;
    }

    loadData(jobTitleID) {
        this.reset();
        if (user.userID()) {
            this.isLoading(true);
            users.getUser(user.userID(), { includeFullJobTitleID: -1 })
            .then((data) => {
                var pu = new PublicUser(data);
                this.user(pu);
                if (!jobTitleID) {
                    return pu.jobProfile() && pu.jobProfile()[0] && pu.jobProfile()[0].jobTitleID();
                }
                else {
                    return jobTitleID;
                }
            })
            .then((jobTitleID) => {
                // For service professionals:
                if (jobTitleID) {
                    ////////////
                    // User Job Title
                    userListingItem(jobTitleID)
                    .onceLoaded()
                    .then((listing) => {
                        // Fill the job title record
                        this.listingTitle(listing.title);
                        this.userJobTitle(new UserJobTitle(listing));
                        // Load badges
                        return badgesByListing(listing.userListingID)
                        .onceLoaded()
                        .then(expandUserBadges)
                        .then(this.userBadges);
                    })
                    .catch((error) => {
                        showError({
                            title: 'There was an error loading your listing.',
                            error
                        });
                    });
                    ////////////
                    // Submitted Licenses
                    userLicensesCertifications.getList(jobTitleID)
                    .then((list) => {
                        // Save for use in the view
                        this.submittedUserLicensesCertifications(userLicensesCertifications.asModel(list));
                    })
                    .catch((error) => {
                        showError({
                            title: 'There was an error while loading.',
                            error
                        });
                    });
                    ////////////
                    // Active title
                    this.user().selectedJobTitleID(jobTitleID);
                }
            })
            .catch((error) => {
                showError({
                    error,
                    title: 'The user profile could not be loaded.'
                });
            })
            .then(() => {
                // always
                this.isLoading(false);
            });
        }
    }

    /**
     * URL segments template: /{jobTitleID:int}
     * If not jobTitleID, the first one is returned
     * TODO: Is automatic jobTitleID still valid? wasn't buggy? Review and
     * clarify previous comment
     * @param {Object} state
     * @param {Object} state.route
     * @param {Array<string>} state.route.segments Contains per position:
     * - {number} segments[0] jobTitleID
     */
    show(state) {
        super.show(state);

        var params = state.route.segments;
        var jobTitleID = params[0] |0;
        this.loadData(jobTitleID);
        this.jobTitleID(jobTitleID);
        this.refreshTs(new Date());
    }
}

activities.register(ROUTE_NAME, ListingEditor);
<commit_msg>Recovered switch button to on and off a listing<commit_after>/**
 * ListingEditor
 * Visualizes and allow edition of a listing of the current user
 *
 * TODO: create components and combine with listing activity
 *
 * @module activities/listing-editor
 *
 * FIXME: Complete jsdocs, description
 * TODO: Quick460 Must complete refactoring
 */

import '../../kocomponents/badge/viewer';
import '../../kocomponents/switch-checkbox';
import * as activities from '../index';
import { byListing as badgesByListing, expandUserBadges } from '../../data/userBadges';
import $ from 'jquery';
import Activity from '../../components/Activity';
import AlertLink from '../../viewmodels/AlertLink';
import PublicUser from '../../models/PublicUser';
import PublicUserJobTitle from '../../models/PublicUserJobTitle';
import UserJobTitle from '../../models/UserJobTitle';
import UserType from '../../enums/UserType';
import clipboard from '../../utils/clipboard';
import ko from 'knockout';
import shell from '../../app.shell';
import { show as showConfirm } from '../../modals/confirm';
import { show as showError } from '../../modals/error';
import { show as showNotification } from '../../modals/notification';
import style from './style.styl';
import template from './template.html';
import { data as user } from '../../data/userProfile';
import userLicensesCertifications from '../../data/userLicensesCertifications';
import { item as userListingItem } from '../../data/userListings';
import users from '../../data/users';

const ROUTE_NAME = 'listing-editor';

export default class ListingEditor extends Activity {

    static get template() { return template; }

    static get style() { return style; }

    constructor($activity, app) {
        super($activity, app);

        this.accessLevel = UserType.serviceProfessional;
        this.helpLink = '/help/relatedArticles/202034083-managing-your-marketplace-profile';
        this.navBar = Activity.createSubsectionNavBar('Your listings', {
            backLink: '/listings',
            helpLink: this.helpLink
        });
        this.title = ko.pureComputed(() => {
            const user = this.user();
            if (user) {
                const title = user.selectedJobTitle() && user.selectedJobTitle().title();
                return `Edit your ${title} listing`;
            }
            else {
                return 'Edit your listing';
            }
        });

        this.__defViewProperties();
        this.__defViewMethods();

        // DOM Event handler
        this.registerHandler({
            event: 'layoutUpdate',
            target: $(window),
            handler: () => this.refreshTs(new Date())
        });
    }

    __defViewProperties() {
        this.isLoading = ko.observable(false);
        this.user = ko.observable(null);
        this.jobTitleID = ko.observable(0);
        this.userJobTitle = ko.observable(null);
        this.userBadges = ko.observableArray([]);
        this.listingTitle = ko.observable();
        /**
         * Time zone name displayed to the user.
         * We used the standard TZID with a special case for ones defined for
         * USA using the special prefix 'US/', where we strip that prefix.
         * @member {KnockoutComputed<string>}
         */
        this.timeZone = ko.pureComputed(() => {
            const tz = this.user() && this.user().weeklySchedule() &&
                this.user().weeklySchedule().timeZone();
            return tz && tz.replace('US/', '') || '';
        });
        // Just a timestamp to notice that a request to refresh UI happens
        // Is updated on 'show' and layoutUpdate (when inside this UI) currently
        // just to notify app-address-map elements
        this.refreshTs = ko.observable(new Date());
        this.returnLinkGeneralActivity = ko.pureComputed(() => {
            const jobTitleID = this.user() && this.selectedJobTitle() && this.selectedJobTitle().jobTitleID();
            if (jobTitleID) {
                return `?mustReturn=listing-editor/${jobTitleID}&returnText=Edit listing`;
            }
            else {
                return '';
            }
        });
        /**
         * Generates the final path to link a jobTitleID based URL, adding parameters
         * for the 'return back' link so points to this activity with proper labeling.
         * @member {KnockoutComputed<string>}
         */
        this.returnLinkJobTitleActivity = ko.pureComputed(() => {
            const jobTitleID = this.user() && this.selectedJobTitle() && this.selectedJobTitle().jobTitleID();
            if (jobTitleID) {
                return `${jobTitleID}?mustReturn=listing-editor/${jobTitleID}&returnText=Edit listing`;
            }
            else {
                return '';
            }
        });
        /**
         * Generates a relative URL to the public listing page for current user and listing
         * @member {KnockoutComputed<string>}
         */
        this.publicListingUrl = ko.pureComputed(() => {
            const userID = user.userID();
            const jobTitleID = this.user() && this.selectedJobTitle() && this.selectedJobTitle().jobTitleID();
            if (userID && jobTitleID) {
                return `#!/listing/${userID}/${jobTitleID}`;
            }
            else {
                return '';
            }
        });
         /// Related models information
         this.submittedUserLicensesCertifications = ko.observableArray([]);

         this.hasServicesOverview = ko.pureComputed(() => {
            var jobTitle = this.user() && this.user().selectedJobTitle();
            var hasIntro = jobTitle && jobTitle.hasIntro();
            var hasAttributes = jobTitle && jobTitle.serviceAttributes().hasAttributes();
            return hasIntro || hasAttributes;
        });
        this.selectedJobTitle = ko.pureComputed(() => this.user() && this.user().selectedJobTitle() || new PublicUserJobTitle());
        this.isToggleReady = ko.pureComputed(() => this.userJobTitle() && this.userJobTitle().isComplete());
        this.isActiveStatus = ko.pureComputed({
            read: () => {
                var j = this.userJobTitle();
                return j && j.statusID() === UserJobTitle.status.on || false;
            },
            write: (v) => {
                var status = this.userJobTitle() && this.userJobTitle().statusID();
                if (v === true && status === UserJobTitle.status.off) {
                    this.userJobTitle().statusID(UserJobTitle.status.on);
                    // Push change to back-end
                    userListingItem(this.jobTitleID()).reactivate()
                    .catch((error) => {
                        showError({
                            title: 'Error enabling your listing',
                            error
                        });
                    });
                }
                else if (v === false && status === UserJobTitle.status.on) {
                    this.userJobTitle().statusID(UserJobTitle.status.off);
                    // Push change to back-end
                    userListingItem(this.jobTitleID()).deactivate()
                    .catch((error) => {
                        showError({
                            title: 'Error disabling your listing',
                            error
                        });
                    });
                    // Per #1001, notify user about availability of bookMeNow button even with public marketplace profile
                    // disabled/hidden
                    showNotification({
                        message: 'Clients will no longer be able to find you in the marketplace. However, any "book me now" links you have posted will still be active.',
                        buttonText: 'Got it!'
                    });
                }
            }
        });
        this.statusLabel = ko.pureComputed(() => {
            var statusID = this.userJobTitle() && this.userJobTitle().statusID();
            switch (statusID) {
                case UserJobTitle.status.on:
                    return 'This listing is active';
                case UserJobTitle.status.off:
                    return 'This listing is inactive';
                //case UserJobTitle.status.incomplete:
                default:
                    return "You're almost there!";
            }
        });
        this.requiredAlertLinks = ko.pureComputed(() => {
            var userJobTitle = this.userJobTitle();
            var jobTitleID = userJobTitle && userJobTitle.jobTitleID();
            var requiredAlerts = (userJobTitle && userJobTitle.requiredAlerts()) || [];
            return requiredAlerts.map((profileAlert) => AlertLink.fromProfileAlert(profileAlert, { jobTitleID: jobTitleID }));
        });

         this.__defWorkPhotosMembers();
         this.__defAddressMembers();
         this.__defSocialLinksMembers();
    }

    __defWorkPhotosMembers() {
        var DEFAULT_WORKPHOTOS_LIMIT = 2;
        this.isShowingAllPhotos = ko.observable(false);
        this.workPhotos = ko.pureComputed(() => {
            var u = this.user();
            var ph = u && u.selectedJobTitle() && u.selectedJobTitle().workPhotos();
            if (!ph) {
                return [];
            }
            else if (this.isShowingAllPhotos()) {
                return ph;
            }
            else {
                // Filter by 2 first photos:
                var firsts = [];
                ph.some((p, i) => {
                    if (i > DEFAULT_WORKPHOTOS_LIMIT - 1) {
                        return true;
                    }
                    firsts.push(p);
                });
                return firsts;
            }
        });
        this.viewMoreWorkPhotosLabel = ko.pureComputed(() => {
            var imgCount = this.user() && this.user().selectedJobTitle() && this.user().selectedJobTitle().workPhotos();
            imgCount = imgCount && imgCount.length || 0;
            if (this.isShowingAllPhotos() || imgCount === 0 || imgCount <= DEFAULT_WORKPHOTOS_LIMIT) {
                return '';
            }
            else {
                return 'View all ' + imgCount + ' images';
            }
        });
        this.viewAllPhotos = () => {
            this.isShowingAllPhotos(true);
        };
    }

    __defAddressMembers() {
        this.serviceAddresses = ko.pureComputed(() => {
            var u = this.user();
            var adds = u && u.selectedJobTitle() && u.selectedJobTitle().serviceAddresses();
            return adds || [];
        });
        this.changeJobTitle = (jobTitle, event) => {
            this.user().selectedJobTitleID(jobTitle.jobTitleID());
            if (event) {
                event.preventDefault();
                event.stopImmediatePropagation();
            }
            var url = event.target.getAttribute('href');
            shell.pushState(null, null, url);
        };
    }

    __defSocialLinksMembers() {
        this.getEmailLink = ko.pureComputed(() => {
            var u = this.user();
            if (!u) return '';
            var url = encodeURIComponent(u.profile().serviceProfessionalProfileUrl());
            return 'mailto:?body=' + encodeURIComponent(u.profile().fullName() + ': ') + url;
        });
        this.getFacebookLink = ko.pureComputed(() => {
            var u = this.user();
            if (!u) return '';
            var url = encodeURIComponent(u.profile().serviceProfessionalProfileUrl());
            return 'http://www.facebook.com/share.php?u=' + url + '&t=' + encodeURIComponent(u.profile().fullName());
        });
        this.getTwitterLink = ko.pureComputed(() => {
            var u = this.user();
            if (!u) return '';
            var url = encodeURIComponent(u.profile().serviceProfessionalProfileUrl());
            return 'http://twitter.com/intent/tweet?text=' + encodeURIComponent(u.profile().fullName() + ': ' + url);
        });
        this.getGooglePlusLink = ko.pureComputed(() => {
            var u = this.user();
            if (!u) return '';
            var url = encodeURIComponent(u.profile().serviceProfessionalProfileUrl());
            return 'https://plus.google.com/share?url=' + url;
        });
        this.getPinterestLink = ko.pureComputed(() => {
            var u = this.user();
            if (!u) return '';
            var url = encodeURIComponent(u.profile().serviceProfessionalProfileUrl());
            var photo = encodeURIComponent(u.profile().photoUrl());
            return 'http://pinterest.com/pin/create/button/?url=' + url + '&media=' + photo + '&description=' + encodeURIComponent(u.profile().fullName() + ': ' + url);
        });
        this.getBookLink = ko.pureComputed(() => {
            var u = this.user();
            if (!u) return '';
            return '#!booking/' + u.profile().userID() + '/' + u.selectedJobTitleID();
        });
        this.getSendMessageLink = ko.pureComputed(() => {
            var u = this.user();
            if (!u) return '';
            return '#!inbox/new/' + u.profile().userID();
        });
    }

    __defViewMethods() {
        this.reset = () => {
            this.user(null);
            this.listingTitle('Job Title');
        };
        this.copyPublicListingUrl = () => {
            const url = window.location.origin + this.publicListingUrl();
            // Try to copy
            const error = clipboard.copy(url);
            if (error) {
                showError({
                    title: 'Copying listing URL',
                    error: `Automatic copy failed, but you can select and copy it here: ${url}`
                });
            }
            else {
                showNotification({
                    title: 'Copying listing URL',
                    message: `Copied: ${url}`,
                    buttonText: 'Done'
                });
            }
            // Do not prevent default:
            return true;
        };
        this.deleteJobTitle = () => {
            var jid = this.jobTitleID();
            var jname = this.listingTitle();
            if (jid) {
                showConfirm({
                    title: 'Delete ' + jname + ' listing',
                    message: 'Are you sure you really want to delete your ' + jname +' listing?',
                    yes: 'Delete',
                    no: 'Keep'
                })
                .then(() => userListingItem(jid).delete())
                .then(() => shell.go('/listings'))
                .catch((error) => {
                    if (error) {
                        showError({
                            error,
                            title: 'Error while deleting your listing'
                        });
                    }
                });
            }
        };
        /**
         * Returns a URL to where to edit the badge assigned to the user, with a return
         * link to the listing editor.
         * @param {rest/UserBadge} userBadge record for a badge assigned to a user (AKA 'assertion' in OpenBadges naming)
         * @returns {string}
         */
        this.getBadgeEditURL = (userBadge) => {
            if (userBadge.createdBy !== 'user') return null;
            else return `/badge-edit/${userBadge.userBadgeID}?jobTitleID=${this.jobTitleID()}&mustReturn=listing-editor/${this.jobTitleID()}&returnText=${encodeURIComponent('Listing Editor')}`;
        };
        /**
         * Returns a URL to where to view details of the badge assigned to the user, with a return
         * link to the listing editor.
         * @param {OpenBadgesV2/Assertion} assertion data for an assertion
         * @returns {string}
         */
        this.getBadgeDetailsURL = (assertion) => `/badge-view/${encodeURIComponent(assertion.id)}?mustReturn=listing-editor/${this.jobTitleID()}&returnText=${encodeURIComponent('Listing Editor')}`;
    }

    loadData(jobTitleID) {
        this.reset();
        if (user.userID()) {
            this.isLoading(true);
            users.getUser(user.userID(), { includeFullJobTitleID: -1 })
            .then((data) => {
                var pu = new PublicUser(data);
                this.user(pu);
                if (!jobTitleID) {
                    return pu.jobProfile() && pu.jobProfile()[0] && pu.jobProfile()[0].jobTitleID();
                }
                else {
                    return jobTitleID;
                }
            })
            .then((jobTitleID) => {
                // For service professionals:
                if (jobTitleID) {
                    ////////////
                    // User Job Title
                    userListingItem(jobTitleID)
                    .onceLoaded()
                    .then((listing) => {
                        // Fill the job title record
                        this.listingTitle(listing.title);
                        this.userJobTitle(new UserJobTitle(listing));
                        // Load badges
                        return badgesByListing(listing.userListingID)
                        .onceLoaded()
                        .then(expandUserBadges)
                        .then(this.userBadges);
                    })
                    .catch((error) => {
                        showError({
                            title: 'There was an error loading your listing.',
                            error
                        });
                    });
                    ////////////
                    // Submitted Licenses
                    userLicensesCertifications.getList(jobTitleID)
                    .then((list) => {
                        // Save for use in the view
                        this.submittedUserLicensesCertifications(userLicensesCertifications.asModel(list));
                    })
                    .catch((error) => {
                        showError({
                            title: 'There was an error while loading.',
                            error
                        });
                    });
                    ////////////
                    // Active title
                    this.user().selectedJobTitleID(jobTitleID);
                }
            })
            .catch((error) => {
                showError({
                    error,
                    title: 'The user profile could not be loaded.'
                });
            })
            .then(() => {
                // always
                this.isLoading(false);
            });
        }
    }

    /**
     * URL segments template: /{jobTitleID:int}
     * If not jobTitleID, the first one is returned
     * TODO: Is automatic jobTitleID still valid? wasn't buggy? Review and
     * clarify previous comment
     * @param {Object} state
     * @param {Object} state.route
     * @param {Array<string>} state.route.segments Contains per position:
     * - {number} segments[0] jobTitleID
     */
    show(state) {
        super.show(state);

        var params = state.route.segments;
        var jobTitleID = params[0] |0;
        this.loadData(jobTitleID);
        this.jobTitleID(jobTitleID);
        this.refreshTs(new Date());
    }
}

activities.register(ROUTE_NAME, ListingEditor);
<|endoftext|>


<commit_before>(function(){
    var app = angular.module("roster", []);

    /* Controller for Rostermenu */
    app.controller("RosterMenuController", function(){
        this.checkoutAddJid = function(event){
            if(event.key == "Enter")
                Roster_ajaxSearchContact(event.target.value);
        };
    });

    /* Controller for Rosterlist */
    app.controller("RosterController", function($scope){
        $scope.contacts = [];
        $scope.groups = [];
$scope.list = [];

        /* Dictionaries */
        $scope.lookupgroups = {};
        $scope.lookupjid = {};

        $scope.initContacts = function(list){
//$scope.list = list;
            for(var i = 0; i<list.length; i++){
                /* New group */
                if(!(list[i].groupname in $scope.lookupgroups)){
                    el = {
                        'agroup': list[i].groupname,
                        'agroupitems': [],
                        'tombstone': false,
                    };
                    $scope.pushInPlace(el, $scope.contacts, groupnameCompare);

                    /* Create a reference in the localstorage for toggling */
                    localStorage.setItem("rosterGroup_"+list[0].groupname, true);
                }
                /* New jid */
                if(!(list[i].jid in $scope.lookupjid)){
                    el = {
                        'ajid':     list[i].jid,
                        'aval':     list[i].value,
                        'ajiditems': [],
                        'tombstone': false,
                    };
                    $scope.pushInPlace(el, $scope.lookupgroups[list[i].groupname].agroupitems, jidAvalCompare);
                }
                /* New ressource (can't just push the whole set of same jid because there is no set) */
                if(!$scope.isInJidItems(list[i].jid, list[i].ressource)){
                    $scope.pushInPlace(list[i], $scope.lookupjid[list[i].jid].ajiditems, ressourceCompare);
                }
            }
            /* Sort jid by presence in each group and update jid dictionary */
            for(var i = 0; i < $scope.contacts.length; i++){
                //$scope.contacts[i].agroupitems.sort(function(a, b){return a.aval - b.aval;});
                for(var j = 0; j < $scope.contacts[i].agroupitems.length; j++){
                    jid = $scope.contacts[i].agroupitems[j].jid;
                    $scope.lookupjid[jid] = $scope.contacts[i].agroupitems[j];
                }
            }

            $scope.$apply();
        };

        $scope.isInJidItems = function(jid, ressource){
            l = $scope.lookupjid[jid].ajiditems.length;
            for(var i = 0; i < l; i++){
                if($scope.lookupjid[jid].ajiditems[i].ressource == ressource)
                    return true;
            }
            return false;
        }

        $scope.initGroups = function(list){
            for (i in list){
                if(localStorage.getItem("rosterGroup_"+i) == null){
                    list[i] = true;
                    localStorage.setItem("rosterGroup_"+i, true);
                }
                else list[i] = localStorage.getItem("rosterGroup_"+i);
            }
            $scope.groups = list;

            $scope.$apply();
        };

        $scope.deleteContact = function(jid){
            $scope.lookupjid[jid].tombstone = true;

            $scope.$apply();
        };

        $scope.pushInPlace = function(element, array, comparer){
            if(array === $scope.contacts){
                dico = $scope.lookupgroups;
                key = "agroup";
            } else {
                dico = $scope.lookupjid;
                key = "ajid";
            }
            /* Put element in the right place inside array */
            index = locationOf(element, array, comparer);
            /*if(comparer == ressourceCompare)
                console.log("index = "+index);*/
            array.splice(index, 0, element);
            /* Update dictionnary from the appropriate index */
            for(var i=index; i<array.length; i++){
                dico[(array[i][key])] = array[i];
            }
        };

        $scope.updateContact = function(list){
$scope.list.push(list);
            /* New group */
            if(!(list[0].groupname in $scope.lookupgroups)) {
                l = $scope.contacts.length;
                /* A known jid has been moved to a new group */
                if (list[0].jid in $scope.lookupjid){
                    /* Create the group and put the jid in it */
                    el = {
                        'agroup': list[0].groupname,
                        'agroupitems': [{
                            'ajid':     list[0].jid,
                            'aval':     list[0].value,
                            'ajiditems': list,
                            'tombstone': false,
                        }],
                        'tombstone': false,
                    }
                    pushInPlace(el, $scope.contacts, groupnameCompare);

                    /* Kill jid from old location or whole group if it's the only jid */
                    oldgroupname = $scope.lookupjid[list[0].jid].ajiditems[0].groupname;
                    if($scope.lookupgroups[oldgroupname].agroupitems.length == 1)
                        $scope.lookupgroups[oldgroupname].tombstone = true;
                    else
                        $scope.lookupjid[list[0].jid].tombstone = true;

                    /* Update dictionaries */
                    $scope.lookupgroups[list[0].groupname] = $scope.contacts[l];
                    $scope.lookupjid[list[0].jid] = $scope.lookupgroups[list[0].groupname].agroupitems[0];
                }
                /* An unknown jid in an unknown group */
                else {
                    $scope.contacts.push({
                        'agroup': list[0].groupname,
                        'agroupitems': [],
                    });
                    $scope.lookupgroups[list[0].groupname] = $scope.contacts[l];
                }

                /* Create a reference in the localstorage for toggling */
                localStorage.setItem("rosterGroup_"+list[0].groupname, true);
            }

            /* New jid */
            if(!(list[0].jid in $scope.lookupjid)){
                l = $scope.lookupgroups[list[0].groupname].agroupitems.length;
                $scope.lookupgroups[list[0].groupname].agroupitems.push({
                    'ajid':     list[0].jid,
                    'aval':     list[0].value,
                    'ajiditems': [],
                    'tombstone': false,
                });
                $scope.lookupjid[list[0].jid] = $scope.lookupgroups[list[0].groupname].agroupitems[l];
            }
            /* Known jid in another existing group */
            else if(!($scope.lookupjid[list[0].jid].ajiditems[0].groupname == list[0].groupname)){
                /* Kill jid from old location or whole group if it's the only jid */
                oldgroupname = $scope.lookupjid[list[0].jid].ajiditems[0].groupname;
                if($scope.lookupgroups[oldgroupname].agroupitems.length == 1)
                    $scope.lookupgroups[oldgroupname].tombstone = true;
                else
                    $scope.lookupjid[list[0].jid].tombstone = true;

                /* Add to new group */
                l = $scope.lookupgroups[list[0].groupname].agroupitems.length;
                $scope.lookupgroups[list[0].groupname].agroupitems.push({
                    'ajid':     list[0].jid,
                    'aval':     list[0].value,
                    'ajiditems': list,
                    'tombstone': false,
                });

                /* Update JID dictionary */
                $scope.lookupjid[list[0].jid] = $scope.lookupgroups[list[0].groupname].agroupitems[l];

            }

            /* Replace the ajiditems by the new list of ressource */
            $scope.lookupjid[list[0].jid].ajiditems = list;
            /* Update the value of the global presence */
            $scope.lookupjid[list[0].jid].aval = list[0].value;

             /*
             * Sort jid array of the concerned group by global presence of each jid
             * and update jids dictionary
             **/
            $scope.lookupgroups[list[0].groupname].agroupitems.sort(function(a, b){return a.aval - b.aval;});
            for(var j = 0; j < $scope.lookupgroups[list[0].groupname].agroupitems.length; j++){
                jid = $scope.lookupgroups[list[0].groupname].agroupitems[j].jid;
                $scope.lookupjid[jid] = $scope.lookupgroups[list[0].groupname].agroupitems[j];
            }

            $scope.$apply();
        };

        this.showHideGroup = function(g){
            ls = localStorage.getItem("rosterGroup_"+g);
            if(ls == null){
                ls = localStorage.getItem("rosterGroup_Ungrouped");
                g = "Ungrouped";
            }

            ls = (ls == 'true' || ls == true) ? 'false' : 'true';

            localStorage.setItem("rosterGroup_"+g, ls);
            $scope.groups[g] = ls;
        };

        this.postChatAction = function(c){
            eval(c.rosterview.openchat);
        };

        this.postJingleAction = function(c){
            Popup.close();
            Popup.open(c.jid + "/" + c.ressource);
        };

        this.groupIsShown = function(grp){
            if(typeof $scope.groups[grp] != "undefined"){
                return $scope.groups[grp];
            }
            else return $scope.groups["Ungrouped"];
        };

        this.offlineIsShown = function(){
            if(localStorage.getItem("rosterShow_offline") == "true")
                return "offlineshown";
            else
                return "";
        };

        this.getContactTitle = function(c){
            status = c.status || "";
            ressource = c.ressource || "";
            title = c.jid;
            if(status != "") title += " - " + status;
            if(ressource != "") title += " - " + ressource;
            return title;
        };

        this.getContactClient = function(c){
            liclass = "";
            if(c.rosterview.client)
                liclass = "client " + c.rosterview.client;
            return liclass;
        };

        this.getContactClient = function(c){
            liclass = "";
            if(c.rosterview.client)
                liclass = "client " + c.rosterview.client;
            return liclass;
        };

        this.getJidStatusRessource = function(c){
            lititle = c.jid;
            if(c.status != '') lititle += " - " + c.status;
            lititle += " - " + c.ressource;
            return lititle;
        };

        this.getPresenceInactiveClient = function(c){
            liclass = c.rosterview.presencetxt + " " + contact.rosterview.inactive;
            if(c.client) liclass += " client " + c.client;
            return liclass;
        };
    });
})();

/* Functions to call angular inner functions */
function initContacts(tab){
    angular.element(roster).scope().initContacts(JSON.parse(tab));
}

function initGroups(tab){
    angular.element(roster).scope().initGroups(JSON.parse(tab));
}

function updateContact(tab){
    angular.element(roster).scope().updateContact(JSON.parse(tab));
}

function deleteContact(jid){
    angular.element(roster).scope().deleteContact(jid);
}

function locationOf(element, array, comparer, start, end) {
    if (array.length === 0)
        return 0;
    start = start || 0;
    end = end || array.length;
    var pivot = (start + end) >> 1;  // >>1 = /2
    var c = comparer(element, array[pivot]);
    /*if(comparer == ressourceCompare){
        console.log(c);
    }*/
    if ((end - start) <= 1){
        return (c == -1 && pivot > 0) ? pivot : pivot+1;
    }

    switch (c) {
        case -1: return locationOf(element, array, comparer, start, pivot);
        case 0: return pivot;
        case 1: return locationOf(element, array, comparer, pivot, end);
    };
};

/* Object comparison functions */
var groupnameCompare = function(a, b) {
    //console.log(a.agroup+" vs "+b.agroup);
    return a.agroup.localeCompare(b.agroup);
};

var ressourceCompare = function(a, b) {
    n = a.value - b.value;
    //console.log(a.value+" vs "+b.value);
    return n ? n < 0 ? -1 : 1 : 0;
};

var jidCompare = function(a, b) {
    //console.log(a.jid+" vs "+b.ajid);
    return a.jid.localeCompare(b.ajid);
};

var jidAvalCompare = function(a, b) {
    n = a.aval - b.aval;
    return n ? n < 0 ? -1 : 1 : 0;
};

/* === Old functions still in use === */
function showHideOffline() {
    if(localStorage.getItem("rosterShow_offline") != "true" ){
        document.querySelector('ul#rosterlist').className = 'offlineshown';
        localStorage.setItem("rosterShow_offline", "true");
    }
    else{
        document.querySelector('ul#rosterlist').className = '';
        localStorage.setItem("rosterShow_offline", "false");
    }
}

MovimWebsocket.attach(function()
{
    Roster_ajaxGetRoster();
});

movim_add_onload(function()
{
    var search      = document.querySelector('#rostersearch');
    var roster      = document.querySelector('#roster');
    var rosterlist  = document.querySelector('#rosterlist');

    var roster_classback      = document.querySelector('#roster').className;
    var rosterlist_classback  = document.querySelector('#rosterlist').className;

    roster.onblur  = function() {
        roster.className = roster_classback;
        rosterlist.className = rosterlist_classback;
    };
    search.onkeyup = function(event) {
        if(search.value.length > 0) {
            roster.className = 'search';
            rosterlist.className = 'offlineshown';
        } else {
            roster.className = roster_classback;
            rosterlist.className = rosterlist_classback;
        }

        // We clear the old search
        var selector_clear = '#rosterlist div > li';
        var li = document.querySelectorAll(selector_clear);

        for(i = 0; i < li.length; i++) {
            li.item(i).className = '';
        }

        // We select the interesting li
        var selector = '#rosterlist div > li[title*=\'' + search.value + '\']';
        var li = document.querySelectorAll(selector);

        for(i = 0; i < li.length; i++) {
            li.item(i).className = 'found';
        }
    };
});
<commit_msg>- mini fix for pushInPlace()<commit_after>(function(){
    var app = angular.module("roster", []);

    /* Controller for Rostermenu */
    app.controller("RosterMenuController", function(){
        this.checkoutAddJid = function(event){
            if(event.key == "Enter")
                Roster_ajaxSearchContact(event.target.value);
        };
    });

    /* Controller for Rosterlist */
    app.controller("RosterController", function($scope){
        $scope.contacts = [];
        $scope.groups = [];
$scope.list = [];

        /* Dictionaries */
        $scope.lookupgroups = {};
        $scope.lookupjid = {};

        $scope.initContacts = function(list){
//$scope.list = list;
            for(var i = 0; i<list.length; i++){
                /* New group */
                if(!(list[i].groupname in $scope.lookupgroups)){
                    el = {
                        'agroup': list[i].groupname,
                        'agroupitems': [],
                        'tombstone': false,
                    };
                    $scope.pushInPlace(el, $scope.contacts, groupnameCompare);

                    /* Create a reference in the localstorage for toggling */
                    localStorage.setItem("rosterGroup_"+list[0].groupname, true);
                }
                /* New jid */
                if(!(list[i].jid in $scope.lookupjid)){
                    el = {
                        'ajid': list[i].jid,
                        'aval': list[i].value,
                        'ajiditems': [],
                        'tombstone': false,
                    };
                    $scope.pushInPlace(el, $scope.lookupgroups[list[i].groupname].agroupitems, jidAvalCompare);
                }
                /* New ressource (can't just push the whole set of same jid because there is no set) */
                if(!$scope.isInJidItems(list[i].jid, list[i].ressource)){
                    $scope.pushInPlace(list[i], $scope.lookupjid[list[i].jid].ajiditems, ressourceCompare);
                }
            }
            /* Sort jid by presence in each group and update jid dictionary */
            for(var i = 0; i < $scope.contacts.length; i++){
                //$scope.contacts[i].agroupitems.sort(function(a, b){return a.aval - b.aval;});
                for(var j = 0; j < $scope.contacts[i].agroupitems.length; j++){
                    jid = $scope.contacts[i].agroupitems[j].jid;
                    $scope.lookupjid[jid] = $scope.contacts[i].agroupitems[j];
                }
            }

            $scope.$apply();
        };

        $scope.isInJidItems = function(jid, ressource){
            l = $scope.lookupjid[jid].ajiditems.length;
            for(var i = 0; i < l; i++){
                if($scope.lookupjid[jid].ajiditems[i].ressource == ressource)
                    return true;
            }
            return false;
        }

        $scope.initGroups = function(list){
            for (i in list){
                if(localStorage.getItem("rosterGroup_"+i) == null){
                    list[i] = true;
                    localStorage.setItem("rosterGroup_"+i, true);
                }
                else list[i] = localStorage.getItem("rosterGroup_"+i);
            }
            $scope.groups = list;

            $scope.$apply();
        };

        $scope.deleteContact = function(jid){
            $scope.lookupjid[jid].tombstone = true;

            $scope.$apply();
        };

        $scope.pushInPlace = function(element, array, comparer){
            if(array === $scope.contacts){
                dico = $scope.lookupgroups;
                key = "agroup";
            } else {
                dico = $scope.lookupjid;
                key = "ajid";
            }
            /* Put element in the right place inside array */
            index = locationOf(element, array, comparer);
            if(comparer == groupnameCompare){
                console.log(element.agroup);
                console.log("index = "+index);
            }
            array.splice(index, 0, element);
            /* Update dictionnary from the appropriate index */
            for(var i=index; i<array.length; i++){
                dico[(array[i][key])] = array[i];
            }
        };

        $scope.updateContact = function(list){
$scope.list.push(list);
            /* New group */
            if(!(list[0].groupname in $scope.lookupgroups)) {
                l = $scope.contacts.length;
                /* A known jid has been moved to a new group */
                if (list[0].jid in $scope.lookupjid){
                    /* Create the group and put the jid in it */
                    el = {
                        'agroup': list[0].groupname,
                        'agroupitems': [{
                            'ajid':     list[0].jid,
                            'aval':     list[0].value,
                            'ajiditems': list,
                            'tombstone': false,
                        }],
                        'tombstone': false,
                    }
                    pushInPlace(el, $scope.contacts, groupnameCompare);

                    /* Kill jid from old location or whole group if it's the only jid */
                    oldgroupname = $scope.lookupjid[list[0].jid].ajiditems[0].groupname;
                    if($scope.lookupgroups[oldgroupname].agroupitems.length == 1)
                        $scope.lookupgroups[oldgroupname].tombstone = true;
                    else
                        $scope.lookupjid[list[0].jid].tombstone = true;

                    /* Update dictionaries */
                    $scope.lookupgroups[list[0].groupname] = $scope.contacts[l];
                    $scope.lookupjid[list[0].jid] = $scope.lookupgroups[list[0].groupname].agroupitems[0];
                }
                /* An unknown jid in an unknown group */
                else {
                    $scope.contacts.push({
                        'agroup': list[0].groupname,
                        'agroupitems': [],
                    });
                    $scope.lookupgroups[list[0].groupname] = $scope.contacts[l];
                }

                /* Create a reference in the localstorage for toggling */
                localStorage.setItem("rosterGroup_"+list[0].groupname, true);
            }

            /* New jid */
            if(!(list[0].jid in $scope.lookupjid)){
                l = $scope.lookupgroups[list[0].groupname].agroupitems.length;
                $scope.lookupgroups[list[0].groupname].agroupitems.push({
                    'ajid':     list[0].jid,
                    'aval':     list[0].value,
                    'ajiditems': [],
                    'tombstone': false,
                });
                $scope.lookupjid[list[0].jid] = $scope.lookupgroups[list[0].groupname].agroupitems[l];
            }
            /* Known jid in another existing group */
            else if(!($scope.lookupjid[list[0].jid].ajiditems[0].groupname == list[0].groupname)){
                /* Kill jid from old location or whole group if it's the only jid */
                oldgroupname = $scope.lookupjid[list[0].jid].ajiditems[0].groupname;
                if($scope.lookupgroups[oldgroupname].agroupitems.length == 1)
                    $scope.lookupgroups[oldgroupname].tombstone = true;
                else
                    $scope.lookupjid[list[0].jid].tombstone = true;

                /* Add to new group */
                l = $scope.lookupgroups[list[0].groupname].agroupitems.length;
                $scope.lookupgroups[list[0].groupname].agroupitems.push({
                    'ajid':     list[0].jid,
                    'aval':     list[0].value,
                    'ajiditems': list,
                    'tombstone': false,
                });

                /* Update JID dictionary */
                $scope.lookupjid[list[0].jid] = $scope.lookupgroups[list[0].groupname].agroupitems[l];

            }

            /* Replace the ajiditems by the new list of ressource */
            $scope.lookupjid[list[0].jid].ajiditems = list;
            /* Update the value of the global presence */
            $scope.lookupjid[list[0].jid].aval = list[0].value;

             /*
             * Sort jid array of the concerned group by global presence of each jid
             * and update jids dictionary
             **/
            $scope.lookupgroups[list[0].groupname].agroupitems.sort(function(a, b){return a.aval - b.aval;});
            for(var j = 0; j < $scope.lookupgroups[list[0].groupname].agroupitems.length; j++){
                jid = $scope.lookupgroups[list[0].groupname].agroupitems[j].jid;
                $scope.lookupjid[jid] = $scope.lookupgroups[list[0].groupname].agroupitems[j];
            }

            $scope.$apply();
        };

        this.showHideGroup = function(g){
            ls = localStorage.getItem("rosterGroup_"+g);
            if(ls == null){
                ls = localStorage.getItem("rosterGroup_Ungrouped");
                g = "Ungrouped";
            }

            ls = (ls == 'true' || ls == true) ? 'false' : 'true';

            localStorage.setItem("rosterGroup_"+g, ls);
            $scope.groups[g] = ls;
        };

        this.postChatAction = function(c){
            eval(c.rosterview.openchat);
        };

        this.postJingleAction = function(c){
            Popup.close();
            Popup.open(c.jid + "/" + c.ressource);
        };

        this.groupIsShown = function(grp){
            if(typeof $scope.groups[grp] != "undefined"){
                return $scope.groups[grp];
            }
            else return $scope.groups["Ungrouped"];
        };

        this.offlineIsShown = function(){
            if(localStorage.getItem("rosterShow_offline") == "true")
                return "offlineshown";
            else
                return "";
        };

        this.getContactTitle = function(c){
            status = c.status || "";
            ressource = c.ressource || "";
            title = c.jid;
            if(status != "") title += " - " + status;
            if(ressource != "") title += " - " + ressource;
            return title;
        };

        this.getContactClient = function(c){
            liclass = "";
            if(c.rosterview.client)
                liclass = "client " + c.rosterview.client;
            return liclass;
        };

        this.getContactClient = function(c){
            liclass = "";
            if(c.rosterview.client)
                liclass = "client " + c.rosterview.client;
            return liclass;
        };

        this.getJidStatusRessource = function(c){
            lititle = c.jid;
            if(c.status != '') lititle += " - " + c.status;
            lititle += " - " + c.ressource;
            return lititle;
        };

        this.getPresenceInactiveClient = function(c){
            liclass = c.rosterview.presencetxt + " " + contact.rosterview.inactive;
            if(c.client) liclass += " client " + c.client;
            return liclass;
        };
    });
})();

/* Functions to call angular inner functions */
function initContacts(tab){
    angular.element(roster).scope().initContacts(JSON.parse(tab));
}

function initGroups(tab){
    angular.element(roster).scope().initGroups(JSON.parse(tab));
}

function updateContact(tab){
    angular.element(roster).scope().updateContact(JSON.parse(tab));
}

function deleteContact(jid){
    angular.element(roster).scope().deleteContact(jid);
}

function locationOf(element, array, comparer, start, end) {
    if (array.length === 0)
        return 0;
    start = start || 0;
    end = end || array.length;
    var pivot = (start + end) >> 1;  // >>1 = /2
    var c = comparer(element, array[pivot]);
    if(comparer == groupnameCompare){
        console.log(c);
        console.log(pivot);
    }
    if ((end - start) <= 1){
        console.log("end of dicho");
        return (c == -1) ? pivot : pivot+1;
    }

    switch (c) {
        case -1: return locationOf(element, array, comparer, start, pivot);
        case 0: return pivot;
        case 1: return locationOf(element, array, comparer, pivot, end);
    };
};

/* Object comparison functions */
var groupnameCompare = function(a, b) {
    console.log(a.agroup+" vs "+b.agroup);
    return a.agroup.localeCompare(b.agroup);
};

var ressourceCompare = function(a, b) {
    n = a.value - b.value;
    //console.log(a.value+" vs "+b.value);
    return n ? n < 0 ? -1 : 1 : 0;
};

var jidCompare = function(a, b) {
    //console.log(a.jid+" vs "+b.ajid);
    return a.jid.localeCompare(b.ajid);
};

var jidAvalCompare = function(a, b) {
    n = a.aval - b.aval;
    return n ? n < 0 ? -1 : 1 : 0;
};

/* === Old functions still in use === */
function showHideOffline() {
    if(localStorage.getItem("rosterShow_offline") != "true" ){
        document.querySelector('ul#rosterlist').className = 'offlineshown';
        localStorage.setItem("rosterShow_offline", "true");
    }
    else{
        document.querySelector('ul#rosterlist').className = '';
        localStorage.setItem("rosterShow_offline", "false");
    }
}

MovimWebsocket.attach(function()
{
    Roster_ajaxGetRoster();
});

movim_add_onload(function()
{
    var search      = document.querySelector('#rostersearch');
    var roster      = document.querySelector('#roster');
    var rosterlist  = document.querySelector('#rosterlist');

    var roster_classback      = document.querySelector('#roster').className;
    var rosterlist_classback  = document.querySelector('#rosterlist').className;

    roster.onblur  = function() {
        roster.className = roster_classback;
        rosterlist.className = rosterlist_classback;
    };
    search.onkeyup = function(event) {
        if(search.value.length > 0) {
            roster.className = 'search';
            rosterlist.className = 'offlineshown';
        } else {
            roster.className = roster_classback;
            rosterlist.className = rosterlist_classback;
        }

        // We clear the old search
        var selector_clear = '#rosterlist div > li';
        var li = document.querySelectorAll(selector_clear);

        for(i = 0; i < li.length; i++) {
            li.item(i).className = '';
        }

        // We select the interesting li
        var selector = '#rosterlist div > li[title*=\'' + search.value + '\']';
        var li = document.querySelectorAll(selector);

        for(i = 0; i < li.length; i++) {
            li.item(i).className = 'found';
        }
    };
});
<|endoftext|>