<commit_before>var server_events = (function () {

var exports = {};

var waiting_on_homeview_load = true;

var events_stored_while_loading = [];

var get_events_xhr;
var get_events_timeout;
var get_events_failures = 0;
var get_events_params = {};

// This field keeps track of whether we are attempting to
// force-reconnect to the events server due to suspecting we are
// offline.  It is important for avoiding races with the presence
// system when coming back from unsuspend.
exports.suspect_offline = false;

function get_events_success(events) {
    var messages = [];
    var update_message_events = [];
    var post_message_events = [];
    var new_pointer;

    var clean_event = function clean_event(event) {
        // Only log a whitelist of the event to remove private data
        return _.pick(event, 'id', 'type', 'op');
    };

    _.each(events, function (event) {
        try {
            get_events_params.last_event_id = Math.max(get_events_params.last_event_id,
                                                       event.id);
        } catch (ex) {
            blueslip.error('Failed to update last_event_id',
                           {event: clean_event(event)},
                           ex.stack);
        }
    });

    if (waiting_on_homeview_load) {
        events_stored_while_loading = events_stored_while_loading.concat(events);
        return;
    }

    if (events_stored_while_loading.length > 0) {
        events = events_stored_while_loading.concat(events);
        events_stored_while_loading = [];
    }

    // Most events are dispatched via the code server_events_dispatch,
    // called in the default case.  The goal of this split is to avoid
    // contributors needing to read or understand the complex and
    // rarely modified logic for non-normal events.
    var dispatch_event = function dispatch_event(event) {
        switch (event.type) {
        case 'message':
            var msg = event.message;
            msg.flags = event.flags;
            if (event.local_message_id) {
                msg.local_id = event.local_message_id;
                sent_messages.report_event_received(event.local_message_id);
            }
            messages.push(msg);
            break;

        case 'pointer':
            new_pointer = event.pointer;
            break;

        case 'update_message':
            update_message_events.push(event);
            break;

        case 'delete_message':
        case 'submessage':
        case 'update_message_flags':
            post_message_events.push(event);
            break;

        default:
            return server_events_dispatch.dispatch_normal_event(event);
        }
    };

    _.each(events, function (event) {
        try {
            dispatch_event(event);
        } catch (ex1) {
            blueslip.error('Failed to process an event\n' +
                           blueslip.exception_msg(ex1),
                           {event: clean_event(event)},
                           ex1.stack);
        }
    });

    if (messages.length !== 0) {
        // Sort by ID, so that if we get multiple messages back from
        // the server out-of-order, we'll still end up with our
        // message lists in order.
        messages = _.sortBy(messages, 'id');
        try {
            messages = echo.process_from_server(messages);
            _.each(messages, message_store.set_message_booleans);
            message_events.insert_new_messages(messages);
        } catch (ex2) {
            blueslip.error('Failed to insert new messages\n' +
                           blueslip.exception_msg(ex2),
                           undefined,
                           ex2.stack);
        }
    }

    if (new_pointer !== undefined
        && new_pointer > pointer.furthest_read) {
        pointer.furthest_read = new_pointer;
        pointer.server_furthest_read = new_pointer;
        home_msg_list.select_id(new_pointer, {then_scroll: true, use_closest: true});
    }

    if (home_msg_list.selected_id() === -1 && !home_msg_list.empty()) {
        home_msg_list.select_id(home_msg_list.first().id, {then_scroll: false});
    }

    if (update_message_events.length !== 0) {
        try {
            message_events.update_messages(update_message_events);
        } catch (ex3) {
            blueslip.error('Failed to update messages\n' +
                           blueslip.exception_msg(ex3),
                           undefined,
                           ex3.stack);
        }
    }

    // We do things like updating message flags and deleting messages last,
    // to avoid ordering issues that are caused by batch handling of
    // messages above.
    _.each(post_message_events, function (event) {
        server_events_dispatch.dispatch_normal_event(event);
    });
}

function get_events(options) {
    options = _.extend({dont_block: false}, options);

    if (reload_state.is_in_progress()) {
        return;
    }

    get_events_params.dont_block = options.dont_block || get_events_failures > 0;

    if (get_events_params.dont_block) {
        // If we're requesting an immediate re-connect to the server,
        // that means it's fairly likely that this client has been off
        // the Internet and thus may have stale state (which is
        // important for potential presence issues).
        exports.suspect_offline = true;
    }
    if (get_events_params.queue_id === undefined) {
        get_events_params.queue_id = page_params.queue_id;
        get_events_params.last_event_id = page_params.last_event_id;
    }

    if (get_events_xhr !== undefined) {
        get_events_xhr.abort();
    }
    if (get_events_timeout !== undefined) {
        clearTimeout(get_events_timeout);
    }

    get_events_params.client_gravatar = true;

    get_events_timeout = undefined;
    get_events_xhr = channel.get({
        url:      '/json/events',
        data:     get_events_params,
        idempotent: true,
        timeout:  page_params.poll_timeout,
        success: function (data) {
            exports.suspect_offline = false;
            try {
                get_events_xhr = undefined;
                get_events_failures = 0;
                ui_report.hide_error($("#connection-error"));
                $("#connection-error").removeClass('get-events-error');

                get_events_success(data.events);
            } catch (ex) {
                blueslip.error('Failed to handle get_events success\n' +
                               blueslip.exception_msg(ex),
                               undefined,
                               ex.stack);
            }
            get_events_timeout = setTimeout(get_events, 0);
        },
        error: function (xhr, error_type) {
            try {
                get_events_xhr = undefined;
                // If we're old enough that our message queue has been
                // garbage collected, immediately reload.
                if (xhr.status === 400 &&
                    JSON.parse(xhr.responseText).code === 'BAD_EVENT_QUEUE_ID') {
                    page_params.event_queue_expired = true;
                    reload.initiate({immediate: true,
                                     save_pointer: false,
                                     save_narrow: true,
                                     save_compose: true});
                }

                if (error_type === 'abort') {
                    // Don't restart if we explicitly aborted
                    return;
                } else if (error_type === 'timeout') {
                    // Retry indefinitely on timeout.
                    get_events_failures = 0;
                    ui_report.hide_error($("#connection-error"));
                    $("#connection-error").removeClass('get-events-error');
                } else {
                    get_events_failures += 1;
                }

                if (get_events_failures >= 5) {
                    ui_report.show_error($("#connection-error"));
                    $("#connection-error").addClass('get-events-error');
                } else {
                    ui_report.hide_error($("#connection-error"));
                    $("#connection-error").removeClass('get-events-error');
                }
            } catch (ex) {
                blueslip.error('Failed to handle get_events error\n' +
                               blueslip.exception_msg(ex),
                               undefined,
                               ex.stack);
            }
            var retry_sec = Math.min(90, Math.exp(get_events_failures / 2));
            get_events_timeout = setTimeout(get_events, retry_sec * 1000);
        },
    });
}

exports.assert_get_events_running = function assert_get_events_running(error_message) {
    if (get_events_xhr === undefined && get_events_timeout === undefined) {
        exports.restart_get_events({dont_block: true});
        blueslip.error(error_message);
    }
};

exports.restart_get_events = function restart_get_events(options) {
    get_events(options);
};

exports.force_get_events = function force_get_events() {
    get_events_timeout = setTimeout(get_events, 0);
};

exports.home_view_loaded = function home_view_loaded() {
    waiting_on_homeview_load = false;
    get_events_success([]);
    $(document).trigger("home_view_loaded.zulip");
};

var watchdog_time = $.now();
exports.check_for_unsuspend = function () {
    var new_time = $.now();
    if (new_time - watchdog_time > 20000) { // 20 seconds.
        // Defensively reset watchdog_time here in case there's an
        // exception in one of the event handlers
        watchdog_time = new_time;
        // Our app's JS wasn't running, which probably means the machine was
        // asleep.
        $(document).trigger($.Event('unsuspend'));
    }
    watchdog_time = new_time;
};
setInterval(exports.check_for_unsuspend, 5000);

exports.initialize = function () {
    $(document).on('unsuspend', function () {
        // Immediately poll for new events on unsuspend
        blueslip.log("Restarting get_events due to unsuspend");
        get_events_failures = 0;
        exports.restart_get_events({dont_block: true});
    });
    get_events();
};

exports.cleanup_event_queue = function cleanup_event_queue() {
    // Submit a request to the server to cleanup our event queue
    if (page_params.event_queue_expired === true) {
        return;
    }
    blueslip.log("Cleaning up our event queue");
    // Set expired because in a reload we may be called twice.
    page_params.event_queue_expired = true;
    channel.del({
        url:      '/json/events',
        data:     {queue_id: page_params.queue_id},
    });
};

window.addEventListener("beforeunload", function () {
    exports.cleanup_event_queue();
});

// For unit testing
exports._get_events_success = get_events_success;

return exports;

}());
if (typeof module !== 'undefined') {
    module.exports = server_events;
}
window.server_events = server_events;
<commit_msg>server_events: Extract functions for managing connection errors.<commit_after>var server_events = (function () {

var exports = {};

var waiting_on_homeview_load = true;

var events_stored_while_loading = [];

var get_events_xhr;
var get_events_timeout;
var get_events_failures = 0;
var get_events_params = {};

// This field keeps track of whether we are attempting to
// force-reconnect to the events server due to suspecting we are
// offline.  It is important for avoiding races with the presence
// system when coming back from unsuspend.
exports.suspect_offline = false;

function get_events_success(events) {
    var messages = [];
    var update_message_events = [];
    var post_message_events = [];
    var new_pointer;

    var clean_event = function clean_event(event) {
        // Only log a whitelist of the event to remove private data
        return _.pick(event, 'id', 'type', 'op');
    };

    _.each(events, function (event) {
        try {
            get_events_params.last_event_id = Math.max(get_events_params.last_event_id,
                                                       event.id);
        } catch (ex) {
            blueslip.error('Failed to update last_event_id',
                           {event: clean_event(event)},
                           ex.stack);
        }
    });

    if (waiting_on_homeview_load) {
        events_stored_while_loading = events_stored_while_loading.concat(events);
        return;
    }

    if (events_stored_while_loading.length > 0) {
        events = events_stored_while_loading.concat(events);
        events_stored_while_loading = [];
    }

    // Most events are dispatched via the code server_events_dispatch,
    // called in the default case.  The goal of this split is to avoid
    // contributors needing to read or understand the complex and
    // rarely modified logic for non-normal events.
    var dispatch_event = function dispatch_event(event) {
        switch (event.type) {
        case 'message':
            var msg = event.message;
            msg.flags = event.flags;
            if (event.local_message_id) {
                msg.local_id = event.local_message_id;
                sent_messages.report_event_received(event.local_message_id);
            }
            messages.push(msg);
            break;

        case 'pointer':
            new_pointer = event.pointer;
            break;

        case 'update_message':
            update_message_events.push(event);
            break;

        case 'delete_message':
        case 'submessage':
        case 'update_message_flags':
            post_message_events.push(event);
            break;

        default:
            return server_events_dispatch.dispatch_normal_event(event);
        }
    };

    _.each(events, function (event) {
        try {
            dispatch_event(event);
        } catch (ex1) {
            blueslip.error('Failed to process an event\n' +
                           blueslip.exception_msg(ex1),
                           {event: clean_event(event)},
                           ex1.stack);
        }
    });

    if (messages.length !== 0) {
        // Sort by ID, so that if we get multiple messages back from
        // the server out-of-order, we'll still end up with our
        // message lists in order.
        messages = _.sortBy(messages, 'id');
        try {
            messages = echo.process_from_server(messages);
            _.each(messages, message_store.set_message_booleans);
            message_events.insert_new_messages(messages);
        } catch (ex2) {
            blueslip.error('Failed to insert new messages\n' +
                           blueslip.exception_msg(ex2),
                           undefined,
                           ex2.stack);
        }
    }

    if (new_pointer !== undefined
        && new_pointer > pointer.furthest_read) {
        pointer.furthest_read = new_pointer;
        pointer.server_furthest_read = new_pointer;
        home_msg_list.select_id(new_pointer, {then_scroll: true, use_closest: true});
    }

    if (home_msg_list.selected_id() === -1 && !home_msg_list.empty()) {
        home_msg_list.select_id(home_msg_list.first().id, {then_scroll: false});
    }

    if (update_message_events.length !== 0) {
        try {
            message_events.update_messages(update_message_events);
        } catch (ex3) {
            blueslip.error('Failed to update messages\n' +
                           blueslip.exception_msg(ex3),
                           undefined,
                           ex3.stack);
        }
    }

    // We do things like updating message flags and deleting messages last,
    // to avoid ordering issues that are caused by batch handling of
    // messages above.
    _.each(post_message_events, function (event) {
        server_events_dispatch.dispatch_normal_event(event);
    });
}

function show_ui_connection_error() {
    ui_report.show_error($("#connection-error"));
    $("#connection-error").addClass('get-events-error');
}

function hide_ui_connection_error() {
    ui_report.hide_error($("#connection-error"));
    $("#connection-error").removeClass('get-events-error');
}

function get_events(options) {
    options = _.extend({dont_block: false}, options);

    if (reload_state.is_in_progress()) {
        return;
    }

    get_events_params.dont_block = options.dont_block || get_events_failures > 0;

    if (get_events_params.dont_block) {
        // If we're requesting an immediate re-connect to the server,
        // that means it's fairly likely that this client has been off
        // the Internet and thus may have stale state (which is
        // important for potential presence issues).
        exports.suspect_offline = true;
    }
    if (get_events_params.queue_id === undefined) {
        get_events_params.queue_id = page_params.queue_id;
        get_events_params.last_event_id = page_params.last_event_id;
    }

    if (get_events_xhr !== undefined) {
        get_events_xhr.abort();
    }
    if (get_events_timeout !== undefined) {
        clearTimeout(get_events_timeout);
    }

    get_events_params.client_gravatar = true;

    get_events_timeout = undefined;
    get_events_xhr = channel.get({
        url:      '/json/events',
        data:     get_events_params,
        idempotent: true,
        timeout:  page_params.poll_timeout,
        success: function (data) {
            exports.suspect_offline = false;
            try {
                get_events_xhr = undefined;
                get_events_failures = 0;
                hide_ui_connection_error();

                get_events_success(data.events);
            } catch (ex) {
                blueslip.error('Failed to handle get_events success\n' +
                               blueslip.exception_msg(ex),
                               undefined,
                               ex.stack);
            }
            get_events_timeout = setTimeout(get_events, 0);
        },
        error: function (xhr, error_type) {
            try {
                get_events_xhr = undefined;
                // If we're old enough that our message queue has been
                // garbage collected, immediately reload.
                if (xhr.status === 400 &&
                    JSON.parse(xhr.responseText).code === 'BAD_EVENT_QUEUE_ID') {
                    page_params.event_queue_expired = true;
                    reload.initiate({immediate: true,
                                     save_pointer: false,
                                     save_narrow: true,
                                     save_compose: true});
                }

                if (error_type === 'abort') {
                    // Don't restart if we explicitly aborted
                    return;
                } else if (error_type === 'timeout') {
                    // Retry indefinitely on timeout.
                    get_events_failures = 0;
                    hide_ui_connection_error();
                } else {
                    get_events_failures += 1;
                }

                if (get_events_failures >= 5) {
                    show_ui_connection_error();
                } else {
                    hide_ui_connection_error();
                }
            } catch (ex) {
                blueslip.error('Failed to handle get_events error\n' +
                               blueslip.exception_msg(ex),
                               undefined,
                               ex.stack);
            }
            var retry_sec = Math.min(90, Math.exp(get_events_failures / 2));
            get_events_timeout = setTimeout(get_events, retry_sec * 1000);
        },
    });
}

exports.assert_get_events_running = function assert_get_events_running(error_message) {
    if (get_events_xhr === undefined && get_events_timeout === undefined) {
        exports.restart_get_events({dont_block: true});
        blueslip.error(error_message);
    }
};

exports.restart_get_events = function restart_get_events(options) {
    get_events(options);
};

exports.force_get_events = function force_get_events() {
    get_events_timeout = setTimeout(get_events, 0);
};

exports.home_view_loaded = function home_view_loaded() {
    waiting_on_homeview_load = false;
    get_events_success([]);
    $(document).trigger("home_view_loaded.zulip");
};

var watchdog_time = $.now();
exports.check_for_unsuspend = function () {
    var new_time = $.now();
    if (new_time - watchdog_time > 20000) { // 20 seconds.
        // Defensively reset watchdog_time here in case there's an
        // exception in one of the event handlers
        watchdog_time = new_time;
        // Our app's JS wasn't running, which probably means the machine was
        // asleep.
        $(document).trigger($.Event('unsuspend'));
    }
    watchdog_time = new_time;
};
setInterval(exports.check_for_unsuspend, 5000);

exports.initialize = function () {
    $(document).on('unsuspend', function () {
        // Immediately poll for new events on unsuspend
        blueslip.log("Restarting get_events due to unsuspend");
        get_events_failures = 0;
        exports.restart_get_events({dont_block: true});
    });
    get_events();
};

exports.cleanup_event_queue = function cleanup_event_queue() {
    // Submit a request to the server to cleanup our event queue
    if (page_params.event_queue_expired === true) {
        return;
    }
    blueslip.log("Cleaning up our event queue");
    // Set expired because in a reload we may be called twice.
    page_params.event_queue_expired = true;
    channel.del({
        url:      '/json/events',
        data:     {queue_id: page_params.queue_id},
    });
};

window.addEventListener("beforeunload", function () {
    exports.cleanup_event_queue();
});

// For unit testing
exports._get_events_success = get_events_success;

return exports;

}());
if (typeof module !== 'undefined') {
    module.exports = server_events;
}
window.server_events = server_events;
<|endoftext|>


<commit_before>var GameView = {
    ARROW_HEIGHT: 14,
    ARROW_WIDTH: 30,
    roundCount: 0,
    maxHp: 0,
    pixelMultiplier: 20,
    findGameAndRoundId: function( href ) {
        var hrefArray = href.split( "?" )[ 1 ].split( "&" );
        var attribute, gameid, roundid;

        for ( var i = 0; i < hrefArray.length; ++i ) {
            attribute = hrefArray[ i ].split( "=" );
            switch ( attribute[ 0 ] ) {
                case 'roundid':
                    roundid = parseInt( attribute[ 1 ] );
                    break;
                case 'gameid':
                    gameid = parseInt( attribute[ 1 ] );
                    break;
            }
        }
        return {
            gameid: gameid,
            roundid: roundid
        }
    },
    fixUserList: function( creatures ) {
        var hasCreatures = [];
        for ( var i = 0; i < creatures.length; ++i ) {
            creature = creatures[ i ];
            if ( creature.hp > 0 ) {
                hasCreatures[ creature.userid ] = true;
            }
        }

        $( '.playerList li' ).each( function( index, value ) {
            var $node = $( this );
            var userid = $node.attr( 'data-id' );
            var $nameNode = $node.contents()[ 1 ];
            var $newNameNode = $( document.createTextNode( $node.text() ) );

            if ( !hasCreatures[ userid ] ) {
                $newNameNode = $( "" + $newNameNode.text() + "" );
            }
            $nameNode.remove();
            $node.append( $newNameNode );
        } );
    },
    makeUrl: function( gameid, roundid ) {
        return "game/view?gameid=" + gameid + "&roundid=" + roundid;
    },
    fixUrls: function( gameid, roundid ) {
        $( ".next a" ).attr( 'href', this.makeUrl( gameid, roundid + 1 ) );
        $( ".previous a" ).attr( 'href', this.makeUrl( gameid, roundid - 1 ) );
    },
    createCreature: function( creatureInfo, color ) {
        var $creature = $( '

' );
        for ( var attribute in creatureInfo ) {
            var value = creatureInfo[ attribute ];
            $creature.attr( 'data-' + attribute, value );
        }
        $creature.css( {
            left: creatureInfo.x * 20 + 'px',
            top: creatureInfo.y * 20 + 'px'
        } );
        return $creature;
    },
    processCreatures: function( creatures ) {
        $( '.creature' ).remove();
        for ( var i = 0; i < creatures.length; ++i ) {
            var creature = creatures[ i ];
            if ( creature.hp > 0 ) {
                var $user = $( '.playerList li[data-id=' + creature.userid + ']' );
                var username = $user.text();
                var color = $user.find( 'span.bubble' ).attr( 'data-color' );
                creatureInfo = {
                    creatureid: creature.creatureid,
                    username: username,
                    x: creature.x,
                    y: creature.y,
                    hp: creature.hp
                };
                var $creature = GameView.createCreature( creatureInfo, color );
                $( '.gameboard' ).prepend( $creature );
            }
        }
    },
    getMap: function() {
        var href = this.href;
        $.getJSON( href, function( creatures ) {
            var gameInfo = GameView.findGameAndRoundId( href );
            var gameid = gameInfo.gameid;
            var roundid = gameInfo.roundid;

            history.pushState( {}, "", href );

            $( '.roundid' ).text( 'Round ' + roundid );

            $( '.next' ).toggle( roundid + 1 < GameView.roundCount );
            $( '.previous' ).toggle( roundid - 1 >= 0 );
            GameView.fixUrls( gameid, roundid );

            GameView.processCreatures( creatures );

            GameView.fixUserList( creatures );
        } );
        return false;
    },
    fixWidthAndHeight: function( $element ) {
        if ( $element.attr( 'data-width' ) ) {
            $element.css( 'width', $element.attr( 'data-width' ) * GameView.pixelMultiplier );
        }
        if ( $element.attr( 'data-height' ) ) {
            $element.css( 'height', $element.attr( 'data-height' ) * GameView.pixelMultiplier );
        }
    },
    ready: function() {
        GameView.fixWidthAndHeight( $( '.game' ) );
        GameView.fixWidthAndHeight( $( '.time' ) );
        GameView.roundCount = $( '.gamemeta h2' ).attr( 'data-rounds' );
        GameView.maxHp = $( '.gamemeta h2' ).attr( 'data-maxHp' );
        $( document ).on( "mouseover", ".creature", function() {
            var id = this.getAttribute( 'data-creatureid' );
            var username = this.getAttribute( 'data-username' );
            var x = this.getAttribute( 'data-x' );
            var y = this.getAttribute( 'data-y' );
            var hp = this.getAttribute( 'data-hp' );
            var $this = $( this );
            var offsetTop = $this.offset().top - $( '.infobubble' ).height() - 14;
            var $infobubble = $( '.infobubble' );
            var positioning;

            $infobubble.show();
            $( '.player' ).text( username );
            $( '.creatureid' ).text( 'Creature ' + id );
            $( '.location' ).text( x + ', ' + y );
            $( '.numeric' ).text( hp + ' / ' + GameView.maxHp );
            $( '.damage' ).css( 'width', Math.floor( 100 * ( GameView.maxHp - hp ) / GameView.maxHp ) + '%' );
            if ( offsetTop < 0 ) {
                positioning = $this.height() + GameView.ARROW_HEIGHT;
                $infobubble.addClass( 'reversed' );
            }
            else { // bubble doesn't fit on the screen
                positioning = -$infobubble.height() - GameView.ARROW_HEIGHT;
            }
            $infobubble.css( 'top', $this.position().top + positioning );
            $infobubble.css( 'left', $this.position().left - $infobubble.width() + GameView.ARROW_WIDTH );
        } );
        $( document ).on( "mouseout", ".creature", function() {
            var $infobubble = $( '.infobubble' );
            $infobubble.removeClass( 'reversed' );
            $infobubble.hide();
        } );
        $( '.next a' ).click( GameView.getMap );
        $( '.previous a' ).click( GameView.getMap );
    }
}
$( document ).ready( GameView.ready );
<commit_msg>pixelMultiplier to PIXEL_MULTIPLIER<commit_after>var GameView = {
    ARROW_HEIGHT: 14,
    ARROW_WIDTH: 30,
    roundCount: 0,
    maxHp: 0,
    PIXEL_MULTIPLIER: 20,
    findGameAndRoundId: function( href ) {
        var hrefArray = href.split( "?" )[ 1 ].split( "&" );
        var attribute, gameid, roundid;

        for ( var i = 0; i < hrefArray.length; ++i ) {
            attribute = hrefArray[ i ].split( "=" );
            switch ( attribute[ 0 ] ) {
                case 'roundid':
                    roundid = parseInt( attribute[ 1 ] );
                    break;
                case 'gameid':
                    gameid = parseInt( attribute[ 1 ] );
                    break;
            }
        }
        return {
            gameid: gameid,
            roundid: roundid
        }
    },
    fixUserList: function( creatures ) {
        var hasCreatures = [];
        for ( var i = 0; i < creatures.length; ++i ) {
            creature = creatures[ i ];
            if ( creature.hp > 0 ) {
                hasCreatures[ creature.userid ] = true;
            }
        }

        $( '.playerList li' ).each( function( index, value ) {
            var $node = $( this );
            var userid = $node.attr( 'data-id' );
            var $nameNode = $node.contents()[ 1 ];
            var $newNameNode = $( document.createTextNode( $node.text() ) );

            if ( !hasCreatures[ userid ] ) {
                $newNameNode = $( "" + $newNameNode.text() + "" );
            }
            $nameNode.remove();
            $node.append( $newNameNode );
        } );
    },
    makeUrl: function( gameid, roundid ) {
        return "game/view?gameid=" + gameid + "&roundid=" + roundid;
    },
    fixUrls: function( gameid, roundid ) {
        $( ".next a" ).attr( 'href', this.makeUrl( gameid, roundid + 1 ) );
        $( ".previous a" ).attr( 'href', this.makeUrl( gameid, roundid - 1 ) );
    },
    createCreature: function( creatureInfo, color ) {
        var $creature = $( '

' );
        for ( var attribute in creatureInfo ) {
            var value = creatureInfo[ attribute ];
            $creature.attr( 'data-' + attribute, value );
        }
        $creature.css( {
            left: creatureInfo.x * 20 + 'px',
            top: creatureInfo.y * 20 + 'px'
        } );
        return $creature;
    },
    processCreatures: function( creatures ) {
        $( '.creature' ).remove();
        for ( var i = 0; i < creatures.length; ++i ) {
            var creature = creatures[ i ];
            if ( creature.hp > 0 ) {
                var $user = $( '.playerList li[data-id=' + creature.userid + ']' );
                var username = $user.text();
                var color = $user.find( 'span.bubble' ).attr( 'data-color' );
                creatureInfo = {
                    creatureid: creature.creatureid,
                    username: username,
                    x: creature.x,
                    y: creature.y,
                    hp: creature.hp
                };
                var $creature = GameView.createCreature( creatureInfo, color );
                $( '.gameboard' ).prepend( $creature );
            }
        }
    },
    getMap: function() {
        var href = this.href;
        $.getJSON( href, function( creatures ) {
            var gameInfo = GameView.findGameAndRoundId( href );
            var gameid = gameInfo.gameid;
            var roundid = gameInfo.roundid;

            history.pushState( {}, "", href );

            $( '.roundid' ).text( 'Round ' + roundid );

            $( '.next' ).toggle( roundid + 1 < GameView.roundCount );
            $( '.previous' ).toggle( roundid - 1 >= 0 );
            GameView.fixUrls( gameid, roundid );

            GameView.processCreatures( creatures );

            GameView.fixUserList( creatures );
        } );
        return false;
    },
    fixWidthAndHeight: function( $element ) {
        if ( $element.attr( 'data-width' ) ) {
            $element.css( 'width', $element.attr( 'data-width' ) * GameView.PIXEL_MULTIPLIER );
        }
        if ( $element.attr( 'data-height' ) ) {
            $element.css( 'height', $element.attr( 'data-height' ) * GameView.PIXEL_MULTIPLIER );
        }
    },
    ready: function() {
        GameView.fixWidthAndHeight( $( '.game' ) );
        GameView.fixWidthAndHeight( $( '.time' ) );
        GameView.roundCount = $( '.gamemeta h2' ).attr( 'data-rounds' );
        GameView.maxHp = $( '.gamemeta h2' ).attr( 'data-maxHp' );
        $( document ).on( "mouseover", ".creature", function() {
            var id = this.getAttribute( 'data-creatureid' );
            var username = this.getAttribute( 'data-username' );
            var x = this.getAttribute( 'data-x' );
            var y = this.getAttribute( 'data-y' );
            var hp = this.getAttribute( 'data-hp' );
            var $this = $( this );
            var offsetTop = $this.offset().top - $( '.infobubble' ).height() - 14;
            var $infobubble = $( '.infobubble' );
            var positioning;

            $infobubble.show();
            $( '.player' ).text( username );
            $( '.creatureid' ).text( 'Creature ' + id );
            $( '.location' ).text( x + ', ' + y );
            $( '.numeric' ).text( hp + ' / ' + GameView.maxHp );
            $( '.damage' ).css( 'width', Math.floor( 100 * ( GameView.maxHp - hp ) / GameView.maxHp ) + '%' );
            if ( offsetTop < 0 ) {
                positioning = $this.height() + GameView.ARROW_HEIGHT;
                $infobubble.addClass( 'reversed' );
            }
            else { // bubble doesn't fit on the screen
                positioning = -$infobubble.height() - GameView.ARROW_HEIGHT;
            }
            $infobubble.css( 'top', $this.position().top + positioning );
            $infobubble.css( 'left', $this.position().left - $infobubble.width() + GameView.ARROW_WIDTH );
        } );
        $( document ).on( "mouseout", ".creature", function() {
            var $infobubble = $( '.infobubble' );
            $infobubble.removeClass( 'reversed' );
            $infobubble.hide();
        } );
        $( '.next a' ).click( GameView.getMap );
        $( '.previous a' ).click( GameView.getMap );
    }
}
$( document ).ready( GameView.ready );
<|endoftext|>


<commit_before>/**
 * Copyright 2017 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {dev} from '../../../src/log';
import {Observable} from '../../../src/observable';

/** @private @const {string} */
const TAG_ = 'amp-analytics.VisibilityModel';

/**
 * This class implements visibility calculations based on the
 * visibility ratio. It's used for documents, embeds and individual element.
 * @implements {../../../src/service.Disposable}
 */
export class VisibilityModel {
  /**
   * @param {!Object<string, *>} spec
   * @param {function():number} calcVisibility
   */
  constructor(spec, calcVisibility) {
    /** @const @private */
    this.calcVisibility_ = calcVisibility;

    /**
     * Spec parameters.
     * @private {{
     *   visiblePercentageMin: number,
     *   visiblePercentageMax: number,
     *   totalTimeMin: number,
     *   totalTimeMax: number,
     *   continuousTimeMin: number,
     *   continuousTimeMax: number,
     * }}
     */
    this.spec_ = {
      visiblePercentageMin: Number(spec['visiblePercentageMin']) / 100 || 0,
      visiblePercentageMax: Number(spec['visiblePercentageMax']) / 100 || 1,
      totalTimeMin: Number(spec['totalTimeMin']) || 0,
      totalTimeMax: Number(spec['totalTimeMax']) || Infinity,
      continuousTimeMin: Number(spec['continuousTimeMin']) || 0,
      continuousTimeMax: Number(spec['continuousTimeMax']) || Infinity,
    };
    // Above, if visiblePercentageMax was not specified, assume 100%.
    // Here, do allow 0% to be the value if that is what was specified.
    if (String(spec['visiblePercentageMax']).trim() === '0') {
      this.spec_.visiblePercentageMax = 0;
    }

    /** @private {boolean} */
    this.repeat_ = spec['repeat'] === true;

    /** @private {?function()} */
    this.eventResolver_ = null;

    /** @private {?Observable} */
    this.onTriggerObservable_ = new Observable();

    /** @private */
    this.eventPromise_ = new Promise(resolve => {
      this.eventResolver_ = resolve;
    });

    this.eventPromise_.then(() => {
      if (!this.onTriggerObservable_) {
        dev().error(TAG_, 'onTriggerObservable_ is unexpectedly null.');
        return;
      }
      this.onTriggerObservable_.fire();
    });

    /** @private {!Array<!UnlistenDef>} */
    this.unsubscribe_ = [];

    /** @const @private {time} */
    this.createdTime_ = Date.now();

    /** @private {boolean} */
    this.ready_ = true;

    /** @private {boolean} */
    this.reportReady_ = true;

    /** @private {?function():!Promise} */
    this.createReportReadyPromise_ = null;

    /** @private {?number} */
    this.scheduledUpdateTimeoutId_ = null;

    /** @private {boolean} */
    this.matchesVisibility_ = false;

    /** @private {boolean} */
    this.everMatchedVisibility_ = false;

    /** @private {time} duration in milliseconds */
    this.continuousTime_ = 0;

    /** @private {time} duration in milliseconds */
    this.maxContinuousVisibleTime_ = 0;

    /** @private {time} duration in milliseconds */
    this.totalVisibleTime_ = 0;

    /** @private {time} milliseconds since epoch */
    this.firstSeenTime_ = 0;

    /** @private {time} milliseconds since epoch */
    this.lastSeenTime_ = 0;

    /** @private {time} milliseconds since epoch */
    this.firstVisibleTime_ = 0;

    /** @private {time} milliseconds since epoch */
    this.lastVisibleTime_ = 0;

    /** @private {time} percent value in a [0, 1] range */
    this.loadTimeVisibility_ = 0;

    /** @private {number} percent value in a [0, 1] range */
    this.minVisiblePercentage_ = 0;

    /** @private {number} percent value in a [0, 1] range */
    this.maxVisiblePercentage_ = 0;

    /** @private {time} milliseconds since epoch */
    this.lastVisibleUpdateTime_ = 0;

    /** @private {boolean} */
    this.waitToReset_ = false;

    /** @private {?number} */
    this.scheduleRepeatId_ = null;
  }

  /**
   * Refresh counter on visible reset.
   * TODO: Right now all state value are scoped state values that gets reset.
   * We may need to add support to global state values,
   * that never reset like globalTotalVisibleTime.
   * Note: loadTimeVisibility is an exception.
   * @private
   */
  reset_() {
    dev().assert(!this.eventResolver_,
        'Attempt to refresh visible event before previous one resolve');
    this.eventPromise_ = new Promise(resolve => {
      this.eventResolver_ = resolve;
    });
    this.eventPromise_.then(() => {
      if (!this.onTriggerObservable_) {
        dev().error(TAG_, 'onTriggerObservable_ is unexpectedly null.');
        return;
      }
      this.onTriggerObservable_.fire();
    });
    this.scheduleRepeatId_ = null;
    this.everMatchedVisibility_ = false;
    this.matchesVisibility_ = false;
    this.continuousTime_ = 0;
    this.maxContinuousVisibleTime_ = 0;
    this.totalVisibleTime_ = 0;
    this.firstVisibleTime_ = 0;
    this.firstSeenTime_ = 0;
    this.lastSeenTime_ = 0;
    this.lastVisibleTime_ = 0;
    this.minVisiblePercentage_ = 0;
    this.maxVisiblePercentage_ = 0;
    this.lastVisibleUpdateTime_ = 0;
    this.waitToReset_ = false;
  }

  /**
   * Function that visibilityManager can used to dispose model or reset model
   */
  maybeDispose() {
    if (!this.repeat_) {
      this.dispose();
    }
  }

  /** @override */
  dispose() {
    if (this.scheduledUpdateTimeoutId_) {
      clearTimeout(this.scheduledUpdateTimeoutId_);
      this.scheduledUpdateTimeoutId_ = null;
    }
    if (this.scheduleRepeatId_) {
      clearTimeout(this.scheduleRepeatId_);
      this.scheduleRepeatId_ = null;
    }
    this.unsubscribe_.forEach(unsubscribe => {
      unsubscribe();
    });
    this.unsubscribe_.length = 0;
    this.eventResolver_ = null;
    // TODO(jonkeller): Investigate why dispose() can be called twice,
    // necessitating this "if", and the same "if" elsewhere in this file.
    if (!this.onTriggerObservable_) {
      dev().error(TAG_,
          'dispose() called when onTriggerObservable_ already null.');
      return;
    }
    this.onTriggerObservable_.removeAll();
    this.onTriggerObservable_ = null;
  }

  /**
   * Adds the unsubscribe handler that will be called when this visibility
   * model is destroyed.
   * @param {!UnlistenDef} handler
   */
  unsubscribe(handler) {
    this.unsubscribe_.push(handler);
  }

  /**
   * Adds the event handler that will be called when all visibility conditions
   * have been met.
   * @param {function()} handler
   */
  onTriggerEvent(handler) {
    if (this.onTriggerObservable_) {
      this.onTriggerObservable_.add(handler);
    } else {
      dev().error(TAG_, 'onTriggerObservable_ is unexpectedly null.');
    }
    if (this.eventPromise_ && !this.eventResolver_) {
      // If eventPromise has already resolved, need to call handler manually.
      handler();
    }
  }

  /**
   * Sets whether this object is ready. Ready means that visibility is
   * ready to be calculated, e.g. because an element has been
   * sufficiently rendered.
   * @param {boolean} ready
   */
  setReady(ready) {
    this.ready_ = ready;
    this.update();
  }

  /**
   * Sets that the model needs to wait on extra report ready promise
   * after all visibility conditions have been met to call report handler
   * @param {function():!Promise} callback
   */
  setReportReady(callback) {
    this.reportReady_ = false;
    this.createReportReadyPromise_ = callback;
  }

  /**
   * @return {number}
   * @private
   */
  getVisibility_() {
    return this.ready_ ? this.calcVisibility_() : 0;
  }

  /**
   * Runs the calculation cycle.
   */
  update() {
    this.update_(this.getVisibility_());
  }

  /**
   * Returns the calculated state of visibility.
   * @param {time} startTime
   * @return {!Object<string, string|number>}
   */
  getState(startTime) {
    return {
      // Observed times, relative to the `startTime`.
      firstSeenTime: timeBase(this.firstSeenTime_, startTime),
      lastSeenTime: timeBase(this.lastSeenTime_, startTime),
      lastVisibleTime: timeBase(this.lastVisibleTime_, startTime),
      firstVisibleTime: timeBase(this.firstVisibleTime_, startTime),

      // Durations.
      maxContinuousVisibleTime: this.maxContinuousVisibleTime_,
      totalVisibleTime: this.totalVisibleTime_,

      // Visibility percents.
      loadTimeVisibility: this.loadTimeVisibility_ * 100 || 0,
      minVisiblePercentage: this.minVisiblePercentage_ * 100,
      maxVisiblePercentage: this.maxVisiblePercentage_ * 100,
    };
  }

  /**
   * @param {number} visibility
   * @private
   */
  update_(visibility) {
    // Update state and check if all conditions are satisfied
    if (this.waitToReset_) {
      if (!this.isVisibilityMatch_(visibility)) {
        // We were waiting for a condition to become unmet, and now it has
        this.reset_();
      }
      return;
    }
    if (!this.eventResolver_) {
      return;
    }
    const conditionsMet = this.updateCounters_(visibility);
    if (conditionsMet) {
      if (this.scheduledUpdateTimeoutId_) {
        clearTimeout(this.scheduledUpdateTimeoutId_);
        this.scheduledUpdateTimeoutId_ = null;
      }
      if (this.reportReady_) {
        // TODO(jonkeller): Can we eliminate eventResolver_?
        this.eventResolver_();
        this.eventResolver_ = null;
        if (this.repeat_) {
          this.waitToReset_ = true;
          this.continuousTime_ = 0;
        }
      } else if (this.createReportReadyPromise_) {
        // Report when report ready promise resolve
        const reportReadyPromise = this.createReportReadyPromise_();
        this.createReportReadyPromise_ = null;
        reportReadyPromise.then(() => {
          this.reportReady_ = true;
          // Need to update one more time in case time exceeds
          // maxContinuousVisibleTime.
          this.update();
        });
      }
    } else if (this.matchesVisibility_ && !this.scheduledUpdateTimeoutId_) {
      // There is unmet duration condition, schedule a check
      const timeToWait = this.computeTimeToWait_();
      if (timeToWait > 0) {
        this.scheduledUpdateTimeoutId_ = setTimeout(() => {
          this.scheduledUpdateTimeoutId_ = null;
          this.update();
        }, timeToWait);
      }
    } else if (!this.matchesVisibility_ && this.scheduledUpdateTimeoutId_) {
      clearTimeout(this.scheduledUpdateTimeoutId_);
      this.scheduledUpdateTimeoutId_ = null;
    }
  }

  /**
   * Check if visibility fall into the percentage range
   * @param {number} visibility
   * @return {boolean}
   */
  isVisibilityMatch_(visibility) {
    dev().assert(visibility >= 0 && visibility <= 1,
        'invalid visibility value: %s', visibility);
    // Special case: If visiblePercentageMin is 100%, then it doesn't make
    // sense to do the usual (min, max] since that would never be true.
    if (this.spec_.visiblePercentageMin == 1) {
      return visibility == 1;
    }
    // Special case: If visiblePercentageMax is 0%, then we
    // want to ping when the creative becomes not visible.
    if (this.spec_.visiblePercentageMax == 0) {
      return visibility == 0;
    }
    return visibility > this.spec_.visiblePercentageMin &&
        visibility <= this.spec_.visiblePercentageMax;
  }

  /**
   * @param {number} visibility
   * @return {boolean} true
   * @private
   */
  updateCounters_(visibility) {
    dev().assert(visibility >= 0 && visibility <= 1,
        'invalid visibility value: %s', visibility);
    const now = Date.now();

    if (visibility > 0) {
      this.firstSeenTime_ = this.firstSeenTime_ || now;
      this.lastSeenTime_ = now;
      // Consider it as load time visibility if this happens within 300ms of
      // page load.
      if (!this.loadTimeVisibility_ && (now - this.createdTime_) < 300) {
        this.loadTimeVisibility_ = visibility;
      }
    }

    const prevMatchesVisibility = this.matchesVisibility_;
    const timeSinceLastUpdate =
        this.lastVisibleUpdateTime_ ? now - this.lastVisibleUpdateTime_ : 0;
    this.matchesVisibility_ = this.isVisibilityMatch_(visibility);
    if (this.matchesVisibility_) {
      this.everMatchedVisibility_ = true;
      if (prevMatchesVisibility) {
        // Keep counting.
        this.totalVisibleTime_ += timeSinceLastUpdate;
        this.continuousTime_ += timeSinceLastUpdate;
        this.maxContinuousVisibleTime_ =
            Math.max(this.maxContinuousVisibleTime_, this.continuousTime_);
      } else {
        // The resource came into view: start counting.
        dev().assert(!this.lastVisibleUpdateTime_);
        this.firstVisibleTime_ = this.firstVisibleTime_ || now;
      }
      this.lastVisibleUpdateTime_ = now;
      this.minVisiblePercentage_ =
          this.minVisiblePercentage_ > 0 ?
            Math.min(this.minVisiblePercentage_, visibility) :
            visibility;
      this.maxVisiblePercentage_ =
          Math.max(this.maxVisiblePercentage_, visibility);
      this.lastVisibleTime_ = now;
    } else if (prevMatchesVisibility) {
      // The resource went out of view. Do final calculations and reset state.
      dev().assert(this.lastVisibleUpdateTime_ > 0);

      this.maxContinuousVisibleTime_ = Math.max(
          this.maxContinuousVisibleTime_,
          this.continuousTime_ + timeSinceLastUpdate);

      // Reset for next visibility event.
      this.lastVisibleUpdateTime_ = 0;
      this.totalVisibleTime_ += timeSinceLastUpdate;
      this.continuousTime_ = 0; // Clear only after max is calculated above.
      this.lastVisibleTime_ = now;
    }

    return this.everMatchedVisibility_ &&
        (this.totalVisibleTime_ >= this.spec_.totalTimeMin) &&
        (this.totalVisibleTime_ <= this.spec_.totalTimeMax) &&
        (this.maxContinuousVisibleTime_ >= this.spec_.continuousTimeMin) &&
        (this.maxContinuousVisibleTime_ <= this.spec_.continuousTimeMax);
  }

  /**
   * Computes time, assuming the object is currently visible, that it'd take
   * it to match all timing requirements.
   * @return {time}
   * @private
   */
  computeTimeToWait_() {
    const waitForContinuousTime = Math.max(
        this.spec_.continuousTimeMin - this.continuousTime_, 0);
    const waitForTotalTime = Math.max(
        this.spec_.totalTimeMin - this.totalVisibleTime_, 0);
    const maxWaitTime = Math.max(waitForContinuousTime, waitForTotalTime);
    return Math.min(
        maxWaitTime,
        waitForContinuousTime || Infinity,
        waitForTotalTime || Infinity);
  }
}

/**
 * Calculates the specified time based on the given `baseTime`.
 * @param {time} time
 * @param {time} baseTime
 * @return {time}
 */
function timeBase(time, baseTime) {
  return time >= baseTime ? time - baseTime : 0;
}
<commit_msg>remove old error logging (#12875)<commit_after>/**
 * Copyright 2017 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {dev} from '../../../src/log';
import {Observable} from '../../../src/observable';

/**
 * This class implements visibility calculations based on the
 * visibility ratio. It's used for documents, embeds and individual element.
 * @implements {../../../src/service.Disposable}
 */
export class VisibilityModel {
  /**
   * @param {!Object<string, *>} spec
   * @param {function():number} calcVisibility
   */
  constructor(spec, calcVisibility) {
    /** @const @private */
    this.calcVisibility_ = calcVisibility;

    /**
     * Spec parameters.
     * @private {{
     *   visiblePercentageMin: number,
     *   visiblePercentageMax: number,
     *   totalTimeMin: number,
     *   totalTimeMax: number,
     *   continuousTimeMin: number,
     *   continuousTimeMax: number,
     * }}
     */
    this.spec_ = {
      visiblePercentageMin: Number(spec['visiblePercentageMin']) / 100 || 0,
      visiblePercentageMax: Number(spec['visiblePercentageMax']) / 100 || 1,
      totalTimeMin: Number(spec['totalTimeMin']) || 0,
      totalTimeMax: Number(spec['totalTimeMax']) || Infinity,
      continuousTimeMin: Number(spec['continuousTimeMin']) || 0,
      continuousTimeMax: Number(spec['continuousTimeMax']) || Infinity,
    };
    // Above, if visiblePercentageMax was not specified, assume 100%.
    // Here, do allow 0% to be the value if that is what was specified.
    if (String(spec['visiblePercentageMax']).trim() === '0') {
      this.spec_.visiblePercentageMax = 0;
    }

    /** @private {boolean} */
    this.repeat_ = spec['repeat'] === true;

    /** @private {?function()} */
    this.eventResolver_ = null;

    /** @private {?Observable} */
    this.onTriggerObservable_ = new Observable();

    /** @private */
    this.eventPromise_ = new Promise(resolve => {
      this.eventResolver_ = resolve;
    });

    this.eventPromise_.then(() => {
      this.onTriggerObservable_.fire();
    });

    /** @private {!Array<!UnlistenDef>} */
    this.unsubscribe_ = [];

    /** @const @private {time} */
    this.createdTime_ = Date.now();

    /** @private {boolean} */
    this.ready_ = true;

    /** @private {boolean} */
    this.reportReady_ = true;

    /** @private {?function():!Promise} */
    this.createReportReadyPromise_ = null;

    /** @private {?number} */
    this.scheduledUpdateTimeoutId_ = null;

    /** @private {boolean} */
    this.matchesVisibility_ = false;

    /** @private {boolean} */
    this.everMatchedVisibility_ = false;

    /** @private {time} duration in milliseconds */
    this.continuousTime_ = 0;

    /** @private {time} duration in milliseconds */
    this.maxContinuousVisibleTime_ = 0;

    /** @private {time} duration in milliseconds */
    this.totalVisibleTime_ = 0;

    /** @private {time} milliseconds since epoch */
    this.firstSeenTime_ = 0;

    /** @private {time} milliseconds since epoch */
    this.lastSeenTime_ = 0;

    /** @private {time} milliseconds since epoch */
    this.firstVisibleTime_ = 0;

    /** @private {time} milliseconds since epoch */
    this.lastVisibleTime_ = 0;

    /** @private {time} percent value in a [0, 1] range */
    this.loadTimeVisibility_ = 0;

    /** @private {number} percent value in a [0, 1] range */
    this.minVisiblePercentage_ = 0;

    /** @private {number} percent value in a [0, 1] range */
    this.maxVisiblePercentage_ = 0;

    /** @private {time} milliseconds since epoch */
    this.lastVisibleUpdateTime_ = 0;

    /** @private {boolean} */
    this.waitToReset_ = false;

    /** @private {?number} */
    this.scheduleRepeatId_ = null;
  }

  /**
   * Refresh counter on visible reset.
   * TODO: Right now all state value are scoped state values that gets reset.
   * We may need to add support to global state values,
   * that never reset like globalTotalVisibleTime.
   * Note: loadTimeVisibility is an exception.
   * @private
   */
  reset_() {
    dev().assert(!this.eventResolver_,
        'Attempt to refresh visible event before previous one resolve');
    this.eventPromise_ = new Promise(resolve => {
      this.eventResolver_ = resolve;
    });
    this.eventPromise_.then(() => {
      this.onTriggerObservable_.fire();
    });
    this.scheduleRepeatId_ = null;
    this.everMatchedVisibility_ = false;
    this.matchesVisibility_ = false;
    this.continuousTime_ = 0;
    this.maxContinuousVisibleTime_ = 0;
    this.totalVisibleTime_ = 0;
    this.firstVisibleTime_ = 0;
    this.firstSeenTime_ = 0;
    this.lastSeenTime_ = 0;
    this.lastVisibleTime_ = 0;
    this.minVisiblePercentage_ = 0;
    this.maxVisiblePercentage_ = 0;
    this.lastVisibleUpdateTime_ = 0;
    this.waitToReset_ = false;
  }

  /**
   * Function that visibilityManager can used to dispose model or reset model
   */
  maybeDispose() {
    if (!this.repeat_) {
      this.dispose();
    }
  }

  /** @override */
  dispose() {
    if (this.scheduledUpdateTimeoutId_) {
      clearTimeout(this.scheduledUpdateTimeoutId_);
      this.scheduledUpdateTimeoutId_ = null;
    }
    if (this.scheduleRepeatId_) {
      clearTimeout(this.scheduleRepeatId_);
      this.scheduleRepeatId_ = null;
    }
    this.unsubscribe_.forEach(unsubscribe => {
      unsubscribe();
    });
    this.unsubscribe_.length = 0;
    this.eventResolver_ = null;
    if (this.onTriggerObservable_) {
      this.onTriggerObservable_.removeAll();
      this.onTriggerObservable_ = null;
    }
  }

  /**
   * Adds the unsubscribe handler that will be called when this visibility
   * model is destroyed.
   * @param {!UnlistenDef} handler
   */
  unsubscribe(handler) {
    this.unsubscribe_.push(handler);
  }

  /**
   * Adds the event handler that will be called when all visibility conditions
   * have been met.
   * @param {function()} handler
   */
  onTriggerEvent(handler) {
    if (this.onTriggerObservable_) {
      this.onTriggerObservable_.add(handler);
    }
    if (this.eventPromise_ && !this.eventResolver_) {
      // If eventPromise has already resolved, need to call handler manually.
      handler();
    }
  }

  /**
   * Sets whether this object is ready. Ready means that visibility is
   * ready to be calculated, e.g. because an element has been
   * sufficiently rendered.
   * @param {boolean} ready
   */
  setReady(ready) {
    this.ready_ = ready;
    this.update();
  }

  /**
   * Sets that the model needs to wait on extra report ready promise
   * after all visibility conditions have been met to call report handler
   * @param {function():!Promise} callback
   */
  setReportReady(callback) {
    this.reportReady_ = false;
    this.createReportReadyPromise_ = callback;
  }

  /**
   * @return {number}
   * @private
   */
  getVisibility_() {
    return this.ready_ ? this.calcVisibility_() : 0;
  }

  /**
   * Runs the calculation cycle.
   */
  update() {
    this.update_(this.getVisibility_());
  }

  /**
   * Returns the calculated state of visibility.
   * @param {time} startTime
   * @return {!Object<string, string|number>}
   */
  getState(startTime) {
    return {
      // Observed times, relative to the `startTime`.
      firstSeenTime: timeBase(this.firstSeenTime_, startTime),
      lastSeenTime: timeBase(this.lastSeenTime_, startTime),
      lastVisibleTime: timeBase(this.lastVisibleTime_, startTime),
      firstVisibleTime: timeBase(this.firstVisibleTime_, startTime),

      // Durations.
      maxContinuousVisibleTime: this.maxContinuousVisibleTime_,
      totalVisibleTime: this.totalVisibleTime_,

      // Visibility percents.
      loadTimeVisibility: this.loadTimeVisibility_ * 100 || 0,
      minVisiblePercentage: this.minVisiblePercentage_ * 100,
      maxVisiblePercentage: this.maxVisiblePercentage_ * 100,
    };
  }

  /**
   * @param {number} visibility
   * @private
   */
  update_(visibility) {
    // Update state and check if all conditions are satisfied
    if (this.waitToReset_) {
      if (!this.isVisibilityMatch_(visibility)) {
        // We were waiting for a condition to become unmet, and now it has
        this.reset_();
      }
      return;
    }
    if (!this.eventResolver_) {
      return;
    }
    const conditionsMet = this.updateCounters_(visibility);
    if (conditionsMet) {
      if (this.scheduledUpdateTimeoutId_) {
        clearTimeout(this.scheduledUpdateTimeoutId_);
        this.scheduledUpdateTimeoutId_ = null;
      }
      if (this.reportReady_) {
        // TODO(jonkeller): Can we eliminate eventResolver_?
        this.eventResolver_();
        this.eventResolver_ = null;
        if (this.repeat_) {
          this.waitToReset_ = true;
          this.continuousTime_ = 0;
        }
      } else if (this.createReportReadyPromise_) {
        // Report when report ready promise resolve
        const reportReadyPromise = this.createReportReadyPromise_();
        this.createReportReadyPromise_ = null;
        reportReadyPromise.then(() => {
          this.reportReady_ = true;
          // Need to update one more time in case time exceeds
          // maxContinuousVisibleTime.
          this.update();
        });
      }
    } else if (this.matchesVisibility_ && !this.scheduledUpdateTimeoutId_) {
      // There is unmet duration condition, schedule a check
      const timeToWait = this.computeTimeToWait_();
      if (timeToWait > 0) {
        this.scheduledUpdateTimeoutId_ = setTimeout(() => {
          this.scheduledUpdateTimeoutId_ = null;
          this.update();
        }, timeToWait);
      }
    } else if (!this.matchesVisibility_ && this.scheduledUpdateTimeoutId_) {
      clearTimeout(this.scheduledUpdateTimeoutId_);
      this.scheduledUpdateTimeoutId_ = null;
    }
  }

  /**
   * Check if visibility fall into the percentage range
   * @param {number} visibility
   * @return {boolean}
   */
  isVisibilityMatch_(visibility) {
    dev().assert(visibility >= 0 && visibility <= 1,
        'invalid visibility value: %s', visibility);
    // Special case: If visiblePercentageMin is 100%, then it doesn't make
    // sense to do the usual (min, max] since that would never be true.
    if (this.spec_.visiblePercentageMin == 1) {
      return visibility == 1;
    }
    // Special case: If visiblePercentageMax is 0%, then we
    // want to ping when the creative becomes not visible.
    if (this.spec_.visiblePercentageMax == 0) {
      return visibility == 0;
    }
    return visibility > this.spec_.visiblePercentageMin &&
        visibility <= this.spec_.visiblePercentageMax;
  }

  /**
   * @param {number} visibility
   * @return {boolean} true
   * @private
   */
  updateCounters_(visibility) {
    dev().assert(visibility >= 0 && visibility <= 1,
        'invalid visibility value: %s', visibility);
    const now = Date.now();

    if (visibility > 0) {
      this.firstSeenTime_ = this.firstSeenTime_ || now;
      this.lastSeenTime_ = now;
      // Consider it as load time visibility if this happens within 300ms of
      // page load.
      if (!this.loadTimeVisibility_ && (now - this.createdTime_) < 300) {
        this.loadTimeVisibility_ = visibility;
      }
    }

    const prevMatchesVisibility = this.matchesVisibility_;
    const timeSinceLastUpdate =
        this.lastVisibleUpdateTime_ ? now - this.lastVisibleUpdateTime_ : 0;
    this.matchesVisibility_ = this.isVisibilityMatch_(visibility);
    if (this.matchesVisibility_) {
      this.everMatchedVisibility_ = true;
      if (prevMatchesVisibility) {
        // Keep counting.
        this.totalVisibleTime_ += timeSinceLastUpdate;
        this.continuousTime_ += timeSinceLastUpdate;
        this.maxContinuousVisibleTime_ =
            Math.max(this.maxContinuousVisibleTime_, this.continuousTime_);
      } else {
        // The resource came into view: start counting.
        dev().assert(!this.lastVisibleUpdateTime_);
        this.firstVisibleTime_ = this.firstVisibleTime_ || now;
      }
      this.lastVisibleUpdateTime_ = now;
      this.minVisiblePercentage_ =
          this.minVisiblePercentage_ > 0 ?
            Math.min(this.minVisiblePercentage_, visibility) :
            visibility;
      this.maxVisiblePercentage_ =
          Math.max(this.maxVisiblePercentage_, visibility);
      this.lastVisibleTime_ = now;
    } else if (prevMatchesVisibility) {
      // The resource went out of view. Do final calculations and reset state.
      dev().assert(this.lastVisibleUpdateTime_ > 0);

      this.maxContinuousVisibleTime_ = Math.max(
          this.maxContinuousVisibleTime_,
          this.continuousTime_ + timeSinceLastUpdate);

      // Reset for next visibility event.
      this.lastVisibleUpdateTime_ = 0;
      this.totalVisibleTime_ += timeSinceLastUpdate;
      this.continuousTime_ = 0; // Clear only after max is calculated above.
      this.lastVisibleTime_ = now;
    }

    return this.everMatchedVisibility_ &&
        (this.totalVisibleTime_ >= this.spec_.totalTimeMin) &&
        (this.totalVisibleTime_ <= this.spec_.totalTimeMax) &&
        (this.maxContinuousVisibleTime_ >= this.spec_.continuousTimeMin) &&
        (this.maxContinuousVisibleTime_ <= this.spec_.continuousTimeMax);
  }

  /**
   * Computes time, assuming the object is currently visible, that it'd take
   * it to match all timing requirements.
   * @return {time}
   * @private
   */
  computeTimeToWait_() {
    const waitForContinuousTime = Math.max(
        this.spec_.continuousTimeMin - this.continuousTime_, 0);
    const waitForTotalTime = Math.max(
        this.spec_.totalTimeMin - this.totalVisibleTime_, 0);
    const maxWaitTime = Math.max(waitForContinuousTime, waitForTotalTime);
    return Math.min(
        maxWaitTime,
        waitForContinuousTime || Infinity,
        waitForTotalTime || Infinity);
  }
}

/**
 * Calculates the specified time based on the given `baseTime`.
 * @param {time} time
 * @param {time} baseTime
 * @return {time}
 */
function timeBase(time, baseTime) {
  return time >= baseTime ? time - baseTime : 0;
}
<|endoftext|>


<commit_before>/**
 * This class is the client-side representation for the permutation features of
 * Jasy and supports features like auto-selecting builds based on specific feature sets.
 */
(function()
{
  // Small hack to correctly bootstrap the next line
  jasy.Permutation = {getValue:function(){}};

  // The build system is replacing this call via the loader permutation
  var values = jasy.Permutation.getValue("jasy.values");

  delete jasy.Permutation;

  // Stores all selected values in a simple map
  var selected = {};

  var checksum = values ? (function()
  {
    // Process entries
    var key = [];
    for (var i=0, l=values.length; i<l; i++)
    {
      var entry = values[i];
      var name = entry[0];
      var allowed = entry[1];

      var test = entry[2];
      if (test)
      {
        var value = "VALUE" in test ? test.VALUE : test.get(name);

        // Fallback to first value if test results in unsupported value
        if (allowed.indexOf(value) == -1) {
          value = allowed[0];
        }
      }
      else
      {
        // In cases with no test, we don't have an array of values but just a value
        value = allowed;
      }

      selected[name] = value;
      key.push(name + ":" + value);
    }

    var adler32 = jasy.Adler32.compute(key.join(";"));
    var prefix = adler32 < 0 ? "a" : "b";
    var checksum = prefix + (adler32 < 0 ? -adler32 : adler32).toString(16);

    return checksum;
  })() : "";

  Core.declare("jasy.Permutation",
  {
    /** {Map} Currently selected values from Permutation data */
    selected : selected,

    /** {Number} Holds the checksum for the current permutation which is auto detected by features or by compiled-in data */
    CHECKSUM : checksum,

    isEnabled : function(name) {
      return selected[name] == true;
    },

    isSet : function(name, value) {
      return selected[name] == value;
    },

    getValue : function(name) {
      return selected[name];
    },

    loadScripts : function(uris)
    {
      var patched = [];
      for (var i=0, l=uris.length; i<l; i++) {
        patched[i] = this.patchFilename(uris[i]);
      }

      return jasy.Loader.loadScripts(patched);
    },

    patchFilename : function(fileName)
    {
      var pos = fileName.lastIndexOf(".");
      var checksum = "-" + this.CHECKSUM;

      if (pos == -1)
      {
        return fileName + checksum;
      }
      else
      {
        var fileExt = fileName.substring(pos+1);
        return fileName.substring(0, pos) + checksum + "." + fileExt;
      }
    }
  });
})();

<commit_msg>Added comment<commit_after>/**
 * This class is the client-side representation for the permutation features of
 * Jasy and supports features like auto-selecting builds based on specific feature sets.
 */
(function()
{
  // Small hack to correctly bootstrap the next line
  jasy.Permutation = {getValue:function(){}};

  // The build system is replacing this call via the loader permutation
  var values = jasy.Permutation.getValue("jasy.values");

  // Cleanup for first line
  delete jasy.Permutation;

  // Stores all selected values in a simple map
  var selected = {};

  var checksum = values ? (function()
  {
    // Process entries
    var key = [];
    for (var i=0, l=values.length; i<l; i++)
    {
      var entry = values[i];
      var name = entry[0];
      var allowed = entry[1];

      var test = entry[2];
      if (test)
      {
        var value = "VALUE" in test ? test.VALUE : test.get(name);

        // Fallback to first value if test results in unsupported value
        if (allowed.indexOf(value) == -1) {
          value = allowed[0];
        }
      }
      else
      {
        // In cases with no test, we don't have an array of values but just a value
        value = allowed;
      }

      selected[name] = value;
      key.push(name + ":" + value);
    }

    var adler32 = jasy.Adler32.compute(key.join(";"));
    var prefix = adler32 < 0 ? "a" : "b";
    var checksum = prefix + (adler32 < 0 ? -adler32 : adler32).toString(16);

    return checksum;
  })() : "";

  Core.declare("jasy.Permutation",
  {
    /** {Map} Currently selected values from Permutation data */
    selected : selected,

    /** {Number} Holds the checksum for the current permutation which is auto detected by features or by compiled-in data */
    CHECKSUM : checksum,

    isEnabled : function(name) {
      return selected[name] == true;
    },

    isSet : function(name, value) {
      return selected[name] == value;
    },

    getValue : function(name) {
      return selected[name];
    },

    loadScripts : function(uris)
    {
      var patched = [];
      for (var i=0, l=uris.length; i<l; i++) {
        patched[i] = this.patchFilename(uris[i]);
      }

      return jasy.Loader.loadScripts(patched);
    },

    patchFilename : function(fileName)
    {
      var pos = fileName.lastIndexOf(".");
      var checksum = "-" + this.CHECKSUM;

      if (pos == -1)
      {
        return fileName + checksum;
      }
      else
      {
        var fileExt = fileName.substring(pos+1);
        return fileName.substring(0, pos) + checksum + "." + fileExt;
      }
    }
  });
})();

<|endoftext|>


<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * A directive which allows elements to be manually focused / blurred.
 */
angular.module('element').directive('guacFocus', ['$parse', function guacFocus($parse) {

    return {
        restrict: 'A',

        link: function linkGuacFocus($scope, $element, $attrs) {

            /**
             * Whether the element associated with this directive should be
             * focussed.
             *
             * @type Boolean
             */
            var guacFocus = $parse($attrs.guacFocus);

            /**
             * The element which will be focused / blurred.
             *
             * @type Element
             */
            var element = $element[0];

            // Set/unset focus depending on value of guacFocus
            $scope.$watch(guacFocus, function updateFocus(value) {
                $scope.$evalAsync(function updateFocusAsync() {
                    if (value)
                        element.focus();
                    else
                        element.blur();
                });
            });

            // Set focus flag when focus is received
            element.addEventListener('focus', function focusReceived() {
                $scope.$evalAsync(function setGuacFocusAsync() {
                    guacFocus.assign($scope, true);
                });
            });

            // Unset focus flag when focus is lost
            element.addEventListener('blur', function focusLost() {
                $scope.$evalAsync(function unsetGuacFocusAsync() {
                    guacFocus.assign($scope, false);
                });
            });

        } // end guacFocus link function

    };

}]);
<commit_msg>GUACAMOLE-302: Merge narrowing of guacFocus behavior to simply setting/removing focus.<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * A directive which allows elements to be manually focused / blurred.
 */
angular.module('element').directive('guacFocus', ['$parse', function guacFocus($parse) {

    return {
        restrict: 'A',

        link: function linkGuacFocus($scope, $element, $attrs) {

            /**
             * Whether the element associated with this directive should be
             * focussed.
             *
             * @type Boolean
             */
            var guacFocus = $parse($attrs.guacFocus);

            /**
             * The element which will be focused / blurred.
             *
             * @type Element
             */
            var element = $element[0];

            // Set/unset focus depending on value of guacFocus
            $scope.$watch(guacFocus, function updateFocus(value) {
                $scope.$evalAsync(function updateFocusAsync() {
                    if (value)
                        element.focus();
                    else
                        element.blur();
                });
            });

        } // end guacFocus link function

    };

}]);
<|endoftext|>


<commit_before>/*
 *  band.js
 *
 *  David Janes
 *  IOTDB
 *  2016-02-13
 *
 *  Copyright [2013-2016] [David P. Janes]
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

"use strict";

const events = require('events');

const iotdb = require("iotdb");
const _ = iotdb._;

const errors = require("iotdb-errors");
const helpers = require("./helpers");

const make = (_thing, _d, _band_name) => {
    const self = Object.assign({});

    let _timestamp = _.timestamp.epoch();
    let _pending = {};
    let _emitter = new events.EventEmitter();

    const _update = function(uds, paramd) {
        return new Promise(( resolve, reject ) => {
            paramd = _.d.compose.shallow(paramd, {
                add_timestamp: true,
                check_timestamp: true,
                notify: true,
            });

            var utimestamp = paramd.timestamp || _.timestamp.make();

            if (paramd.check_timestamp && !_.timestamp.check.values(_timestamp, utimestamp)) {
                return reject(new errors.Timestamp());
            }

            const changed = {};

            uds.map(function(ud) {
                if (ud.key.match(/^@/)) {
                    return;
                }

                var ovalue = _d[ud.key];

                if (_.is.Equal(ud.value, ovalue)) {
                    return;
                }

                self._put(_d, ud.key, ud.value);
                self._put(changed, ud.key, ud.value);

                if (paramd.notify) {
                    process.nextTick(function() {
                        _emitter.emit(ud.key, _thing, self, ud.value);
                    });
                }
            });

            if (_.is.Empty(changed)) {
                return resolve(changed);
            }

            if (paramd.notify) {
                // thing-level notifications are batched on ticks
                _pending = _.d.compose.shallow(_pending, changed);

                process.nextTick(function() {
                    const updated_keys = _.keys(_pending);
                    _pending = {};

                    _thing.emit(_band_name, _thing, self, updated_keys);
                });
            }

            if (paramd.add_timestamp) {
                _timestamp = utimestamp;
            }

            return resolve(changed);
        });
    };

    self.set = function(key, value) {
        const tkey = self._transform_key(key);
        const tvalue = self._transform_value(tkey, value);

        return _update([ { key: tkey, value: tvalue } ], {
            add_timestamp: true,
        });
    };

    self.update = (updated, paramd) => _update(
        self._unroll(updated),
        _.d.compose.shallow({ timestamp: updated["@timestamp"] }, paramd)
    );

    self.thing = () => _thing;
    self.band_name = () => _band_name;
    self.timestamp = () => _timestamp;

    self.state = () => _.d.clone.deep(_d);
    self.get = (key, otherwise) => self._get(_d, self._transform_key(key), otherwise);
    self.list = (key, otherwise) => self._list(_d, self._transform_key(key), otherwise);
    self.first = (key, otherwise) => self._first(_d, self._transform_key(key), otherwise);

    // dictionary manipulation - only for internal and descendents
    self._get = (d, key, otherwise) => _.d.get(d, key, otherwise);
    self._first = (d, key, otherwise) => _.d.first(d, key, otherwise);
    self._list = (d, key, otherwise) => _.d.list(d, key, otherwise);
    self._put = (d, key, value) => _.d.set(d, key, value);
    self._transform_key = (key) => key;
    self._transform_value = (key, value) => value;
    self._unroll = helpers.unroll_deep;

    // emitter section
    self.emitter = () => _emitter;
    self.on = (key, listener) => _emitter.on(self._transform_key(key), listener);

    return self;
};

/**
 *  API
 */
exports.make = make;
<commit_msg>tighten up code, no functional change<commit_after>/*
 *  band.js
 *
 *  David Janes
 *  IOTDB
 *  2016-02-13
 *
 *  Copyright [2013-2016] [David P. Janes]
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

"use strict";

const events = require('events');

const iotdb = require("iotdb");
const _ = iotdb._;

const errors = require("iotdb-errors");
const helpers = require("./helpers");

const make = (_thing, _d, _band_name) => {
    const self = Object.assign({});

    let _timestamp = _.timestamp.epoch();
    let _pending = {};
    let _emitter = new events.EventEmitter();

    const _update = (uds, paramd) => {
        return new Promise(( resolve, reject ) => {
            paramd = _.d.compose.shallow(paramd, {
                add_timestamp: true,
                check_timestamp: true,
                notify: true,
            });

            var utimestamp = paramd.timestamp || _.timestamp.make();

            if (paramd.check_timestamp && !_.timestamp.check.values(_timestamp, utimestamp)) {
                return reject(new errors.Timestamp());
            }

            const changed = {};

            uds.map(function(ud) {
                if (ud.key.match(/^@/)) {
                    return;
                }

                var ovalue = _d[ud.key];

                if (_.is.Equal(ud.value, ovalue)) {
                    return;
                }

                self._put(_d, ud.key, ud.value);
                self._put(changed, ud.key, ud.value);

                if (paramd.notify) {
                    process.nextTick(function() {
                        _emitter.emit(ud.key, _thing, self, ud.value);
                    });
                }
            });

            if (_.is.Empty(changed)) {
                return resolve(changed);
            }

            if (paramd.notify) {
                // thing-level notifications are batched on ticks
                _pending = _.d.compose.shallow(_pending, changed);

                process.nextTick(function() {
                    const updated_keys = _.keys(_pending);
                    _pending = {};

                    _thing.emit(_band_name, _thing, self, updated_keys);
                });
            }

            if (paramd.add_timestamp) {
                _timestamp = utimestamp;
            }

            return resolve(changed);
        });
    };

    self.set = (key, value) => {
        const tkey = self._transform_key(key);
        const tvalue = self._transform_value(tkey, value);

        return _update([ { key: tkey, value: tvalue } ]);
    };

    self.update = (updated, paramd) => _update(
        self._unroll(updated),
        _.d.compose.shallow({ timestamp: updated["@timestamp"] }, paramd)
    );

    self.thing = () => _thing;
    self.band_name = () => _band_name;
    self.timestamp = () => _timestamp;

    self.state = () => _.d.clone.deep(_d);
    self.get = (key, otherwise) => self._get(_d, self._transform_key(key), otherwise);
    self.list = (key, otherwise) => self._list(_d, self._transform_key(key), otherwise);
    self.first = (key, otherwise) => self._first(_d, self._transform_key(key), otherwise);

    // emitter section
    self.emitter = () => _emitter;
    self.on = (key, listener) => _emitter.on(self._transform_key(key), listener);

    // dictionary manipulation - only for internal and descendents
    self._get = (d, key, otherwise) => _.d.get(d, key, otherwise);
    self._first = (d, key, otherwise) => _.d.first(d, key, otherwise);
    self._list = (d, key, otherwise) => _.d.list(d, key, otherwise);
    self._put = (d, key, value) => _.d.set(d, key, value);
    self._transform_key = (key) => key;
    self._transform_value = (key, value) => value;
    self._unroll = helpers.unroll_deep;

    return self;
};

/**
 *  API
 */
exports.make = make;
<|endoftext|>


<commit_before>define(['jquery'], function($) {

	var TRANSITION  = 'transition';
	var TRANSFORM   = 'transform';
	var TRANSITION_END = 'transitionend';
	var TRANSFORM_CSS  = 'transform';
	var TRANSITION_CSS = 'transition';

	if (typeof document.body.style.webkitTransform !== undefined) {
		TRANSITION = 'webkitTransition';
		TRANSFORM = 'webkitTransform';
		TRANSITION_END = 'webkitTransitionEnd';
		TRANSFORM_CSS = '-webkit-transform';
		TRANSITION_CSS = '-webkit-transition';
	}
	/**
	 * TouchSliderPlugin responsible for adding ability to advance
	 * the slider using swipe left/right or up/down.
	 *
	 * This class isn't meant to be instantiated by itself. Collaborates
	 * With Slider.
	 *
	 * Usage:
	 *      var plugin = new TouchSliderPlugin();
	 *      plugin.init(slider);
	 */
	var TouchSliderPlugin = function(config) {
		config = config || {};
		this.handleTouchEvents = $.proxy(this.handleTouchEvents, this);
	};

	// Initializes the plugin.
	TouchSliderPlugin.prototype.init = function(slider) {
		this.slider = slider;
		if(this.hasTouchSupport()) {
			this.hideNav();
			this.attachTouchEvents();
		}
	};

	// Returns true if the device has touch support, false otherwise.
	TouchSliderPlugin.prototype.hasTouchSupport = function() {
		return 'ontouchstart' in document.documentElement;
	};

	// Hides the navigation element.
	TouchSliderPlugin.prototype.hideNav = function() {
		$(".sliderNav", this.slider.el).hide();
	};

	// Attaches touch event handles to the slider.
	TouchSliderPlugin.prototype.attachTouchEvents = function() {
		$(this.slider.el).on("touchmove touchstart touchend", this.handleTouchEvents);
	};

	// Handler for touch events.
	TouchSliderPlugin.prototype.handleTouchEvents = function(evt) {
		var e = evt.originalEvent;
		var direction = 0;
		var scrollTop = false;
        var $parentEl = $(this.slider.el).parent();
		var index = $parentEl.index();

		//console.log("touch event", evt.type, "index", index, "event object", evt);

		switch(e.type) {
			case 'touchstart':
				this.startPos = e.touches[0].clientX;
				this.lastPos = this.startPos;
				direction = 0;
				this.startPosY = e.touches[0].clientY;
				this.lastPosY = this.startPosY;
				break;
			case 'touchmove':
				e.preventDefault();
				direction = (this.lastPos > this.startPos) ? -1 : 1;
				this.lastPosY = e.touches[0].clientY;
				this.lastPos = e.touches[0].clientX;
				//console.log('last down: ' + lastPosY);
				break;
			case 'touchend':
				if(this.lastPosY - this.startPosY > 50) {
					index--;
					if(index <= 0) {
						//first slider scroll to top of page
						scrollTop = $('html').position().top;
					} else {
						//page up
						if($parentEl.prev().position()) {
							scrollTop = $parentEl.prev().position().top;
						}
					}
					this.scrollTop(scrollTop);
				} else if (this.lastPosY - this.startPosY < -50) {
					index++;
					if (index >= this.slider.getNumItems()) {
						//first slider scroll to top of page
						scrollTop = $('html').position().bottom;
					} else {
						//page down
						if($parentEl.next().position()) {
							scrollTop = $parentEl.next().position().top;
						}
					}
					this.scrollTop(scrollTop);
				} else {
					if(this.lastPos - this.startPos > 100) {
						this.slider.goToPrev();
					} else if(this.lastPos - this.startPos < -100) {
						this.slider.goToNext();
					}
				}
				break;
			default:
				//console.log("touch event not handled", e.type);
				break;
		}
		return false;
	};

	// Helper function to scroll the screen.
	TouchSliderPlugin.prototype.scrollTop = function(scrollTop) {
		var duration = 500;
		if(typeof scrollTop === 'number') {
			$('html body').animate({scrollTop: scrollTop}, duration);
		}
	};

	// Not used.
	TouchSliderPlugin.prototype.cleanTransitions = function(node) {
		node.style[TRANSITION] = 'none';
	};

	// Not used.
	TouchSliderPlugin.prototype.setPosition = function(node, left) {
		node.style[TRANSFORM] =  "translate3d("+left+"px, 0, 0)";
	};

	return TouchSliderPlugin;
});
<commit_msg>fix issue with touch events on slider and review/quiz buttons not working<commit_after>define(['jquery'], function($) {

	var TRANSITION  = 'transition';
	var TRANSFORM   = 'transform';
	var TRANSITION_END = 'transitionend';
	var TRANSFORM_CSS  = 'transform';
	var TRANSITION_CSS = 'transition';

	if (typeof document.body.style.webkitTransform !== undefined) {
		TRANSITION = 'webkitTransition';
		TRANSFORM = 'webkitTransform';
		TRANSITION_END = 'webkitTransitionEnd';
		TRANSFORM_CSS = '-webkit-transform';
		TRANSITION_CSS = '-webkit-transition';
	}
	/**
	 * TouchSliderPlugin responsible for adding ability to advance
	 * the slider using swipe left/right or up/down.
	 *
	 * This class isn't meant to be instantiated by itself. Collaborates
	 * With Slider.
	 *
	 * Usage:
	 *      var plugin = new TouchSliderPlugin();
	 *      plugin.init(slider);
	 */
	var TouchSliderPlugin = function(config) {
		config = config || {};
		this.handleTouchEvents = $.proxy(this.handleTouchEvents, this);
	};

	// Initializes the plugin.
	TouchSliderPlugin.prototype.init = function(slider) {
		this.slider = slider;
		if(this.hasTouchSupport()) {
			this.hideNav();
			this.attachTouchEvents();
		}
	};

	// Returns true if the device has touch support, false otherwise.
	TouchSliderPlugin.prototype.hasTouchSupport = function() {
		return 'ontouchstart' in document.documentElement;
	};

	// Hides the navigation element.
	TouchSliderPlugin.prototype.hideNav = function() {
		$(".sliderNav", this.slider.el).hide();
	};

	// Attaches touch event handles to the slider.
	TouchSliderPlugin.prototype.attachTouchEvents = function() {
		$(this.slider.el).on("touchmove touchstart touchend", this.handleTouchEvents);
	};

	// Handler for touch events.
	TouchSliderPlugin.prototype.handleTouchEvents = function(evt) {
		var e = evt.originalEvent;
		var direction = 0;
		var scrollTop = false;
        var $parentEl = $(this.slider.el).parent();
		var index = $parentEl.index();

		//console.log("touch event", evt.type, "index", index, "event object", evt);

		switch(e.type) {
			case 'touchstart':
				this.startPos = e.touches[0].clientX;
				this.lastPos = this.startPos;
				direction = 0;
				this.startPosY = e.touches[0].clientY;
				this.lastPosY = this.startPosY;
				break;
			case 'touchmove':
				e.preventDefault();
				direction = (this.lastPos > this.startPos) ? -1 : 1;
				this.lastPosY = e.touches[0].clientY;
				this.lastPos = e.touches[0].clientX;
				//console.log('last down: ' + lastPosY);
				break;
			case 'touchend':
				if(this.lastPosY - this.startPosY > 50) {
					index--;
					if(index <= 0) {
						//first slider scroll to top of page
						scrollTop = $('html').position().top;
					} else {
						//page up
						if($parentEl.prev().position()) {
							scrollTop = $parentEl.prev().position().top;
						}
					}
					this.scrollTop(scrollTop);
				} else if (this.lastPosY - this.startPosY < -50) {
					index++;
					if (index >= this.slider.getNumItems()) {
						//first slider scroll to top of page
						scrollTop = $('html').position().bottom;
					} else {
						//page down
						if($parentEl.next().position()) {
							scrollTop = $parentEl.next().position().top;
						}
					}
					this.scrollTop(scrollTop);
				} else {
					if(this.lastPos - this.startPos > 100) {
						this.slider.goToPrev();
					} else if(this.lastPos - this.startPos < -100) {
						this.slider.goToNext();
					}
				}
				break;
			default:
				//console.log("touch event not handled", e.type);
				break;
		}
	};

	// Helper function to scroll the screen.
	TouchSliderPlugin.prototype.scrollTop = function(scrollTop) {
		var duration = 500;
		if(typeof scrollTop === 'number') {
			$('html body').animate({scrollTop: scrollTop}, duration);
		}
	};

	// Not used.
	TouchSliderPlugin.prototype.cleanTransitions = function(node) {
		node.style[TRANSITION] = 'none';
	};

	// Not used.
	TouchSliderPlugin.prototype.setPosition = function(node, left) {
		node.style[TRANSFORM] =  "translate3d("+left+"px, 0, 0)";
	};

	return TouchSliderPlugin;
});
<|endoftext|>


<commit_before>/**
 * Password verification handler.
 *
 * This component provides an HTTP handler that verifies a username and
 * password.  The credentials are submitted via an HTML form.
 *
 * This handler is protected against CSRF, in order to defend against [login
 * CSRF][1] attacks.  Consult [Robust Defenses for Cross-Site Request
 * Forgery][2] for a thorough analysis of CSRF, including login CSRF, as well
 * defense mechanisms.
 *
 * [1]: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#login-csrf
 * [2]: https://seclab.stanford.edu/websec/csrf/csrf.pdf
 *
 * @returns {Function[]}
 */
exports = module.exports = function(parse, csrfProtection, authenticate, state) {

  function establishSession(req, res, next) {
    req.login(req.user, req.authInfo, function(err) {
      if (err) { return next(err); }
      // TODO: Consider yeilding state here, for instance an index of the
      // session that was established, for multi login
      return res.resumeState(next);
    });
  }

  function go(req, res, next) {
    // TODO: Add an optional service that will be injected here which determines
    // the default application, and how to redirect to it (OpenID IdP init, etc)

    res.redirect('/');
  }

  // TODO: Investigate using a cookie-less CSRF protection mechanism, such
  //       as checking referrer headers, per
  //       https://seclab.stanford.edu/websec/csrf/csrf.pdf

  return [
    parse('application/x-www-form-urlencoded'),
    csrfProtection(),
    state(),
    authenticate('www-form/password'),
    establishSession,
    go
  ];
};

exports['@require'] = [
  'http://i.bixbyjs.org/http/middleware/parse',
  'http://i.bixbyjs.org/http/middleware/csrfProtection',
  'http://i.bixbyjs.org/http/middleware/authenticate',
  'http://i.bixbyjs.org/http/middleware/state'
];
<commit_msg>Add TODO items.<commit_after>/**
 * Password verification handler.
 *
 * This component provides an HTTP handler that verifies a username and
 * password.  The credentials are submitted via an HTML form.
 *
 * This handler is protected against CSRF, in order to defend against [login
 * CSRF][1] attacks.  Consult [Robust Defenses for Cross-Site Request
 * Forgery][2] for a thorough analysis of CSRF, including login CSRF, as well
 * defense mechanisms.
 *
 * [1]: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#login-csrf
 * [2]: https://seclab.stanford.edu/websec/csrf/csrf.pdf
 *
 * @returns {Function[]}
 */
exports = module.exports = function(parse, csrfProtection, authenticate, state) {

  function establishSession(req, res, next) {
    req.login(req.user, req.authInfo, function(err) {
      if (err) { return next(err); }
      // TODO: Consider yeilding state here, for instance an index of the
      // session that was established, for multi login
      // NOTE: Yes, do that.
      return res.resumeState(next);
    });
  }

  // TODO: Move resume state down here to its own middleware, so we can insert change password, etc
  //.   will need the sessionselector of the just logged in user for this.

  function go(req, res, next) {
    // TODO: Add an optional service that will be injected here which determines
    // the default application, and how to redirect to it (OpenID IdP init, etc)

    res.redirect('/');
  }

  // TODO: Investigate using a cookie-less CSRF protection mechanism, such
  //       as checking referrer headers, per
  //       https://seclab.stanford.edu/websec/csrf/csrf.pdf

  return [
    parse('application/x-www-form-urlencoded'),
    csrfProtection(),
    state(),
    authenticate('www-form/password'),
    establishSession,
    go
  ];
};

exports['@require'] = [
  'http://i.bixbyjs.org/http/middleware/parse',
  'http://i.bixbyjs.org/http/middleware/csrfProtection',
  'http://i.bixbyjs.org/http/middleware/authenticate',
  'http://i.bixbyjs.org/http/middleware/state'
];
<|endoftext|>


<commit_before>
  // begin npm / ender footer
  return window.jQuery;
}
module.exports = create('undefined' === typeof window ? undefined : window);
module.exports.create = create;
}());

<commit_msg>added noConflict as to not conflict with Ender<commit_after>
  // begin npm / ender footer
  window.jQuery.noConflict();
  return window.jQuery;
}
module.exports = create('undefined' === typeof window ? undefined : window);
module.exports.create = create;
}());

<|endoftext|>