<commit_before>/**
 * Created by ksinger on 05.11.2015.
 */

/* TODO this fragment is part of an attempt to sketch a different
 * approach to asynchronity (Remove it or the thunk-based
 * solution afterwards)
 */

/*
* This redux wrapper for the old message-service consists of:
*
* * an "agent" that registers with the service, receives messages
* from it and triggers redux actions.
* * an "component" that listens to state changes and triggers
* messages to the server via the service.
 */

import won from './won-es6';
import {attach,
        delay,
        watchImmutableRdxState,
        checkHttpStatus } from './utils';
import { actionTypes, actionCreators } from './actions/actions';
//import './message-service'; //TODO still uses es5
import { getEventsFromMessage,setCommStateFromResponseForLocalNeedMessage } from './won-message-utils';
import SockJS from 'sockjs';
import * as messages from './actions/messages-actions';

export function runMessagingAgent(redux) {

    console.log('Starting messaging agent.');

    /* TODOs
     * + heartbeat? -> NOPE
     * + make it generic?
     *      + make the url a parameter?
     *      + extract the watch? / make the path a parameter?
     *      + registering a processor for the incoming messages (that
     *        can trigger actions but lets the messaging agent stay generic)
     *           + pass a callback
     *           + make this a signal/observable
     * + framing -> NOPE
     * + reconnecting
     * + lazy socket initialisation
     */

    let ws = newSock();
    window.ws4dbg = ws;//TODO deletme
    let unsubscribeWatch = null;

    function newSock() {
        const ws = new SockJS('/owner/msg', null, {debug: true});
        ws.onopen = onOpen;
        ws.onmessage = onMessage;
        ws.onerror = onError;
        ws.onclose = onClose;
        return ws;
    };
    function onOpen() {
        /* Set up message-queue watch */
        unsubscribeWatch = watchImmutableRdxState(
            redux, ['messages', 'enqueued'],
            (newMsgBuffer, oldMsgBuffer) => {
                if(newMsgBuffer) {
                    const firstEntry = newMsgBuffer.entries().next().value;
                    if(firstEntry) { //undefined if queue is empty
                        const [eventUri, msg] = firstEntry;
                        ws.send(JSON.stringify(msg));
                        console.log("sent message: "+JSON.stringify(msg));
                        redux.dispatch(actionCreators.messages__waitingForAnswer({ eventUri, msg }));
                    }
                }
            }
        );
        /**
         * TODO this watch is part of the session-upgrade hack documented in:
         * https://github.com/researchstudio-sat/webofneeds/issues/381#issuecomment-172569377
         */
        unsubscribeWatch = watchImmutableRdxState(
            redux, ['messages', 'resetWsRequested_Hack'],
            (newRequestState, oldRequestState) => {
                if(newRequestState) {
                    ws.close();
                    // a new ws-connection should be opened automatically in onClose
                    redux.dispatch(actionCreators.messages__requestWsReset_Hack(false));
                }
            }
        );

    };

    /**
     * The messageProcessingArray encapsulates all currently implemented message handlers with their respective redux dispatch
     * events, to process another message you add another anonymous function that checks on the necessary message properties/values
     * and dispatches the needed redux action/event
     */
    const messageProcessingArray = [
        function (events) {
            const msgFromExternal = events['msg:FromExternal'];

            /* Other clients or matcher initiated stuff: */
            if(msgFromExternal && msgFromExternal.hasMessageType === won.WONMSG.hintMessageCompacted) {
                redux.dispatch(actionCreators.messages__hintMessageReceived(msgFromExternal));
                return true;
            } else {
                return false;
            }
        },
        function (events) {
            const msgFromExternal = events['msg:FromExternal'];
            const msgFromOwner = events['msg:FromOwner'];

            /* Other clients or matcher initiated stuff: */
            if(msgFromExternal && msgFromOwner && msgFromOwner.hasMessageType === won.WONMSG.connectMessageCompacted) {
                redux.dispatch(actionCreators.messages__connectMessageReceived(events));
                return true;
            } else {
                return false;
            }
        },
        function (events) {
            const msgFromOwner = events['msg:FromOwner'];

            /* Other clients or matcher initiated stuff: */
            if(msgFromOwner && msgFromOwner.hasMessageType === won.WONMSG.openMessageCompacted) {
                //someone accepted our contact request
                redux.dispatch(actionCreators.messages__openMessageReceived(events));
                return true;
            } else {
                return false;
            }
        },
        function (events) {
            const msgFromExternal = events['msg:FromExternal'];
            const msgFromOwner = events['msg:FromOwner'];

            /* Other clients or matcher initiated stuff: */
            if(msgFromExternal && msgFromOwner && msgFromOwner.hasMessageType === won.WONMSG.connectionMessageCompacted) {
                redux.dispatch(actionCreators.messages__connectionMessageReceived({ events }));
                return true;
            } else {
                return false;
            }
        },
        function (events) {
            const msgFromExternal = events['msg:FromExternal'];
            const msgFromOwner = events['msg:FromOwner'];

            if(msgFromExternal && msgFromOwner && msgFromOwner.hasMessageType === won.WONMSG.closeMessageCompacted) {
                redux.dispatch(actionCreators.messages__close__success(msgFromOwner));
                return true;
            } else {
                return false;
            }
        },
        function (events) {
            const msgFromSystem = events['msg:FromSystem'];

            if(msgFromSystem && msgFromSystem.isResponseToMessageType === won.WONMSG.createMessageCompacted){
                if (msgFromSystem.hasMessageType === won.WONMSG.successResponseCompacted)
                    redux.dispatch(actionCreators.messages__create__success(msgFromSystem));
                //else if(event.hasMessageType === won.WONMSG.failureResponseCompacted)
                //    redux.dispatch(actionCreators.messages__create__failure(event));
                return true;
            } else {
                return false;
            }
        },
        function (events) {
            const msgFromSystem = events['msg:FromSystem'];

            if(msgFromSystem && msgFromSystem.isResponseToMessageType === won.WONMSG.connectMessageCompacted){
                if (msgFromSystem.hasMessageType === won.WONMSG.successResponseCompacted)
                    redux.dispatch(actionCreators.messages__connect__success(msgFromSystem));
                //else if(event.hasMessageType === won.WONMSG.failureResponseCompacted)
                //  redux.dispatch(actionCreators.messages__open__failure(event));
                return true;
            }else{
                return false;
            }

        },
        function (events) {
            const msgFromSystem = events['msg:FromSystem'];

            if(msgFromSystem && msgFromSystem.isResponseToMessageType === won.WONMSG.connectionMessageCompacted){
                var eventUri = msgFromSystem.isRemoteResponseTo || msgFromSystem.isResponseTo;
                var connectionUri = msgFromSystem.hasReceiver;
                if (msgFromSystem.hasMessageType === won.WONMSG.successResponseCompacted) {
                    if (msgFromSystem.isRemoteResponseTo) {
                        // got the second success-response (from the remote-node) - 2nd ACK
                        redux.dispatch(actionCreators.messages__chatMessage__successRemote({ events }));
                    } else {
                        // got the first success-response (from our own node) - 1st ACK
                        redux.dispatch(actionCreators.messages__chatMessage__successOwn({ events }));
                    }
                } else if(msgFromSystem.hasMessageType === won.WONMSG.failureResponseCompacted) {
                    redux.dispatch(actionCreators.messages__chatMessage__failure({ events }));
                }
                return true;
            }else{
                return false;
            }

        },
        function (events) {
            const msgFromSystem = events['msg:FromSystem'];

            if(msgFromSystem && msgFromSystem.isResponseToMessageType === won.WONMSG.openMessageCompacted){
                if (msgFromSystem.hasMessageType === won.WONMSG.successResponseCompacted)
                    redux.dispatch(actionCreators.messages__open__success(msgFromSystem));
                //else if(event.hasMessageType === won.WONMSG.failureResponseCompacted)
                //  redux.dispatch(actionCreators.messages__open__failure(event));
                return true;
            }else{
                return false;
            }

        },
        function (events) {
            const msgFromSystem = events['msg:FromSystem'];

            if(msgFromSystem && msgFromSystem.isResponseToMessageType === won.WONMSG.closeMessageCompacted){
                if (msgFromSystem.hasMessageType === won.WONMSG.successResponseCompacted) //JUMP HERE AND ONLY HERE WHEN CLOSE MESSAGES COME IN!
                    redux.dispatch(actionCreators.messages__close__success(msgFromSystem));
                //else if(event.hasMessageType === won.WONMSG.failureResponseCompacted)
                //  redux.dispatch(actionCreators.messages__close__failure(event));
                return true;
            }else{
                return false;
            }

        },
        function (events) {
            const msgFromSystem = events['msg:FromSystem'];

            if(msgFromSystem && msgFromSystem.isResponseToMessageType === won.WONMSG.closeNeedMessageCompacted){
                if (msgFromSystem.hasMessageType === won.WONMSG.successResponseCompacted)
                    redux.dispatch(actionCreators.messages__closeNeed__success(msgFromSystem));
                else if (msgFromSystem.hasMessageType === won.WONMSG.failureResponseCompacted)
                    redux.dispatch(actionCreators.messages__closeNeed__failure(msgFromSystem));
                return true;
            }else{
                return false;
            }
        }
    ];

    function onMessage(receivedMsg) {
        const data = JSON.parse(receivedMsg.data);

        console.log('onMessage: ', data);
        getEventsFromMessage(data).then(events => {
            console.log('onMessage - events: ', events);

            var messageProcessed = false;

            for(var i = 0; i < messageProcessingArray.length; i++) {
                messageProcessed = messageProcessed || messageProcessingArray[i](events);
            }

            if(!messageProcessed){
                console.warn("MESSAGE WASN'T PROCESSED DUE TO MISSING HANDLER FOR ITS TYPE: ", events);
            }
        })
    };

    function onError(e) {
        console.error('websocket error: ', e);
        this.close();
    };

    let reconnectAttempts = 0;
    function onClose(e) {
        if(e.wasClean){
            console.log('websocket closed. reconnectAttempts = ',reconnectAttempts);
        } else {
            console.error('websocket closed. reconnectAttempts = ',reconnectAttempts);
        }
        if(unsubscribeWatch && typeof unsubscribeWatch === 'function')
            unsubscribeWatch();

        if (e.code === 1011 || reconnectAttempts > 5) {
            console.log('either your session timed out or you encountered an unexpected server condition. \n', e.reason);

            fetch('rest/users/isSignedIn', {credentials: 'include'})
                .then(checkHttpStatus) // will reject if not logged in
                .then(() => {//logged in -- re-initiate route-change
                    ws = newSock();
                    reconnectAttempts = 0;

                }).catch(error => {
                    console.log("you lost the session we will call logout for you");
                    redux.dispatch(actionCreators.logout())
                });
        } else if (reconnectAttempts > 1) {
            setTimeout(() => {
                ws = newSock();
                reconnectAttempts++;
            }, 2000);
        } else {
            // posting anonymously creates a new session for each post
            // thus we need to reconnect here
            // TODO reconnect only on next message instead of straight away <-- bad idea, prevents push notifications
            // TODO add a delay if first reconnect fails
            ws = newSock();
            reconnectAttempts++;
        }
    };
}

<commit_msg>check heartbeat in messaging-agent<commit_after>/**
 * Created by ksinger on 05.11.2015.
 */

/* TODO this fragment is part of an attempt to sketch a different
 * approach to asynchronity (Remove it or the thunk-based
 * solution afterwards)
 */

/*
* This redux wrapper for the old message-service consists of:
*
* * an "agent" that registers with the service, receives messages
* from it and triggers redux actions.
* * an "component" that listens to state changes and triggers
* messages to the server via the service.
 */

import won from './won-es6';
import {attach,
        delay,
        watchImmutableRdxState,
        checkHttpStatus } from './utils';
import { actionTypes, actionCreators } from './actions/actions';
//import './message-service'; //TODO still uses es5
import { getEventsFromMessage,setCommStateFromResponseForLocalNeedMessage } from './won-message-utils';
import SockJS from 'sockjs';
import * as messages from './actions/messages-actions';

export function runMessagingAgent(redux) {

    console.log('Starting messaging agent.');

    /* TODOs
     * + make it generic?
     *      + make the url a parameter?
     *      + extract the watch? / make the path a parameter?
     *      + registering a processor for the incoming messages (that
     *        can trigger actions but lets the messaging agent stay generic)
     *           + pass a callback
     *           + make this a signal/observable
     * + framing -> NOPE
     * + reconnecting
     * + lazy socket initialisation
     */

    let ws = newSock();
    window.ws4dbg = ws;//TODO deletme
    let unsubscribeWatch = null;
    let missedHeartbeats = 0;

    setInterval(checkHeartbeat, 30000);

    function newSock() {
        const ws = new SockJS('/owner/msg', null, {debug: true});
        ws.onopen = onOpen;
        ws.onmessage = onMessage;
        ws.onerror = onError;
        ws.onclose = onClose;
        ws.onheartbeat = onHeartbeat;
        missedHeartbeats = 0;

        return ws;
    };

    function onHeartbeat(e) {
        console.log('heartbeat',e);
        missedHeartbeats = 0;
    }

    function checkHeartbeat() {
        console.log("checking heartbeat presence: ", missedHeartbeats);

        if(++missedHeartbeats > 3) {
            console.log("no heartbeat present invoking logout");
            redux.dispatch(actionCreators.logout());
        }else{
            console.log("heartbeat present");
        }
    }

    function onOpen() {
        /* Set up message-queue watch */
        unsubscribeWatch = watchImmutableRdxState(
            redux, ['messages', 'enqueued'],
            (newMsgBuffer, oldMsgBuffer) => {
                if(newMsgBuffer) {
                    const firstEntry = newMsgBuffer.entries().next().value;
                    if(firstEntry) { //undefined if queue is empty
                        const [eventUri, msg] = firstEntry;
                        ws.send(JSON.stringify(msg));
                        console.log("sent message: "+JSON.stringify(msg));
                        redux.dispatch(actionCreators.messages__waitingForAnswer({ eventUri, msg }));
                    }
                }
            }
        );
        /**
         * TODO this watch is part of the session-upgrade hack documented in:
         * https://github.com/researchstudio-sat/webofneeds/issues/381#issuecomment-172569377
         */
        unsubscribeWatch = watchImmutableRdxState(
            redux, ['messages', 'resetWsRequested_Hack'],
            (newRequestState, oldRequestState) => {
                if(newRequestState) {
                    ws.close();
                    // a new ws-connection should be opened automatically in onClose
                    redux.dispatch(actionCreators.messages__requestWsReset_Hack(false));
                }
            }
        );

    };

    /**
     * The messageProcessingArray encapsulates all currently implemented message handlers with their respective redux dispatch
     * events, to process another message you add another anonymous function that checks on the necessary message properties/values
     * and dispatches the needed redux action/event
     */
    const messageProcessingArray = [
        function (events) {
            const msgFromExternal = events['msg:FromExternal'];

            /* Other clients or matcher initiated stuff: */
            if(msgFromExternal && msgFromExternal.hasMessageType === won.WONMSG.hintMessageCompacted) {
                redux.dispatch(actionCreators.messages__hintMessageReceived(msgFromExternal));
                return true;
            } else {
                return false;
            }
        },
        function (events) {
            const msgFromExternal = events['msg:FromExternal'];
            const msgFromOwner = events['msg:FromOwner'];

            /* Other clients or matcher initiated stuff: */
            if(msgFromExternal && msgFromOwner && msgFromOwner.hasMessageType === won.WONMSG.connectMessageCompacted) {
                redux.dispatch(actionCreators.messages__connectMessageReceived(events));
                return true;
            } else {
                return false;
            }
        },
        function (events) {
            const msgFromOwner = events['msg:FromOwner'];

            /* Other clients or matcher initiated stuff: */
            if(msgFromOwner && msgFromOwner.hasMessageType === won.WONMSG.openMessageCompacted) {
                //someone accepted our contact request
                redux.dispatch(actionCreators.messages__openMessageReceived(events));
                return true;
            } else {
                return false;
            }
        },
        function (events) {
            const msgFromExternal = events['msg:FromExternal'];
            const msgFromOwner = events['msg:FromOwner'];

            /* Other clients or matcher initiated stuff: */
            if(msgFromExternal && msgFromOwner && msgFromOwner.hasMessageType === won.WONMSG.connectionMessageCompacted) {
                redux.dispatch(actionCreators.messages__connectionMessageReceived({ events }));
                return true;
            } else {
                return false;
            }
        },
        function (events) {
            const msgFromExternal = events['msg:FromExternal'];
            const msgFromOwner = events['msg:FromOwner'];

            if(msgFromExternal && msgFromOwner && msgFromOwner.hasMessageType === won.WONMSG.closeMessageCompacted) {
                redux.dispatch(actionCreators.messages__close__success(msgFromOwner));
                return true;
            } else {
                return false;
            }
        },
        function (events) {
            const msgFromSystem = events['msg:FromSystem'];

            if(msgFromSystem && msgFromSystem.isResponseToMessageType === won.WONMSG.createMessageCompacted){
                if (msgFromSystem.hasMessageType === won.WONMSG.successResponseCompacted)
                    redux.dispatch(actionCreators.messages__create__success(msgFromSystem));
                //else if(event.hasMessageType === won.WONMSG.failureResponseCompacted)
                //    redux.dispatch(actionCreators.messages__create__failure(event));
                return true;
            } else {
                return false;
            }
        },
        function (events) {
            const msgFromSystem = events['msg:FromSystem'];

            if(msgFromSystem && msgFromSystem.isResponseToMessageType === won.WONMSG.connectMessageCompacted){
                if (msgFromSystem.hasMessageType === won.WONMSG.successResponseCompacted)
                    redux.dispatch(actionCreators.messages__connect__success(msgFromSystem));
                //else if(event.hasMessageType === won.WONMSG.failureResponseCompacted)
                //  redux.dispatch(actionCreators.messages__open__failure(event));
                return true;
            }else{
                return false;
            }

        },
        function (events) {
            const msgFromSystem = events['msg:FromSystem'];

            if(msgFromSystem && msgFromSystem.isResponseToMessageType === won.WONMSG.connectionMessageCompacted){
                var eventUri = msgFromSystem.isRemoteResponseTo || msgFromSystem.isResponseTo;
                var connectionUri = msgFromSystem.hasReceiver;
                if (msgFromSystem.hasMessageType === won.WONMSG.successResponseCompacted) {
                    if (msgFromSystem.isRemoteResponseTo) {
                        // got the second success-response (from the remote-node) - 2nd ACK
                        redux.dispatch(actionCreators.messages__chatMessage__successRemote({ events }));
                    } else {
                        // got the first success-response (from our own node) - 1st ACK
                        redux.dispatch(actionCreators.messages__chatMessage__successOwn({ events }));
                    }
                } else if(msgFromSystem.hasMessageType === won.WONMSG.failureResponseCompacted) {
                    redux.dispatch(actionCreators.messages__chatMessage__failure({ events }));
                }
                return true;
            }else{
                return false;
            }

        },
        function (events) {
            const msgFromSystem = events['msg:FromSystem'];

            if(msgFromSystem && msgFromSystem.isResponseToMessageType === won.WONMSG.openMessageCompacted){
                if (msgFromSystem.hasMessageType === won.WONMSG.successResponseCompacted)
                    redux.dispatch(actionCreators.messages__open__success(msgFromSystem));
                //else if(event.hasMessageType === won.WONMSG.failureResponseCompacted)
                //  redux.dispatch(actionCreators.messages__open__failure(event));
                return true;
            }else{
                return false;
            }

        },
        function (events) {
            const msgFromSystem = events['msg:FromSystem'];

            if(msgFromSystem && msgFromSystem.isResponseToMessageType === won.WONMSG.closeMessageCompacted){
                if (msgFromSystem.hasMessageType === won.WONMSG.successResponseCompacted) //JUMP HERE AND ONLY HERE WHEN CLOSE MESSAGES COME IN!
                    redux.dispatch(actionCreators.messages__close__success(msgFromSystem));
                //else if(event.hasMessageType === won.WONMSG.failureResponseCompacted)
                //  redux.dispatch(actionCreators.messages__close__failure(event));
                return true;
            }else{
                return false;
            }

        },
        function (events) {
            const msgFromSystem = events['msg:FromSystem'];

            if(msgFromSystem && msgFromSystem.isResponseToMessageType === won.WONMSG.closeNeedMessageCompacted){
                if (msgFromSystem.hasMessageType === won.WONMSG.successResponseCompacted)
                    redux.dispatch(actionCreators.messages__closeNeed__success(msgFromSystem));
                else if (msgFromSystem.hasMessageType === won.WONMSG.failureResponseCompacted)
                    redux.dispatch(actionCreators.messages__closeNeed__failure(msgFromSystem));
                return true;
            }else{
                return false;
            }
        }
    ];

    function onMessage(receivedMsg) {
        const data = JSON.parse(receivedMsg.data);

        console.log('onMessage: ', data);
        getEventsFromMessage(data).then(events => {
            console.log('onMessage - events: ', events);

            var messageProcessed = false;

            for(var i = 0; i < messageProcessingArray.length; i++) {
                messageProcessed = messageProcessed || messageProcessingArray[i](events);
            }

            if(!messageProcessed){
                console.warn("MESSAGE WASN'T PROCESSED DUE TO MISSING HANDLER FOR ITS TYPE: ", events);
            }
        })
    };

    function onError(e) {
        console.error('websocket error: ', e);
        this.close();
    };

    let reconnectAttempts = 0;
    function onClose(e) {
        if(e.wasClean){
            console.log('websocket closed. reconnectAttempts = ',reconnectAttempts);
        } else {
            console.error('websocket closed. reconnectAttempts = ',reconnectAttempts);
        }
        if(unsubscribeWatch && typeof unsubscribeWatch === 'function')
            unsubscribeWatch();

        if (e.code === 1011 || reconnectAttempts > 5) {
            console.log('either your session timed out or you encountered an unexpected server condition. \n', e.reason);

            fetch('rest/users/isSignedIn', {credentials: 'include'})
                .then(checkHttpStatus) // will reject if not logged in
                .then(() => {//logged in -- re-initiate route-change
                    ws = newSock();
                    reconnectAttempts = 0;

                }).catch(error => {
                    console.log("you lost the session we will call logout for you");
                    redux.dispatch(actionCreators.logout())
                });
        } else if (reconnectAttempts > 1) {
            setTimeout(() => {
                ws = newSock();
                reconnectAttempts++;
            }, 2000);
        } else {
            // posting anonymously creates a new session for each post
            // thus we need to reconnect here
            // TODO reconnect only on next message instead of straight away <-- bad idea, prevents push notifications
            // TODO add a delay if first reconnect fails
            ws = newSock();
            reconnectAttempts++;
        }
    };
}

<|endoftext|>


<commit_before>import { default as React, Component } from 'react';
import { storageService } from '../service/StorageService';
import { render } from 'react-dom';
import { Modal } from 'react-bootstrap';
import { authOperation, authEmitter } from '../service/authOperation';

export class SubscribeModal extends Component {
	constructor(props) {
		super(props);
		this.state = {
			showModal: false,
			profile: false,
			subscribeOption: 'major'
		};
		this.options = {
			option1: {
				value: 'major',
				text: 'New GEM releases'
			},
			option2: {
				value: 'all',
				text: 'Limited major updates'
			}
		}
		this.timer = 1;
		this.activetab = true;
		this.holdSubscribe = false;
		this.init();
	}
	init() {
		authEmitter.addListener('profile', (data) => {
			this.setState({
				profile: data
			});
		});
		if (storageService.get('popuptimer')) {
			this.timer = parseInt(storageService.get('popuptimer'));
		}
		setTimeout(() => {
			if (!this.state.profile) {
				this.open();
			}
		}, 1000 * 60 * this.timer);

		$(window).focus(function() {
			this.activetab = true;
			setTimeout(() => {
				if (!this.state.profile && this.holdSubscribe) {
					this.open();
				}
			}, 1000 * 10 * this.timer);
		}.bind(this));

		$(window).blur(function() {
			this.activetab = false;
		}.bind(this));
	}
	close() {
		this.internalClose = true;
		storageService.set('popuptimer', this.timer + 5);
		this.setState({ showModal: false });
	}
	open() {
		if (!this.state.profile) {
			if (!$('.fade.in.modal').length) {
				if(this.activetab) {
					this.setState({ showModal: true });
				} else {
					this.holdSubscribe = true;
				}
			} else {
				setTimeout(() => {
					this.open();
				}, 1000 * 10);
			}
		}
	}
	subscribe() {
		authOperation.login(this.state.subscribeOption);
	}
	subscribeOptionChange(value) {
		this.setState({
			subscribeOption: value
		});
	}
	showIcon() {
		let icon = ();
		if (this.state.profile) {
			icon = ();
		}
		return icon;
	}
	render() {
		return (

			 this.open()}>
				{this.showIcon()}

			<Modal className="modal-info" show={this.state.showModal} onHide={() => this.close()}>
			<Modal.Header closeButton>
				<Modal.Title>Be in the know about major updates!</Modal.Title>
			</Modal.Header>
			<Modal.Body>

								<input type="radio"
									checked={this.state.subscribeOption === this.options.option1.value}
									onChange={() => this.subscribeOptionChange(this.options.option1.value)}
									name="subscribeOption" id="subscribeOption" value={this.options.option1.value} /> {this.options.option1.text}

								<input type="radio"
									checked={this.state.subscribeOption === this.options.option2.value}
									onChange={() => this.subscribeOptionChange(this.options.option2.value)}
									name="subscribeOption1" id="subscribeOption1" value={this.options.option2.value} /> {this.options.option2.text}

					<button className="btn btn-primary" onClick={() => this.subscribe()}>
						 Subscribe with Github
					</button>

				</Modal.Body>
			</Modal>

		)
	}
}
<commit_msg>Fix subscribe modal auto popup issue<commit_after>import { default as React, Component } from 'react';
import { storageService } from '../service/StorageService';
import { render } from 'react-dom';
import { Modal } from 'react-bootstrap';
import { authOperation, authEmitter } from '../service/authOperation';

export class SubscribeModal extends Component {
	constructor(props) {
		super(props);
		this.state = {
			showModal: false,
			profile: false,
			subscribeOption: 'major'
		};
		this.options = {
			option1: {
				value: 'major',
				text: 'New GEM releases'
			},
			option2: {
				value: 'all',
				text: 'Limited major updates'
			}
		}
		this.timer = 1;
		this.activetab = true;
		this.holdSubscribe = false;
		this.internalClose = false;
		this.init();
	}
	init() {
		authEmitter.addListener('profile', (data) => {
			this.setState({
				profile: data
			});
		});
		let subPopuptimer = storageService.get('subPopuptimer');
		if (subPopuptimer && subPopuptimer !== 'NaN') {
			this.timer = parseInt(storageService.get('subPopuptimer'));
		}
		setTimeout(() => {
			if (!this.state.profile) {
				this.open();
			}
		}, 1000 * 5 * this.timer);

		$(window).focus(function() {
			this.activetab = true;
			setTimeout(() => {
				if (!this.state.profile && this.holdSubscribe && !this.internalClose) {
					this.open();
				}
			}, 1000 * 10 * this.timer);
		}.bind(this));

		$(window).blur(function() {
			this.activetab = false;
		}.bind(this));
	}
	close() {
		this.internalClose = true;
		storageService.set('subPopuptimer', this.timer + 5);
		this.setState({ showModal: false });
	}
	open() {
		if (!this.state.profile) {
			if (!$('.fade.in.modal').length) {
				if(this.activetab) {
					this.setState({ showModal: true });
				} else {
					this.holdSubscribe = true;
				}
			} else {
				setTimeout(() => {
					this.open();
				}, 1000 * 10);
			}
		}
	}
	subscribe() {
		authOperation.login(this.state.subscribeOption);
	}
	subscribeOptionChange(value) {
		this.setState({
			subscribeOption: value
		});
	}
	showIcon() {
		let icon = ();
		if (this.state.profile) {
			icon = ();
		}
		return icon;
	}
	render() {
		return (

			 this.open()}>
				{this.showIcon()}

			<Modal className="modal-info" show={this.state.showModal} onHide={() => this.close()}>
			<Modal.Header closeButton>
				<Modal.Title>Be in the know about major updates!</Modal.Title>
			</Modal.Header>
			<Modal.Body>

								<input type="radio"
									checked={this.state.subscribeOption === this.options.option1.value}
									onChange={() => this.subscribeOptionChange(this.options.option1.value)}
									name="subscribeOption" id="subscribeOption" value={this.options.option1.value} /> {this.options.option1.text}

								<input type="radio"
									checked={this.state.subscribeOption === this.options.option2.value}
									onChange={() => this.subscribeOptionChange(this.options.option2.value)}
									name="subscribeOption1" id="subscribeOption1" value={this.options.option2.value} /> {this.options.option2.text}

					<button className="btn btn-primary" onClick={() => this.subscribe()}>
						 Subscribe with Github
					</button>

				</Modal.Body>
			</Modal>

		)
	}
}
<|endoftext|>


<commit_before>import express from 'express';
import bodyParser from 'body-parser';
import * as repoModel from '../models/repo';
import * as tokenModel from '../models/token';
import * as packageModel from '../models/package';

const jsonParser = bodyParser.json();
const router = new express.Router();
export default router;

router.get('/', (req, res) =>
  res.send('hello world'));

router.get('/:provider/:user', (req, res) => {
  repoModel.query(req.params)
  .then(result => res.json(result),
    error => res.send(error));
});

router.route('/token/:provider/:user/:repo')
  .get((req, res) => {
    tokenModel.get(req.params)
    .then(token => res.json(token),
      error => res.send(error));
  })
  .post((req, res) => {
    tokenModel.create(req.params)
    .then(token => res.json(token),
      error => res.send(error.toString()));
  });

router.route('/:provider/:user/:repo')
  .get((req, res) => {
    packageModel.query(req.params)
    .then(result => res.json(result),
      error => res.send(error));
  })
  .post(jsonParser, (req, res) => {
    tokenModel.validate({ ...req.params, token: req.body.token })
    .then(() => packageModel.upsert({ ...req.params, ...req.body }))
    .then(entity => res.json(entity),
      error => res.send(error.toString()));
  });
<commit_msg>Make controller routes as expressions.<commit_after>import express from 'express';
import bodyParser from 'body-parser';

import * as repoModel from '../models/repo';
import * as tokenModel from '../models/token';
import * as packageModel from '../models/package';

const jsonParser = bodyParser.json();
const router = new express.Router();
export default router;

router.get('/', (req, res) =>
  res.send('hello world'));

router.get('/:provider/:user', (req, res) =>
  repoModel.query(req.params)
  .then(result => res.json(result),
    error => res.send(error)));

router.route('/token/:provider/:user/:repo')
  .get((req, res) =>
    tokenModel.get(req.params)
    .then(token => res.json(token),
      error => res.send(error)))
  .post((req, res) =>
    tokenModel.create(req.params)
    .then(token => res.json(token),
      error => res.send(error.toString())));

router.route('/:provider/:user/:repo')
  .get((req, res) =>
    packageModel.query(req.params)
    .then(result => res.json(result),
      error => res.send(error)))
  .post(jsonParser, (req, res) =>
    tokenModel.validate({ ...req.params, token: req.body.token })
    .then(() => packageModel.upsert({ ...req.params, ...req.body }))
    .then(entity => res.json(entity),
      error => res.send(error.toString())));
<|endoftext|>


<commit_before>import Config from 'react-native-config';

// Actions
const GET_QUESTIONS = 'report/GET_QUESTIONS';
const SAVE_REPORT = 'report/SAVE_REPORT';

// Reducer
const initialNavState = {
  forms: {},
  answers: {
    idForm: 'idAlert'
  }
};

export default function reducer(state = initialNavState, action) {
  switch (action.type) {
    case GET_QUESTIONS: {
      return Object.assign({}, state, { forms: action.payload });
    }
    default: {
      return state;
    }
  }
}

// Action Creators
export function getQuestions() {
  return (dispatch, state) => {
    const url = `${Config.API_URL}/questionnaire/${Config.QUESTIONNARIE_ID}`;
    const fetchConfig = {
      headers: {
        'content-type': 'application/json',
        Authorization: `Bearer ${state().user.token}`
      }
    };
    fetch(url, fetchConfig)
      .then(response => {
        if (response.ok) return response.json();
        throw Error(response);
      })
      .then((data) => {
        let form = [];
        if (data && data.data && data.data[0]) form = data.data[0].attributes;
        form.questions = form.questions.sort((a, b) => parseInt(a.order, 10) - parseInt(b.order, 10));
        dispatch({
          type: GET_QUESTIONS,
          payload: form
        });
      })
      .catch((err) => {
        // TODO: handle error
        console.warn(err);
      });
  };
}

export function saveReport(report) {
  console.warn('TODO: save report', report);
  return {
    type: SAVE_REPORT,
    payload: report
  };
}
<commit_msg>Send form to API<commit_after>import Config from 'react-native-config';
import RNFetchBlob from 'react-native-fetch-blob';

// Actions
const GET_QUESTIONS = 'report/GET_QUESTIONS';
const SAVE_REPORT = 'report/SAVE_REPORT';

// Reducer
const initialNavState = {
  forms: {},
  answers: {
    idForm: 'idAlert'
  }
};

export default function reducer(state = initialNavState, action) {
  switch (action.type) {
    case GET_QUESTIONS: {
      return Object.assign({}, state, { forms: action.payload });
    }
    default: {
      return state;
    }
  }
}

// Action Creators
export function getQuestions() {
  return (dispatch, state) => {
    const url = `${Config.API_URL}/questionnaire/${Config.QUESTIONNARIE_ID}`;
    const fetchConfig = {
      headers: {
        'content-type': 'application/json',
        Authorization: `Bearer ${state().user.token}`
      }
    };
    fetch(url, fetchConfig)
      .then(response => {
        if (response.ok) return response.json();
        throw Error(response);
      })
      .then((data) => {
        let form = [];
        if (data && data.data && data.data[0]) form = data.data[0].attributes;
        form.questions = form.questions.sort((a, b) => parseInt(a.order, 10) - parseInt(b.order, 10));
        dispatch({
          type: GET_QUESTIONS,
          payload: form
        });
      })
      .catch((err) => {
        // TODO: handle error
        console.warn(err);
      });
  };
}

export function saveReport(report) {
  return (dispatch, state) => {
    console.warn('TODO: save report', report);
    const form = new FormData();
    const promises = [];
    const keys = [];
    Object.keys(report).forEach((key) => {
      if (report[key].indexOf('jpg') >= 0) { // TODO: improve this
        promises.push(RNFetchBlob.fs.readFile(report[key], 'base64'));
        keys.push(key);
      } else {
        form.append(key, report[key]);
      }
    });
    if (promises.length > 0) {
      Promise.all(promises).then((data) => {
        for (let i = 0, pLenght = promises.length; i < pLenght; i++) {
          form.append(keys[i], data[i]);
        }
      });
    }
    const url = `${Config.API_URL}/questionnaire/${Config.QUESTIONNARIE_ID}`;
    const fetchConfig = {
      headers: {
        Authorization: `Bearer ${state().user.token}`,
        'Content-Type': 'multipart/form-data'
      },
      method: 'POST',
      body: form
    };
    fetch(url, fetchConfig)
      .then((response) => {
        if (response.ok) return response.json();
        throw Error(response);
      })
      .then((data) => {
        console.log(data, 'form data response');
        return {
          type: SAVE_REPORT,
          payload: data
        };
      })
      .catch((err) => {
        console.log('TODO: handle error', err);
      });
  };
}
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import {
	LayoutAnimation,
	ListView,
	StatusBar,
	StyleSheet,
	Text,
	TouchableOpacity,
} from 'react-native';
import moment from 'moment';

import Splash from 'react-native-smart-splash-screen';

import { TIME_FORMAT } from '../../constants';
import talks, { getIndexFromId, getNextTalkFromId, getPrevTalkFromId } from '../../data/talks';
import Navbar from '../../components/Navbar';
import ListTitle from '../../components/ListTitle';
import Scene from '../../components/Scene';

import theme from '../../theme';
import { bindMethods } from '../../utils';

import Break from './components/Break';
import NowButton from './components/NowButton';
import Talk, { TalkSeparator } from './components/Talk';
import SplashScreen from './components/SplashScreen';

function elementIsInView ({ viewTop, viewBottom, elementTop, elementBottom }, fullyInView = true) {
	if (fullyInView) {
		return ((viewTop < elementTop) && (viewBottom > elementBottom));
	} else {
		return ((elementTop <= viewBottom) && (elementBottom >= viewTop));
	}
}

export default class Schedule extends Component {
	constructor (props) {
		super(props);

		bindMethods.call(this, [
			'getActiveTalkLayout',
			'gotoEventInfo',
			'handleScroll',
			'scrolltoActiveTalk',
		]);

		const dataBlob = {};
		const sectionIDs = [];
		const rowIDs = [];
		let sectionIndex = 0;

		props.talks.forEach((talk) => {
			const sID = moment(talk.time.start).format('dddd');

			// create new section and initialize empty array for section index
			if (!dataBlob[sID]) {
				sectionIDs.push(sID);
				rowIDs[sectionIndex] = [];
				sectionIndex++;
				dataBlob[sID] = sID;
			}

			rowIDs[rowIDs.length - 1].push(talk.id);
			dataBlob[sID + ':' + talk.id] = talk;
		});

		const ds = new ListView.DataSource({
			getSectionData: (dataBlob, sectionID) => dataBlob[sectionID],
			getRowData: (dataBlob, sectionID, rowID) => dataBlob[sectionID + ':' + rowID],
			rowHasChanged: (r1, r2) => r1 !== r2,
			sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
		});

		this.state = {
			animatingSplash: true,
			dataSource: ds.cloneWithRowsAndSections(dataBlob, sectionIDs, rowIDs),
			navbarTop: -64,
		};
	}

	componentDidMount () {
		// This is the actual image splash screen, not the animated one.
		if (Splash) {
			Splash.close({
				animationType: Splash.animationType.fade,
				duration: 300,
				delay: 500,
			});
		}
	}

	gotoEventInfo () {
		this.props.navigator.push({
			scene: 'Info',
		});
	}
	handleScroll ({ scrollY, viewHeight }) {
		// Navbar top position
		const navbarTop = Math.max(Math.min(scrollY - 124, 0), -64);
		this.setState({ navbarTop });

		// Now button
		const { activeTalk } = this.state;

		// TODO all talks are over. Discuss how to handle
		if (!activeTalk) return;

		const showNowButton = !elementIsInView({
			viewTop: scrollY,
			viewBottom: scrollY + viewHeight,
			elementTop: activeTalk.position,
			elementBottom: activeTalk.position + activeTalk.height,
		});

		this.toggleNowButton(showNowButton);
	}
	scrolltoActiveTalk () {
		const { activeTalk } = this.state;

		this.refs.listview.scrollTo({ x: 0, y: activeTalk.position, animated: true });

		// HACK scrollTo doesn't have a completion callback
		// See GitHub issue #11657 https://github.com/facebook/react-native/issues/11657

		const approximateScrollDuration = 360;
		setTimeout(() => this.setState({
			showNowButton: false,
		}), approximateScrollDuration);
	}
	toggleNowButton (showNowButton) {
		LayoutAnimation.easeInEaseOut();
		this.setState({ showNowButton });
	}
	getActiveTalkLayout ({ height, position }) {
		this.setState({
			activeTalk: { height, position },
		});
	}
	render () {
		const { navigator, talks } = this.props;
		const { animatingSplash, dataSource, navbarTop, showNowButton } = this.state;

		const renderFooter = () => (
			<TouchableOpacity key="footer" onPress={this.gotoEventInfo} activeOpacity={0.75}>
				<Text style={styles.link}>
					Event Info
				</Text>
			</TouchableOpacity>
		);

		// we need the "active talk" to be rendered to get its scroll position
		// also, there's so few items it's not a perf concern
		const initialListSize = talks.length;

		return (
			<Scene>
				<StatusBar barStyle={navbarTop > -52 ? 'dark-content' : 'light-content'} />
				{animatingSplash
					&& <SplashScreen
						animated
						onAnimationComplete={() => this.setState({ animatingSplash: false })}
					/>}

				<Navbar
					title="Schedule"
					rightButtonText="Event Info"
					rightButtonOnPress={this.gotoEventInfo}
					style={[styles.navbar, { top: navbarTop }]}
				/>

				<ListView
					dataSource={dataSource}
					ref="listview"
					initialListSize={initialListSize}
					onScroll={({ nativeEvent: { contentOffset, layoutMeasurement } }) => this.handleScroll({
						viewHeight: layoutMeasurement.height,
						scrollY: contentOffset.y,
					})}
					scrollEventThrottle={16}
					enableEmptySections
					renderHeader={() => animatingSplash ? null : <SplashScreen />}
					renderSeparator={(sectionID, rowID) => {
						const key = sectionID + ':' + rowID;
						const talk = dataSource._dataBlob[key];
						const status = getTalkStatus(talk.time.start, talk.time.end);

						return <TalkSeparator key={key} status={status} />;
					}}
					renderRow={(talk) => {
						const status = getTalkStatus(talk.time.start, talk.time.end);

						if (talk.break) {
							return (
								<Break
									endTime={moment(talk.time.end).format(TIME_FORMAT)}
									startTime={moment(talk.time.start).format(TIME_FORMAT)}
									status={status}
								/>
							);
						}

						// methods on Talk
						const onPress = () => navigator.push({
							enableSwipeToPop: true,
							scene: 'Talk',
							props: {
								introduceUI: getIndexFromId(talk.id) < (talks.length - 1),
								nextTalk: getNextTalkFromId(talk.id),
								prevTalk: getPrevTalkFromId(talk.id),
								talk,
							},
						});

						const onLayout = status === 'present'
							? ({ nativeEvent: { layout } }) => this.getActiveTalkLayout({
								height: layout.height,
								position: layout.y - theme.listheader.height,
							})
							: null;

						return (
							<Talk
								onPress={onPress}
								speakerName={talk.speaker.name}
								speakerAvatarUri={talk.speaker.avatar}
								startTime={moment(talk.time.start).format(TIME_FORMAT)}
								status={status}
								onLayout={onLayout}
								title={talk.title}
							/>
						);
					}}
					renderSectionHeader={(sectionData, sectionID) => (
						<ListTitle
							bordered={!!dataSource.sectionIdentities.indexOf(sectionID)}
							text={sectionData}
						/>
					)}
					renderFooter={renderFooter}
				/>

				{showNowButton && (
					<NowButton onPress={this.scrolltoActiveTalk} />
				)}
			</Scene>
		);
	}
};

Schedule.propTypes = {
	navigator: PropTypes.object.isRequired,
	talks: PropTypes.array.isRequired,
};
Schedule.defaultProps = {
	talks: talks,
};

const styles = StyleSheet.create({
	navbar: {
		position: 'absolute',
		top: 0,
		right: 0,
		left: 0,
		zIndex: 2,
	},
	link: {
		color: theme.color.blue,
		fontSize: theme.fontSize.default,
		fontWeight: '500',
		paddingVertical: theme.fontSize.large,
		marginBottom: 34 * 2,
		textAlign: 'center',
	},
});

function getTalkStatus (startTime, endTime) {
	const now = moment();

	if (now.isBetween(startTime, endTime)) {
		return 'present';
	} else if (now.isBefore(startTime)) {
		return 'future';
	}

	return 'past';
};
<commit_msg>fix scroll to now distance<commit_after>import React, { Component, PropTypes } from 'react';
import {
	LayoutAnimation,
	ListView,
	StatusBar,
	StyleSheet,
	Text,
	TouchableOpacity,
} from 'react-native';
import moment from 'moment';

import Splash from 'react-native-smart-splash-screen';

import { TIME_FORMAT } from '../../constants';
import talks, { getIndexFromId, getNextTalkFromId, getPrevTalkFromId } from '../../data/talks';
import Navbar from '../../components/Navbar';
import ListTitle from '../../components/ListTitle';
import Scene from '../../components/Scene';

import theme from '../../theme';
import { bindMethods } from '../../utils';

import Break from './components/Break';
import NowButton from './components/NowButton';
import Talk, { TalkSeparator } from './components/Talk';
import SplashScreen from './components/SplashScreen';

function elementIsInView ({ viewTop, viewBottom, elementTop, elementBottom }, fullyInView = true) {
	if (fullyInView) {
		return ((viewTop < elementTop) && (viewBottom > elementBottom));
	} else {
		return ((elementTop <= viewBottom) && (elementBottom >= viewTop));
	}
}

export default class Schedule extends Component {
	constructor (props) {
		super(props);

		bindMethods.call(this, [
			'getActiveTalkLayout',
			'gotoEventInfo',
			'handleScroll',
			'scrolltoActiveTalk',
		]);

		const dataBlob = {};
		const sectionIDs = [];
		const rowIDs = [];
		let sectionIndex = 0;

		props.talks.forEach((talk) => {
			const sID = moment(talk.time.start).format('dddd');

			// create new section and initialize empty array for section index
			if (!dataBlob[sID]) {
				sectionIDs.push(sID);
				rowIDs[sectionIndex] = [];
				sectionIndex++;
				dataBlob[sID] = sID;
			}

			rowIDs[rowIDs.length - 1].push(talk.id);
			dataBlob[sID + ':' + talk.id] = talk;
		});

		const ds = new ListView.DataSource({
			getSectionData: (dataBlob, sectionID) => dataBlob[sectionID],
			getRowData: (dataBlob, sectionID, rowID) => dataBlob[sectionID + ':' + rowID],
			rowHasChanged: (r1, r2) => r1 !== r2,
			sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
		});

		this.state = {
			animatingSplash: true,
			dataSource: ds.cloneWithRowsAndSections(dataBlob, sectionIDs, rowIDs),
			navbarTop: -64,
		};
	}

	componentDidMount () {
		// This is the actual image splash screen, not the animated one.
		if (Splash) {
			Splash.close({
				animationType: Splash.animationType.fade,
				duration: 300,
				delay: 500,
			});
		}
	}

	gotoEventInfo () {
		this.props.navigator.push({
			scene: 'Info',
		});
	}
	handleScroll ({ scrollY, viewHeight }) {
		// Navbar top position
		const navbarTop = Math.max(Math.min(scrollY - 124, 0), -64);
		this.setState({ navbarTop });

		// Now button
		const { activeTalk } = this.state;

		// TODO all talks are over. Discuss how to handle
		if (!activeTalk) return;

		const showNowButton = !elementIsInView({
			viewTop: scrollY,
			viewBottom: scrollY + viewHeight,
			elementTop: activeTalk.position,
			elementBottom: activeTalk.position + activeTalk.height,
		});

		this.toggleNowButton(showNowButton);
	}
	scrolltoActiveTalk () {
		const { activeTalk } = this.state;

		this.refs.listview.scrollTo({ x: 0, y: activeTalk.position, animated: true });

		// HACK scrollTo doesn't have a completion callback
		// See GitHub issue #11657 https://github.com/facebook/react-native/issues/11657

		const approximateScrollDuration = 360;
		setTimeout(() => this.setState({
			showNowButton: false,
		}), approximateScrollDuration);
	}
	toggleNowButton (showNowButton) {
		LayoutAnimation.easeInEaseOut();
		this.setState({ showNowButton });
	}
	getActiveTalkLayout ({ height, position }) {
		this.setState({
			activeTalk: { height, position },
		});
	}
	render () {
		const { navigator, talks } = this.props;
		const { animatingSplash, dataSource, navbarTop, showNowButton } = this.state;

		const renderFooter = () => (
			<TouchableOpacity key="footer" onPress={this.gotoEventInfo} activeOpacity={0.75}>
				<Text style={styles.link}>
					Event Info
				</Text>
			</TouchableOpacity>
		);

		// we need the "active talk" to be rendered to get its scroll position
		// also, there's so few items it's not a perf concern
		const initialListSize = talks.length;

		return (
			<Scene>
				<StatusBar barStyle={navbarTop > -52 ? 'dark-content' : 'light-content'} />
				{animatingSplash
					&& <SplashScreen
						animated
						onAnimationComplete={() => this.setState({ animatingSplash: false })}
					/>}

				<Navbar
					title="Schedule"
					rightButtonText="Event Info"
					rightButtonOnPress={this.gotoEventInfo}
					style={[styles.navbar, { top: navbarTop }]}
				/>

				<ListView
					dataSource={dataSource}
					ref="listview"
					initialListSize={initialListSize}
					onScroll={({ nativeEvent: { contentOffset, layoutMeasurement } }) => this.handleScroll({
						viewHeight: layoutMeasurement.height,
						scrollY: contentOffset.y,
					})}
					scrollEventThrottle={16}
					enableEmptySections
					renderHeader={() => animatingSplash ? null : <SplashScreen />}
					renderSeparator={(sectionID, rowID) => {
						const key = sectionID + ':' + rowID;
						const talk = dataSource._dataBlob[key];
						const status = getTalkStatus(talk.time.start, talk.time.end);

						return <TalkSeparator key={key} status={status} />;
					}}
					renderRow={(talk) => {
						const status = getTalkStatus(talk.time.start, talk.time.end);

						if (talk.break) {
							return (
								<Break
									endTime={moment(talk.time.end).format(TIME_FORMAT)}
									startTime={moment(talk.time.start).format(TIME_FORMAT)}
									status={status}
								/>
							);
						}

						// methods on Talk
						const onPress = () => navigator.push({
							enableSwipeToPop: true,
							scene: 'Talk',
							props: {
								introduceUI: getIndexFromId(talk.id) < (talks.length - 1),
								nextTalk: getNextTalkFromId(talk.id),
								prevTalk: getPrevTalkFromId(talk.id),
								talk,
							},
						});

						const onLayout = status === 'present'
							? ({ nativeEvent: { layout } }) => this.getActiveTalkLayout({
								height: layout.height,
								position: layout.y - theme.navbar.height,
							})
							: null;

						return (
							<Talk
								onPress={onPress}
								speakerName={talk.speaker.name}
								speakerAvatarUri={talk.speaker.avatar}
								startTime={moment(talk.time.start).format(TIME_FORMAT)}
								status={status}
								onLayout={onLayout}
								title={talk.title}
							/>
						);
					}}
					renderSectionHeader={(sectionData, sectionID) => (
						<ListTitle
							bordered={!!dataSource.sectionIdentities.indexOf(sectionID)}
							text={sectionData}
						/>
					)}
					renderFooter={renderFooter}
				/>

				{showNowButton && (
					<NowButton onPress={this.scrolltoActiveTalk} />
				)}
			</Scene>
		);
	}
};

Schedule.propTypes = {
	navigator: PropTypes.object.isRequired,
	talks: PropTypes.array.isRequired,
};
Schedule.defaultProps = {
	talks: talks,
};

const styles = StyleSheet.create({
	navbar: {
		position: 'absolute',
		top: 0,
		right: 0,
		left: 0,
		zIndex: 2,
	},
	link: {
		color: theme.color.blue,
		fontSize: theme.fontSize.default,
		fontWeight: '500',
		paddingVertical: theme.fontSize.large,
		marginBottom: 34 * 2,
		textAlign: 'center',
	},
});

function getTalkStatus (startTime, endTime) {
	const now = moment();

	if (now.isBetween(startTime, endTime)) {
		return 'present';
	} else if (now.isBefore(startTime)) {
		return 'future';
	}

	return 'past';
};
<|endoftext|>


<commit_before>import { color as d3Color } from 'd3-color';
import { scaleLinear } from 'd3-scale';
import React from 'react';
import { DropShadowFilter } from 'pixi-filters';

// Components
import ContextMenuItem from './ContextMenuItem';
import DataFetcher from './DataFetcher';
import Inset from './Inset';
import PixiTrack from './PixiTrack';

// Services
import { chromInfo } from './services';
import pubSub, { create } from './services/pub-sub';

// Factories
import { KeySet } from './factories';

// Utils
import {
  absToChr,
  base64ToCanvas,
  lDist,
  flatten,
  isTrackOrChildTrack,
  latToY,
  lngToX,
  tileToCanvas
} from './utils';

const BASE_MIN_SIZE = 12;
const BASE_MAX_SIZE = 24;
const BASE_SCALE = 4;
const ZOOM_TO_ANNO = 1600;

export default class Insets2dTrack extends PixiTrack {
  constructor(
    scene,
    dataConfig,
    dataType,
    chromInfoPath,
    options,
    animate,
    zoomToDataPos,
    positioning  // Computed track position, location, and offset
  ) {
    super(scene, options);

    this.isAugmentationTrack = true;

    this.dataConfig = dataConfig;
    this.dataType = dataType;
    this.options = options;
    this.animate = animate;
    this.zoomToDataPos = zoomToDataPos;
    this.positioning = positioning;  // Needed for the gallery view

    this.positioning.offsetX = this.positioning.offsetX || 0;
    this.positioning.offsetY = this.positioning.offsetY || 0;

    this.fetchChromInfo = this.dataType === 'cooler'
      ? chromInfo.get(chromInfoPath)
      : undefined;

    this.dropShadow = new DropShadowFilter(
      90,
      this.options.dropDistance,
      this.options.dropBlur,
      0x000000,
      this.options.dropOpacity,
    );

    this.pBase.alpha = this.options.opacity;
    this.pMain.filters = [this.dropShadow];

    this.insets = new KeySet();
    this.insetsInPreparation = new KeySet();

    this.insetMouseHandler = {
      click: this.clickHandler.bind(this),
      clickRight: this.clickRightHandler.bind(this),
      mouseOver: this.mouseOverHandler.bind(this),
      mouseOut: this.mouseOutHandler.bind(this),
      mouseDown: this.mouseDownHandler.bind(this),
      mouseDownRight: this.mouseDownHandler.bind(this),
      mouseUp: this.mouseUpHandler.bind(this),
      mouseUpRight: this.mouseUpHandler.bind(this),
    };

    this.pubSubs.push(
      pubSub.subscribe('app.mouseMove', this.mouseMoveHandler.bind(this)),
    );

    // Create a custom pubSub interface
    const { publish, subscribe, unsubscribe } = create({});
    this.publish = publish;
    this.subscribe = subscribe;
    this.unsubscribe = unsubscribe;

    this.options.fill = d3Color(this.options.fill);
    this.options.borderColor = d3Color(this.options.borderColor);
    this.options.leaderLineColor = d3Color(this.options.leaderLineColor);
    this.options.selectColor = d3Color(this.options.selectColor);

    this.insetMinSize = this.options.minSize || BASE_MIN_SIZE;
    this.insetMaxSize = this.options.maxSize || BASE_MAX_SIZE;
    this.insetScale = this.options.scale || BASE_SCALE;

    this.dataFetcher = new DataFetcher(dataConfig);
    this.dataFetcher.tilesetInfo((tilesetInfo) => {
      if (tilesetInfo.error) {
        console.error(
          'Error retrieving tileset info:', dataConfig, tilesetInfo.error
        );
      }
      this.tilesetInfo = tilesetInfo;
    });

    this.contextMenuGoto = (
      <ContextMenuItem
        onClick={() => this.props.onAddTrack({
          type: 'horizontal-rule',
          y: this.props.coords[1],
          position: 'whole',
        })}
        onMouseEnter={e => this.handleOtherMouseEnter(e)}
      >
        {'Zoom to annotation'}
      </ContextMenuItem>
    );

    this.relCursorDist = scaleLinear()
      .domain([
        this.options.leaderLineDynamicMinDist || 0,
        this.options.leaderLineDynamicMaxDist || Math.max(...this.dimensions)
      ])
      .clamp(true);
  }

  getContextMenuGoto(inset) {
    return (
      <ContextMenuItem
        onClick={() => { this.goTo(inset); }}
      >
        {'Zoom to annotation'}
      </ContextMenuItem>
    );
  }

  goTo(inset) {
    // Add some padding for context
    const dataPos = [...inset.dataPos];
    const width = dataPos[1] - dataPos[0];
    const height = dataPos[3] - dataPos[2];
    dataPos[0] -= width * 0.2;
    dataPos[1] += width * 0.2;
    dataPos[2] -= height * 0.2;
    dataPos[3] += height * 0.2;

    this.zoomToDataPos(...dataPos, true, ZOOM_TO_ANNO);
  }

  clear() {
    this.pMain.clear();
  }

  /**
   * Clean up rendered inset and insets which are in preparation
   *
   * @param  {array}  insetIds  List of unique inset IDs to keep
   */
  cleanUp(insetIds) {
    this.insetsInPreparation.forEach((inset) => {
      if (!(insetIds.indexOf(inset.id) >= 0)) {
        this.insetsInPreparation.delete(inset);
      }
    });

    this.insets.forEach((inset) => {
      if (!(insetIds.indexOf(inset.id) >= 0)) {
        this.destroyInset(inset.id);
      }
    });
  }

  createFetchRenderInset(label, remotePos, renderedPos) {
    const inset = (
      this.insets.get(label.id) ||
      this.initInset(
        label.id,
        remotePos,
        renderedPos,
        label.dataPos
      )
    );
    this.insetsInPreparation.delete(label);

    inset.clear(this.options);
    inset.globalOffset(...this.position);
    inset.globalSize(...this.dimensions);
    inset.origin(label.oX, label.oY, label.oWH, label.oHH);
    inset.position(label.x, label.y);
    inset.size(label.width, label.height);
    inset.drawLeaderLine();
    inset.drawBorder();

    return inset.drawImage(this.rendererInset.bind(this));
  }

  dataToGenomePos(dataPos, _chromInfo) {
    return dataPos.map(([dX1, dX2, dY1, dY2]) => {
      const x = absToChr(dX1, _chromInfo);
      const y = absToChr(dY1, _chromInfo);

      return [
        x[0],
        x[1],
        x[1] + dX2 - dX1,
        y[0],
        y[1],
        y[1] + dY2 - dY1,
      ];
    });
  }

  /**
   * Draw an inset.
   * @description This is the entry point for creating an inset including to
   *   download the snippet or aggregate it represents.
   * @param   {Label}  label  Label to be drawn as an inset.
   * @return  {Promise}  Promise resolving once the inset has been drawn.
   */
  drawInset(label) {
    this.insetsInPreparation.add(label);

    if (this.dataType === 'cooler') {
      if (!this.fetchChromInfo) return Promise.reject('This is truly odd!');

      return this.fetchChromInfo
        .then((_chromInfo) => {
          if (!this.insetsInPreparation.has(label)) {
            // Label must have been deleted in the meantime
            return Promise.resolve('Inset has been deleted prior to rendering');
          }

          return this.createFetchRenderInset(
            label,
            this.dataToGenomePos(label.dataPos, _chromInfo)
          );
        });
    }

    if (this.dataType === 'osm-image') {
      return this.createFetchRenderInset(
        label,
        [...label.dataPos],
        this.lngLatToProjPos(label.dataPos)
      );
    }

    return this.createFetchRenderInset(
      label,
      label.dataPos,
      label.dataPos
    );
  }

  /**
   * Draw insets
   * @param   {KeySet}  insets  Insets to be drawn
   * @return  {Array}  List of promises that resolve one the inset is fully
   *   drawn.
   */
  drawInsets(insets) {
    if (!this.tilesetInfo) {
      return [Promise.reject('Tileset info not available')];
    }

    this.cleanUp(insets.keys);

    return insets.translate(inset => this.drawInset(inset));
  }

  /**
   * Destroy an inset, i.e., call the inset's destroy method and remove it from
   * the cache.
   *
   * @param  {String}  id  ID of the inset to be destroyed.
   */
  destroyInset(id) {
    this.pMain.removeChild(this.insets.get(id).graphics);
    this.insets.get(id).destroy();
    this.insets.delete(id);
  }

  initInset(
    id,
    remotePos,
    renderedPos,
    dataPos,
    dataConfig = this.dataConfig,
    tilesetInfo = this.tilesetInfo,
    options = this.options,
    mouseHandler = this.insetMouseHandler
  ) {
    const newInset = new Inset(
      id,
      remotePos,
      renderedPos,
      dataPos,
      dataConfig,
      tilesetInfo,
      options,
      mouseHandler,
      this.dataType
    );
    this.insets.add(newInset);
    this.pMain.addChild(newInset.graphics);
    return newInset;
  }

  /**
   * Project longitude and latitude to projected Mercator position
   * @param   {array}  dataPos  List of quadruples in form of `[xFrom, xTo,
   *   yFrom, yTo]` with the X coordinates being in logitude and Y being in
   *   latitude.
   * @return  {array}  Projected position in form of `[xFrom, xTo, yFrom,
   *   yTo]` where X and Y correspond to absolute pixel positions at the
   *   highest zoom level.
   */
  lngLatToProjPos(dataPos) {
    return dataPos.map(([dX1, dX2, dY1, dY2]) => [
      lngToX(dX1, 19) * this.tilesetInfo.tile_size,
      lngToX(dX2, 19) * this.tilesetInfo.tile_size,
      latToY(dY1, 19) * this.tilesetInfo.tile_size,
      latToY(dY2, 19) * this.tilesetInfo.tile_size,
    ]);
  }

  clickHandler(/* event, inset */) {}

  clickRightHandler(event, inset) {
    console.log('PIXI CONTEXT MENU', event.type, inset);

    event.data.originalEvent.hgCustomItems = [
      this.getContextMenuGoto(inset)
    ];

    pubSub.publish('contextmenu', event.data.originalEvent);
  }

  mouseOverHandler(/* event, inset */) {
    this.animate();
  }

  mouseOutHandler(/* event, inset */) {
    this.animate();
  }

  mouseDownHandler(event, inset) {
    this.hoveringInsetIdx = this.pMain.getChildIndex(inset.gMain);
    this.pMain.setChildIndex(inset.gMain, this.pMain.children.length - 1);
    this.animate();
  }

  mouseDownRightHandler(/* event, inset */) {
  }

  mouseUpHandler(event, inset) {
    this.pMain.setChildIndex(inset.gMain, this.hoveringInsetIdx);
    this.animate();
  }

  mouseUpRightHandler(/* event, inset */) {}

  mouseMoveHandler(event) {
    if (event.hoveredTracks.some(track => isTrackOrChildTrack(this, track))) {
      let x = event.relTrackX;
      let y = event.relTrackY;
      if (this.positioning.location === 'gallery') {
        x = event.x - this.position[0];
        y = event.y - this.position[0];
      }
      this.updateDistances(x, y);
    }
  }

  updateDistances(x, y) {
    const closest = { d: Infinity, inset: null };
    this.insets.forEach((inset) => {
      const d = this.computeDistance(x, y, inset);

      inset.distance(d, this.relCursorDist(d));

      if (closest.d > d) {
        closest.d = d;
        closest.inset = inset;
      }

      if (this.options.leaderLineDynamic) inset.drawLeaderLine();
    });
    // this.updateClosestInset(closest.inset);
    this.animate();
  }

  updateClosestInset(inset) {
    if (!inset || (this.closestInset && this.closestInset === inset)) return;

    if (this.closestInset) this.closestInset.blur();

    this.closestInset = inset;
    this.closestInset.focus();
    this.animate();
  }

  computeDistance(x, y, inset) {
    const distToOrigin = lDist(
      [inset.originX, inset.originY],
      [x, y]
    );
    const distToInset = lDist(
      [inset.x, inset.y],
      [x, y]
    );
    return Math.min(distToOrigin, distToInset);
  }

  rendererInset(data) {
    return data.dataTypes[0] === 'dataUrl'
      ? this.rendererImage(data.fragments[0])
      : this.rendererHeatmap(data.fragments[0]);
  }

  rendererHeatmap(data) {
    const flatImg = flatten(data);

    const height = data.length;
    const width = data[0].length;

    const n = flatImg.length;

    const pixData = new Uint8ClampedArray(n * 4);

    for (let i = 0; i < n; i++) {
      const j = i * 4;
      const val = 255 - (flatImg[i] * 255);
      pixData[j] = val;
      pixData[j + 1] = val;
      pixData[j + 2] = val;
      pixData[j + 3] = 255;
    }

    return Promise.resolve(tileToCanvas(pixData, width, height));
  }

  rendererImage(data) {
    return base64ToCanvas(data);
  }

  setDimensions(newDimensions) {
    super.setDimensions(newDimensions);

    this.publish('dimensions', newDimensions);
  }

  setPosition(newPosition) {
    super.setPosition(newPosition);

    this.publish('position', newPosition);
  }

  zoomed(newXScale, newYScale, k) {
    super.zoomed(newXScale, newYScale, k);

    this.publish('zoom', { newXScale, newYScale, k });
  }
}
<commit_msg>Fix zoom into pattern<commit_after>import { color as d3Color } from 'd3-color';
import { scaleLinear } from 'd3-scale';
import React from 'react';
import { DropShadowFilter } from 'pixi-filters';

// Components
import ContextMenuItem from './ContextMenuItem';
import DataFetcher from './DataFetcher';
import Inset from './Inset';
import PixiTrack from './PixiTrack';

// Services
import { chromInfo } from './services';
import pubSub, { create } from './services/pub-sub';

// Factories
import { KeySet } from './factories';

// Utils
import {
  absToChr,
  base64ToCanvas,
  lDist,
  flatten,
  isTrackOrChildTrack,
  latToY,
  lngToX,
  tileToCanvas
} from './utils';

const BASE_MIN_SIZE = 12;
const BASE_MAX_SIZE = 24;
const BASE_SCALE = 4;
const ZOOM_TO_ANNO = 1600;

export default class Insets2dTrack extends PixiTrack {
  constructor(
    scene,
    dataConfig,
    dataType,
    chromInfoPath,
    options,
    animate,
    zoomToDataPos,
    positioning  // Computed track position, location, and offset
  ) {
    super(scene, options);

    this.isAugmentationTrack = true;

    this.dataConfig = dataConfig;
    this.dataType = dataType;
    this.options = options;
    this.animate = animate;
    this.zoomToDataPos = zoomToDataPos;
    this.positioning = positioning;  // Needed for the gallery view

    this.positioning.offsetX = this.positioning.offsetX || 0;
    this.positioning.offsetY = this.positioning.offsetY || 0;

    this.fetchChromInfo = this.dataType === 'cooler'
      ? chromInfo.get(chromInfoPath)
      : undefined;

    this.dropShadow = new DropShadowFilter(
      90,
      this.options.dropDistance,
      this.options.dropBlur,
      0x000000,
      this.options.dropOpacity,
    );

    this.pBase.alpha = this.options.opacity;
    this.pMain.filters = [this.dropShadow];

    this.insets = new KeySet();
    this.insetsInPreparation = new KeySet();

    this.insetMouseHandler = {
      click: this.clickHandler.bind(this),
      clickRight: this.clickRightHandler.bind(this),
      mouseOver: this.mouseOverHandler.bind(this),
      mouseOut: this.mouseOutHandler.bind(this),
      mouseDown: this.mouseDownHandler.bind(this),
      mouseDownRight: this.mouseDownHandler.bind(this),
      mouseUp: this.mouseUpHandler.bind(this),
      mouseUpRight: this.mouseUpHandler.bind(this),
    };

    this.pubSubs.push(
      pubSub.subscribe('app.mouseMove', this.mouseMoveHandler.bind(this)),
    );

    // Create a custom pubSub interface
    const { publish, subscribe, unsubscribe } = create({});
    this.publish = publish;
    this.subscribe = subscribe;
    this.unsubscribe = unsubscribe;

    this.options.fill = d3Color(this.options.fill);
    this.options.borderColor = d3Color(this.options.borderColor);
    this.options.leaderLineColor = d3Color(this.options.leaderLineColor);
    this.options.selectColor = d3Color(this.options.selectColor);

    this.insetMinSize = this.options.minSize || BASE_MIN_SIZE;
    this.insetMaxSize = this.options.maxSize || BASE_MAX_SIZE;
    this.insetScale = this.options.scale || BASE_SCALE;

    this.dataFetcher = new DataFetcher(dataConfig);
    this.dataFetcher.tilesetInfo((tilesetInfo) => {
      if (tilesetInfo.error) {
        console.error(
          'Error retrieving tileset info:', dataConfig, tilesetInfo.error
        );
      }
      this.tilesetInfo = tilesetInfo;
    });

    this.contextMenuGoto = (
      <ContextMenuItem
        onClick={() => this.props.onAddTrack({
          type: 'horizontal-rule',
          y: this.props.coords[1],
          position: 'whole',
        })}
        onMouseEnter={e => this.handleOtherMouseEnter(e)}
      >
        {'Zoom to annotation'}
      </ContextMenuItem>
    );

    this.relCursorDist = scaleLinear()
      .domain([
        this.options.leaderLineDynamicMinDist || 0,
        this.options.leaderLineDynamicMaxDist || Math.max(...this.dimensions)
      ])
      .clamp(true);
  }

  getContextMenuGoto(inset) {
    return (
      <ContextMenuItem
        onClick={() => { this.goTo(inset); }}
      >
        {'Zoom to annotation'}
      </ContextMenuItem>
    );
  }

  goTo(inset) {
    // Add some padding for context
    const dataPosBounds = [Infinity, 0, Infinity, 0];

    inset.dataPos.forEach((dataPos) => {
      dataPosBounds[0] = Math.min(dataPosBounds[0], dataPos[0]);
      dataPosBounds[1] = Math.max(dataPosBounds[1], dataPos[1]);
      dataPosBounds[2] = Math.min(dataPosBounds[2], dataPos[2]);
      dataPosBounds[3] = Math.max(dataPosBounds[3], dataPos[3]);
    });

    const width = dataPosBounds[1] - dataPosBounds[0];
    const height = dataPosBounds[3] - dataPosBounds[2];
    dataPosBounds[0] -= width * 0.2;
    dataPosBounds[1] += width * 0.2;
    dataPosBounds[2] -= height * 0.2;
    dataPosBounds[3] += height * 0.2;

    this.zoomToDataPos(...dataPosBounds, true, ZOOM_TO_ANNO);
  }

  clear() {
    this.pMain.clear();
  }

  /**
   * Clean up rendered inset and insets which are in preparation
   *
   * @param  {array}  insetIds  List of unique inset IDs to keep
   */
  cleanUp(insetIds) {
    this.insetsInPreparation.forEach((inset) => {
      if (!(insetIds.indexOf(inset.id) >= 0)) {
        this.insetsInPreparation.delete(inset);
      }
    });

    this.insets.forEach((inset) => {
      if (!(insetIds.indexOf(inset.id) >= 0)) {
        this.destroyInset(inset.id);
      }
    });
  }

  createFetchRenderInset(label, remotePos, renderedPos) {
    const inset = (
      this.insets.get(label.id) ||
      this.initInset(
        label.id,
        remotePos,
        renderedPos,
        label.dataPos
      )
    );
    this.insetsInPreparation.delete(label);

    inset.clear(this.options);
    inset.globalOffset(...this.position);
    inset.globalSize(...this.dimensions);
    inset.origin(label.oX, label.oY, label.oWH, label.oHH);
    inset.position(label.x, label.y);
    inset.size(label.width, label.height);
    inset.drawLeaderLine();
    inset.drawBorder();

    return inset.drawImage(this.rendererInset.bind(this));
  }

  dataToGenomePos(dataPos, _chromInfo) {
    return dataPos.map(([dX1, dX2, dY1, dY2]) => {
      const x = absToChr(dX1, _chromInfo);
      const y = absToChr(dY1, _chromInfo);

      return [
        x[0],
        x[1],
        x[1] + dX2 - dX1,
        y[0],
        y[1],
        y[1] + dY2 - dY1,
      ];
    });
  }

  /**
   * Draw an inset.
   * @description This is the entry point for creating an inset including to
   *   download the snippet or aggregate it represents.
   * @param   {Label}  label  Label to be drawn as an inset.
   * @return  {Promise}  Promise resolving once the inset has been drawn.
   */
  drawInset(label) {
    this.insetsInPreparation.add(label);

    if (this.dataType === 'cooler') {
      if (!this.fetchChromInfo) return Promise.reject('This is truly odd!');

      return this.fetchChromInfo
        .then((_chromInfo) => {
          if (!this.insetsInPreparation.has(label)) {
            // Label must have been deleted in the meantime
            return Promise.resolve('Inset has been deleted prior to rendering');
          }

          return this.createFetchRenderInset(
            label,
            this.dataToGenomePos(label.dataPos, _chromInfo)
          );
        });
    }

    if (this.dataType === 'osm-image') {
      return this.createFetchRenderInset(
        label,
        [...label.dataPos],
        this.lngLatToProjPos(label.dataPos)
      );
    }

    return this.createFetchRenderInset(
      label,
      label.dataPos,
      label.dataPos
    );
  }

  /**
   * Draw insets
   * @param   {KeySet}  insets  Insets to be drawn
   * @return  {Array}  List of promises that resolve one the inset is fully
   *   drawn.
   */
  drawInsets(insets) {
    if (!this.tilesetInfo) {
      return [Promise.reject('Tileset info not available')];
    }

    this.cleanUp(insets.keys);

    return insets.translate(inset => this.drawInset(inset));
  }

  /**
   * Destroy an inset, i.e., call the inset's destroy method and remove it from
   * the cache.
   *
   * @param  {String}  id  ID of the inset to be destroyed.
   */
  destroyInset(id) {
    this.pMain.removeChild(this.insets.get(id).graphics);
    this.insets.get(id).destroy();
    this.insets.delete(id);
  }

  initInset(
    id,
    remotePos,
    renderedPos,
    dataPos,
    dataConfig = this.dataConfig,
    tilesetInfo = this.tilesetInfo,
    options = this.options,
    mouseHandler = this.insetMouseHandler
  ) {
    const newInset = new Inset(
      id,
      remotePos,
      renderedPos,
      dataPos,
      dataConfig,
      tilesetInfo,
      options,
      mouseHandler,
      this.dataType
    );
    this.insets.add(newInset);
    this.pMain.addChild(newInset.graphics);
    return newInset;
  }

  /**
   * Project longitude and latitude to projected Mercator position
   * @param   {array}  dataPos  List of quadruples in form of `[xFrom, xTo,
   *   yFrom, yTo]` with the X coordinates being in logitude and Y being in
   *   latitude.
   * @return  {array}  Projected position in form of `[xFrom, xTo, yFrom,
   *   yTo]` where X and Y correspond to absolute pixel positions at the
   *   highest zoom level.
   */
  lngLatToProjPos(dataPos) {
    return dataPos.map(([dX1, dX2, dY1, dY2]) => [
      lngToX(dX1, 19) * this.tilesetInfo.tile_size,
      lngToX(dX2, 19) * this.tilesetInfo.tile_size,
      latToY(dY1, 19) * this.tilesetInfo.tile_size,
      latToY(dY2, 19) * this.tilesetInfo.tile_size,
    ]);
  }

  clickHandler(/* event, inset */) {}

  clickRightHandler(event, inset) {
    console.log('PIXI CONTEXT MENU', event.type, inset);

    event.data.originalEvent.hgCustomItems = [
      this.getContextMenuGoto(inset)
    ];

    pubSub.publish('contextmenu', event.data.originalEvent);
  }

  mouseOverHandler(/* event, inset */) {
    this.animate();
  }

  mouseOutHandler(/* event, inset */) {
    this.animate();
  }

  mouseDownHandler(event, inset) {
    this.hoveringInsetIdx = this.pMain.getChildIndex(inset.gMain);
    this.pMain.setChildIndex(inset.gMain, this.pMain.children.length - 1);
    this.animate();
  }

  mouseDownRightHandler(/* event, inset */) {
  }

  mouseUpHandler(event, inset) {
    this.pMain.setChildIndex(inset.gMain, this.hoveringInsetIdx);
    this.animate();
  }

  mouseUpRightHandler(/* event, inset */) {}

  mouseMoveHandler(event) {
    if (event.hoveredTracks.some(track => isTrackOrChildTrack(this, track))) {
      let x = event.relTrackX;
      let y = event.relTrackY;
      if (this.positioning.location === 'gallery') {
        x = event.x - this.position[0];
        y = event.y - this.position[0];
      }
      this.updateDistances(x, y);
    }
  }

  updateDistances(x, y) {
    const closest = { d: Infinity, inset: null };
    this.insets.forEach((inset) => {
      const d = this.computeDistance(x, y, inset);

      inset.distance(d, this.relCursorDist(d));

      if (closest.d > d) {
        closest.d = d;
        closest.inset = inset;
      }

      if (this.options.leaderLineDynamic) inset.drawLeaderLine();
    });
    // this.updateClosestInset(closest.inset);
    this.animate();
  }

  updateClosestInset(inset) {
    if (!inset || (this.closestInset && this.closestInset === inset)) return;

    if (this.closestInset) this.closestInset.blur();

    this.closestInset = inset;
    this.closestInset.focus();
    this.animate();
  }

  computeDistance(x, y, inset) {
    const distToOrigin = lDist(
      [inset.originX, inset.originY],
      [x, y]
    );
    const distToInset = lDist(
      [inset.x, inset.y],
      [x, y]
    );
    return Math.min(distToOrigin, distToInset);
  }

  rendererInset(data) {
    return data.dataTypes[0] === 'dataUrl'
      ? this.rendererImage(data.fragments[0])
      : this.rendererHeatmap(data.fragments[0]);
  }

  rendererHeatmap(data) {
    const flatImg = flatten(data);

    const height = data.length;
    const width = data[0].length;

    const n = flatImg.length;

    const pixData = new Uint8ClampedArray(n * 4);

    for (let i = 0; i < n; i++) {
      const j = i * 4;
      const val = 255 - (flatImg[i] * 255);
      pixData[j] = val;
      pixData[j + 1] = val;
      pixData[j + 2] = val;
      pixData[j + 3] = 255;
    }

    return Promise.resolve(tileToCanvas(pixData, width, height));
  }

  rendererImage(data) {
    return base64ToCanvas(data);
  }

  setDimensions(newDimensions) {
    super.setDimensions(newDimensions);

    this.publish('dimensions', newDimensions);
  }

  setPosition(newPosition) {
    super.setPosition(newPosition);

    this.publish('position', newPosition);
  }

  zoomed(newXScale, newYScale, k) {
    super.zoomed(newXScale, newYScale, k);

    this.publish('zoom', { newXScale, newYScale, k });
  }
}
<|endoftext|>


<commit_before>'use strict';

var readabilityCallback = function(dataRdb, target, id, progress) {

  if (dataRdb.error || dataRdb.readability === undefined) {
    progress.destroy();
    progress =
      new ProgressBar.Circle(
        document.getElementById(id).querySelector('.readability-circle'), {
          strokeWidth: 7,
          trailWidth: 7,
          trailColor: '#ddd',
          color: 'black',
          easing: 'easeInOut',
          duration: 800,
        });

    target.find('.readability')
      .find('span')
      .html($('<i>', {
        class: 'fa fa-ban',
        'aria-hidden': 'true',
      }));

    progress.set(1);
    return;
  }

  var difficulty = dataRdb.readability.difficulty;

  if (target.find('.rdb').length === 0) {
    var readabilityColor = 'red';
    if (difficulty === 'average') {
      readabilityColor = 'orange';
    } else if (difficulty === 'easy') {
      readabilityColor = 'green';
    }
    progress.destroy();
    progress =
    new ProgressBar.Circle(
    document.getElementById(id).querySelector('.readability-circle'), {
      strokeWidth: 7,
      trailWidth: 7,
      trailColor: '#ddd',
      color: readabilityColor,
      easing: 'easeInOut',
      duration: 800,
    });

    target.find('.readability')
      .find('span')
      .html($('<i>', {
        class: 'fa fa-book',
        'aria-hidden': 'true',
      }));

    try {
      progress.animate(1);
    } catch (e) {
      progress.set(1);
    }

  }
};

var trustabilityCallback = function(data, target, id, progress) {
  if (data.error || data.trustability === undefined) {
    progress.destroy();
    progress =
      new ProgressBar.Circle(
        document.getElementById(id).querySelector('.trustability-circle'), {
          strokeWidth: 7,
          trailWidth: 7,
          trailColor: '#ddd',
          color: 'black',
          easing: 'easeInOut',
          duration: 800,
        });
    target.find('.trustability')
      .find('span')
      .html($('<i>', {
        class: 'fa fa-ban',
        'aria-hidden': 'true',
      }));
    progress.set(1);
    return;
  }

  var score = data.trustability.score;

  if (target.find('.trb').length === 0) {

    var trustabilityColor = 'red';
    if (score > 33 && score <= 66) {
      trustabilityColor = 'orange';
    } else if (score > 66) {
      trustabilityColor = 'green';
    }
    progress.destroy();
    progress =
      new ProgressBar.Circle(
        document.getElementById(id).querySelector('.trustability-circle'), {
          strokeWidth: 7,
          trailWidth: 7,
          trailColor: '#ddd',
          color: trustabilityColor,
          easing: 'easeInOut',
          duration: 800,
        });
    target.find('.trustability')
      .find('span')
      .html($('<i>', {
        class: 'fa fa-stethoscope',
        'aria-hidden': 'true',
      }));
    if (score === 0) {
      score = 100;
    }
    try {
      progress.animate(score / 100);
    } catch (e) {
      progress.set(score / 100);
    }
  }
};

var requestKconnect = function(event, link) {
  // Prevent Duckduckgo onclick redirect
  event.stopPropagation();

  var domain = kconnect.getDomainFromUrl(link);
  var trustabilityRequest = kconnect.getIsTrustable(domain);
  var readabilityRequest = kconnect.getReadability(link);
  var layerId = 'layer' + event.target.id;
  var $logoId =  $(event.target);

  var fa = document.createElement('style');
  fa.type = 'text/css';
  fa.textContent = '@font-face { font-family: FontAwesome; src: url("' +
    chrome.extension.getURL('fonts/fontawesome-webfont.woff') +
  '"); }';
  document.head.appendChild(fa);

  var popUp = $('<div>', {
    class: 'kconnectPopup',
    style: 'display: none',
    id: layerId,
  }).append($('<div>', {
    class: 'honPopup-header',
  }).text(domain))
    .append($('<div>',{
      class: 'k-infos trustability',
    }).append($('<h4>').text(chrome.i18n.getMessage('trustabilityTitle')))
      .append($('<div>', {
        class: 'trustability-circle',
      }).append($('<span>'))))
    .append($('<div>',{
      class: 'k-infos readability',
    }).append($('<h4>').text(chrome.i18n.getMessage('readabilityTitle')))
      .append($('<div>', {
        class: 'readability-circle',
      }).append($('<span>'))))
    .append($('<div>',{
      class: 'honPopup-footer',
    }).append(
      $('<a>', {
        href: 'https://search.kconnect.eu/beta/extension',
        target: '_blank',
      }).text(chrome.i18n.getMessage('about'))
    ));

  $('body').append(popUp);

  var $layerId =  $('#' + layerId);

  var borderX = 10;
  if (event.pageX + 290 > document.body.getBoundingClientRect().right) {
    borderX = event.pageX + 300 - document.body.getBoundingClientRect().right;
  }

  $layerId.css('left', event.pageX - borderX);
  $layerId.css('top', event.pageY - 10);

  var timeoutId;
  var hideTimeoutId;
  // Add honLogo eventListener
  $logoId.hover(function() {
    if (!timeoutId) {
      timeoutId = window.setTimeout(function() {
        timeoutId = null;
        $layerId.show();
      }, 200);
    }
  }, function() {
    if (timeoutId) {
      window.clearTimeout(timeoutId);
      timeoutId = null;
    }
  });

  $layerId.hover(function() {
    if (hideTimeoutId) {
      window.clearTimeout(hideTimeoutId);
      hideTimeoutId = null;
    }
  }, function() {
    if (!hideTimeoutId) {
      hideTimeoutId = window.setTimeout(function() {
        hideTimeoutId = null;
        $layerId.hide();
      }, 200);
    }
  });

  var rProgress = new ProgressBar.Circle(
    document.getElementById(layerId).querySelector('.readability-circle'), {
    strokeWidth: 7,
    trailWidth: 7,
    trailColor: '#ddd',
  });
  $layerId.find('.readability')
    .find('span')
    .append(
      $('<i>', {
        class: 'fa fa-question',
        'aria-hidden': 'true',
      })
    );

  var tProgress = new ProgressBar.Circle(
    document.getElementById(layerId).querySelector('.trustability-circle'), {
    strokeWidth: 7,
    trailWidth: 7,
    trailColor: '#ddd',
  });
  $layerId.find('.trustability')
    .find('span')
    .append(
      $('<i>', {
        class: 'fa fa-question',
        'aria-hidden': 'true',
      })
    );

  $.when(trustabilityRequest)
    .then(function(trustabilityResponse) {
      trustabilityCallback(trustabilityResponse, $layerId, layerId, tProgress);
    });

  $.when(readabilityRequest)
    .then(function(readabilityResponse) {
      readabilityCallback(readabilityResponse, $layerId, layerId, rProgress);
    }, function(error) {
      readabilityCallback(error.responseJSON, $layerId, layerId, rProgress);
    });

  $layerId.show();
};

var updateLinks = function() {
  var deferred = new $.Deferred();

  // Get links
  var links = [];
  var hrefSelector = '';
  var timeToWait = 1;
  var trustabilityRequested = 0;
  // Match Google
  if (window.location.host.indexOf('google') > -1) {
    hrefSelector = 'h3.r>a';
  }

  // Match Yahoo
  else if (window.location.host.indexOf('yahoo') > -1) {
    hrefSelector = '#web div.compTitle h3.title>a';
    timeToWait = 1000;
  }

  // Match Bing
  else if (window.location.host.indexOf('bing') > -1) {
    hrefSelector = 'li.b_algo h2>a';
  }

  // Match DuckDuckGo
  else if (window.location.host.indexOf('duckduckgo') > -1) {
    hrefSelector = 'h2.result__title>a.result__a';
    timeToWait = 1000;
    $('head').unbind('DOMSubtreeModified', updateLinks);
    $('head').bind('DOMSubtreeModified', updateLinks);
  }

  setTimeout(function() {
    var nodeList = document.querySelectorAll(hrefSelector);
    for (var i = 0; i < nodeList.length; ++i) {
      links[i] = nodeList[i].href;
    }

    links.forEach(function(link, index) {
      var honLogo = $(nodeList.item(index)).parent();

      var logoId = 'kconnectLogo_' + index;

      var honCodeLogo = $('<div>', {
          target: '_blank',
          id: logoId,
          class: 'hon kconnectLogo',
        });

      if (honLogo.children('.kconnectLogo').length === 0) {
        // Normalize Search Engine parents' behaviors
        honLogo.parent().css('overflow','visible');
        honLogo.parent().css('position','relative');
        honLogo.prepend(honCodeLogo);

        // Add onClick listener
        $('#' + logoId).one('click', function(e) {
          requestKconnect(e, link);
        });
      }

      trustabilityRequested++;
      if (trustabilityRequested === links.length) {
        deferred.resolve();
      }
    });
  }, timeToWait);

  return deferred.promise();
};

updateLinks().done(function() {});
<commit_msg>fix selector<commit_after>'use strict';

var readabilityCallback = function(dataRdb, target, id, progress) {

  if (dataRdb.error || dataRdb.readability === undefined) {
    progress.destroy();
    progress =
      new ProgressBar.Circle(
        document.getElementById(id).querySelector('.readability-circle'), {
          strokeWidth: 7,
          trailWidth: 7,
          trailColor: '#ddd',
          color: 'black',
          easing: 'easeInOut',
          duration: 800,
        });

    target.find('.readability')
      .find('span')
      .html($('<i>', {
        class: 'fa fa-ban',
        'aria-hidden': 'true',
      }));

    progress.set(1);
    return;
  }

  var difficulty = dataRdb.readability.difficulty;

  if (target.find('.rdb').length === 0) {
    var readabilityColor = 'red';
    if (difficulty === 'average') {
      readabilityColor = 'orange';
    } else if (difficulty === 'easy') {
      readabilityColor = 'green';
    }
    progress.destroy();
    progress =
    new ProgressBar.Circle(
    document.getElementById(id).querySelector('.readability-circle'), {
      strokeWidth: 7,
      trailWidth: 7,
      trailColor: '#ddd',
      color: readabilityColor,
      easing: 'easeInOut',
      duration: 800,
    });

    target.find('.readability')
      .find('span')
      .html($('<i>', {
        class: 'fa fa-book',
        'aria-hidden': 'true',
      }));

    try {
      progress.animate(1);
    } catch (e) {
      progress.set(1);
    }

  }
};

var trustabilityCallback = function(data, target, id, progress) {
  if (data.error || data.trustability === undefined) {
    progress.destroy();
    progress =
      new ProgressBar.Circle(
        document.getElementById(id).querySelector('.trustability-circle'), {
          strokeWidth: 7,
          trailWidth: 7,
          trailColor: '#ddd',
          color: 'black',
          easing: 'easeInOut',
          duration: 800,
        });
    target.find('.trustability')
      .find('span')
      .html($('<i>', {
        class: 'fa fa-ban',
        'aria-hidden': 'true',
      }));
    progress.set(1);
    return;
  }

  var score = data.trustability.score;

  if (target.find('.trb').length === 0) {

    var trustabilityColor = 'red';
    if (score > 33 && score <= 66) {
      trustabilityColor = 'orange';
    } else if (score > 66) {
      trustabilityColor = 'green';
    }
    progress.destroy();
    progress =
      new ProgressBar.Circle(
        document.getElementById(id).querySelector('.trustability-circle'), {
          strokeWidth: 7,
          trailWidth: 7,
          trailColor: '#ddd',
          color: trustabilityColor,
          easing: 'easeInOut',
          duration: 800,
        });
    target.find('.trustability')
      .find('span')
      .html($('<i>', {
        class: 'fa fa-stethoscope',
        'aria-hidden': 'true',
      }));
    if (score === 0) {
      score = 100;
    }
    try {
      progress.animate(score / 100);
    } catch (e) {
      progress.set(score / 100);
    }
  }
};

var requestKconnect = function(event, link) {
  // Prevent Duckduckgo onclick redirect
  event.stopPropagation();

  var domain = kconnect.getDomainFromUrl(link);
  var trustabilityRequest = kconnect.getIsTrustable(domain);
  var readabilityRequest = kconnect.getReadability(link);
  var layerId = 'layer' + event.target.id;
  var $logoId =  $(event.target);

  var fa = document.createElement('style');
  fa.type = 'text/css';
  fa.textContent = '@font-face { font-family: FontAwesome; src: url("' +
    chrome.extension.getURL('fonts/fontawesome-webfont.woff') +
  '"); }';
  document.head.appendChild(fa);

  var popUp = $('<div>', {
    class: 'kconnectPopup',
    style: 'display: none',
    id: layerId,
  }).append($('<div>', {
    class: 'honPopup-header',
  }).text(domain))
    .append($('<div>',{
      class: 'k-infos trustability',
    }).append($('<h4>').text(chrome.i18n.getMessage('trustabilityTitle')))
      .append($('<div>', {
        class: 'trustability-circle',
      }).append($('<span>'))))
    .append($('<div>',{
      class: 'k-infos readability',
    }).append($('<h4>').text(chrome.i18n.getMessage('readabilityTitle')))
      .append($('<div>', {
        class: 'readability-circle',
      }).append($('<span>'))))
    .append($('<div>',{
      class: 'honPopup-footer',
    }).append(
      $('<a>', {
        href: 'https://search.kconnect.eu/beta/extension',
        target: '_blank',
      }).text(chrome.i18n.getMessage('about'))
    ));

  $('body').append(popUp);

  var $layerId =  $('#' + layerId);

  var borderX = 10;
  if (event.pageX + 290 > document.body.getBoundingClientRect().right) {
    borderX = event.pageX + 300 - document.body.getBoundingClientRect().right;
  }

  $layerId.css('left', event.pageX - borderX);
  $layerId.css('top', event.pageY - 10);

  var timeoutId;
  var hideTimeoutId;
  // Add honLogo eventListener
  $logoId.hover(function() {
    if (!timeoutId) {
      timeoutId = window.setTimeout(function() {
        timeoutId = null;
        $layerId.show();
      }, 200);
    }
  }, function() {
    if (timeoutId) {
      window.clearTimeout(timeoutId);
      timeoutId = null;
    }
  });

  $layerId.hover(function() {
    if (hideTimeoutId) {
      window.clearTimeout(hideTimeoutId);
      hideTimeoutId = null;
    }
  }, function() {
    if (!hideTimeoutId) {
      hideTimeoutId = window.setTimeout(function() {
        hideTimeoutId = null;
        $layerId.hide();
      }, 200);
    }
  });

  var rProgress = new ProgressBar.Circle(
    document.getElementById(layerId).querySelector('.readability-circle'), {
    strokeWidth: 7,
    trailWidth: 7,
    trailColor: '#ddd',
  });
  $layerId.find('.readability')
    .find('span')
    .append(
      $('<i>', {
        class: 'fa fa-question',
        'aria-hidden': 'true',
      })
    );

  var tProgress = new ProgressBar.Circle(
    document.getElementById(layerId).querySelector('.trustability-circle'), {
    strokeWidth: 7,
    trailWidth: 7,
    trailColor: '#ddd',
  });
  $layerId.find('.trustability')
    .find('span')
    .append(
      $('<i>', {
        class: 'fa fa-question',
        'aria-hidden': 'true',
      })
    );

  $.when(trustabilityRequest)
    .then(function(trustabilityResponse) {
      trustabilityCallback(trustabilityResponse, $layerId, layerId, tProgress);
    });

  $.when(readabilityRequest)
    .then(function(readabilityResponse) {
      readabilityCallback(readabilityResponse, $layerId, layerId, rProgress);
    }, function(error) {
      readabilityCallback(error.responseJSON, $layerId, layerId, rProgress);
    });

  $layerId.show();
};

var updateLinks = function() {
  var deferred = new $.Deferred();

  // Get links
  var links = [];
  var hrefSelector = '';
  var timeToWait = 1;
  var trustabilityRequested = 0;
  // Match Google
  if (window.location.host.indexOf('google') > -1) {
    hrefSelector = 'h3.r>a';
  }

  // Match Yahoo
  else if (window.location.host.indexOf('yahoo') > -1) {
    hrefSelector = '#web div.compTitle h3.title>a';
    timeToWait = 1000;
  }

  // Match Bing
  else if (window.location.host.indexOf('bing') > -1) {
    hrefSelector = 'li.b_algo h2>a';
  }

  // Match DuckDuckGo
  else if (window.location.host.indexOf('duckduckgo') > -1) {
    hrefSelector = '#links h2.result__title>a.result__a';
    timeToWait = 1000;
    $('head').unbind('DOMSubtreeModified', updateLinks);
    $('head').bind('DOMSubtreeModified', updateLinks);
  }

  setTimeout(function() {
    var nodeList = document.querySelectorAll(hrefSelector);
    for (var i = 0; i < nodeList.length; ++i) {
      links[i] = nodeList[i].href;
    }

    links.forEach(function(link, index) {
      var honLogo = $(nodeList.item(index)).parent();

      var logoId = 'kconnectLogo_' + index;

      var honCodeLogo = $('<div>', {
          target: '_blank',
          id: logoId,
          class: 'hon kconnectLogo',
        });

      if (honLogo.children('.kconnectLogo').length === 0) {
        // Normalize Search Engine parents' behaviors
        honLogo.parent().css('overflow','visible');
        honLogo.parent().css('position','relative');
        honLogo.prepend(honCodeLogo);

        // Add onClick listener
        $('#' + logoId).one('click', function(e) {
          requestKconnect(e, link);
        });
      }

      trustabilityRequested++;
      if (trustabilityRequested === links.length) {
        deferred.resolve();
      }
    });
  }, timeToWait);

  return deferred.promise();
};

updateLinks().done(function() {});
<|endoftext|>