<commit_before>jsio('from common.javascript import Class, bind');
jsio('from net.protocols.rtjp import RTJPProtocol');

var logger = logging.getLogger(jsio.__path);

exports = Class(RTJPProtocol, function(supr) {

	this._log = function() {
		var args = Array.prototype.slice.call(arguments);
		args.unshift(this.transport._socket._session.key);
		logger.log.apply(this, args);
	}

	this.connectionMade = function() {
		this._log('connectionMade');
		this._itemSubscriptionIds = {};
		this.sendFrame('DEMAND_AUTHENTICATION');
	}

	this.sendFrame = function(name, args) {
		this._log('sendFrame', name, JSON.stringify(args));
		supr(this, 'sendFrame', arguments);
	}

	this.frameReceived = function(id, name, args) {
		this._log('frameReceived', id, name, JSON.stringify(args));

		switch(name) {
			case 'ITEM_SUBSCRIBE':
				this._log('subscribing to item', args.id);
				this.server.getItem(args.id, bind(this, function(item){
					var subId = this.server.subscribeToItemMutations(item, bind(this, 'onItemMutated'));
					this._itemSubscriptionIds[args.id] = subId;
					this.sendFrame('ITEM_SNAPSHOT', item.asObject());
				}));
				break;
			case 'ITEM_MUTATING':
				this.server.handleMutation(args.mutation);
				break;
			case 'LABEL_GET_LIST':
				this.server.getLabelList(args.label, bind(this, function(list) {
					this.sendFrame('LABEL_LIST', { label: args.label, list: list });
				}));
				break;
			case 'REQUEST_CREATE_ITEM':
				this.server.createItem(args.type, bind(this, function(item){
					this.sendFrame('ITEM_CREATED', item.asObject());
				}))
				break;
			case 'REQUEST_CREATE_LABEL':
				this.server.createLabel(args.label, args.map, args.filter, bind(this, function(label) {
					this.sendFrame('LABELS', { labels: [label] });
				}))
				break;
			case 'AUTHENTICATE':
				this.server.authenticate(args.email, args.password, bind(this, function(userLabels, errorMessage) {
					if (!userLabels) {
						this.sendFrame('DEMAND_AUTHENTICATION', { message: errorMessage });
						return;
					}
					this._authenticatedEmail = args.email;
					this.sendFrame('WELCOME');
					this.sendFrame('LABELS', { labels: userLabels });
				}));
				break;
			default:
				logger.warn('Unknown frame type received', id, name, JSON.stringify(args));
				break;
		}
	}

	this.onItemMutated = function(mutation) {
		this.sendFrame('ITEM_MUTATED', { mutation: mutation });
	}

	this.connectionLost = function() {
		logger.log('connection lost - unsubscribing from item mutation subscriptions');
		for (var itemId in this._itemSubscriptionIds) {
			this.server.unsubscribeFromItemMutations(itemId, this._itemSubscriptionIds[itemId]);
		}
	}
})
<commit_msg>Check that the user is authenticated for all frames except AUTHENTICATE<commit_after>jsio('from common.javascript import Class, bind');
jsio('from net.protocols.rtjp import RTJPProtocol');

var logger = logging.getLogger(jsio.__path);

exports = Class(RTJPProtocol, function(supr) {

	this._log = function() {
		var args = Array.prototype.slice.call(arguments);
		args.unshift(this.transport._socket._session.key);
		logger.log.apply(this, args);
	}

	this.connectionMade = function() {
		this._log('connectionMade');
		this._itemSubscriptionIds = {};
		this.sendFrame('DEMAND_AUTHENTICATION');
	}

	this.sendFrame = function(name, args) {
		this._log('sendFrame', name, JSON.stringify(args));
		supr(this, 'sendFrame', arguments);
	}

	this.frameReceived = function(id, name, args) {
		this._log('frameReceived', id, name, JSON.stringify(args));

		if (!this.isAuthenticated() && name != 'AUTHENTICATE') {
			this.sendFrame('DEMAND_AUTHENTICATION', { frame: { id: id, name: name, args: args } });
			return;
		}

		switch(name) {
			case 'AUTHENTICATE':
				this.server.authenticate(args.email, args.password, bind(this, function(userLabels, errorMessage) {
					if (!userLabels) {
						this.sendFrame('DEMAND_AUTHENTICATION', { message: errorMessage });
						return;
					}
					this._authenticatedEmail = args.email;
					this.sendFrame('WELCOME');
					this.sendFrame('LABELS', { labels: userLabels });
				}));
				break;
			case 'ITEM_SUBSCRIBE':
				this._log('subscribing to item', args.id);
				this.server.getItem(args.id, bind(this, function(item){
					var subId = this.server.subscribeToItemMutations(item, bind(this, 'onItemMutated'));
					this._itemSubscriptionIds[args.id] = subId;
					this.sendFrame('ITEM_SNAPSHOT', item.asObject());
				}));
				break;
			case 'ITEM_MUTATING':
				this.server.handleMutation(args.mutation);
				break;
			case 'LABEL_GET_LIST':
				this.server.getLabelList(args.label, bind(this, function(list) {
					this.sendFrame('LABEL_LIST', { label: args.label, list: list });
				}));
				break;
			case 'REQUEST_CREATE_ITEM':
				this.server.createItem(args.type, bind(this, function(item){
					this.sendFrame('ITEM_CREATED', item.asObject());
				}))
				break;
			case 'REQUEST_CREATE_LABEL':
				this.server.createLabel(args.label, args.map, args.filter, bind(this, function(label) {
					this.sendFrame('LABELS', { labels: [label] });
				}))
				break;
			default:
				logger.warn('Unknown frame type received', id, name, JSON.stringify(args));
				break;
		}
	}

	this.isAuthenticated = function() {
		return !!this._authenticatedEmail;
	}

	this.onItemMutated = function(mutation) {
		this.sendFrame('ITEM_MUTATED', { mutation: mutation });
	}

	this.connectionLost = function() {
		logger.log('connection lost - unsubscribing from item mutation subscriptions');
		delete this._authenticatedEmail;
		for (var itemId in this._itemSubscriptionIds) {
			this.server.unsubscribeFromItemMutations(itemId, this._itemSubscriptionIds[itemId]);
		}
	}
})
<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Shows the home screen for a multi-tab simulation, which lets the user see all of the tabs and select one.
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  var PhetButton = require( 'JOIST/PhetButton' );
  var Node = require( 'SCENERY/nodes/Node' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var VBox = require( 'SCENERY/nodes/VBox' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Shape = require( 'KITE/Shape' );
  var inherit = require( 'PHET_CORE/inherit' );
  var TabView = require( 'JOIST/TabView' );
  var Frame = require( 'JOIST/Frame' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Highlight = require( 'JOIST/Highlight' );
  var Property = require( 'AXON/Property' );
  var ButtonListener = require( 'SCENERY/input/ButtonListener' );
  var FullScreenButton = require( 'JOIST/FullScreenButton' );

  var HEIGHT = 70;

  function HomeScreen( sim ) {
    var homeScreen = this;

    //Rendering in SVG seems to solve the problem that the home screen consumes 100% disk and crashes, see https://github.com/phetsims/joist/issues/17
    //Also makes it more responsive (and crisper on retina displays)
    TabView.call( this, {renderer: 'svg'} );

    this.backgroundColor = 'black';

    //iPad doesn't support Century Gothic, so fall back to Futura, see http://wordpress.org/support/topic/font-not-working-on-ipad-browser
    var title = new Text( sim.name, {fontSize: 52, fontFamily: '"Century Gothic", "Futura", sans-serif', fill: 'white', y: 110, centerX: this.layoutBounds.width / 2} );
    this.addChild( title );

    //Keep track of which tab is highlighted so the same tab can remain highlighted even if nodes are replaced (say when one grows larger or smaller)
    var highlightedIndex = new Property( -1 );

    var tabChildren = _.map( sim.tabs, function( tab ) {
      var index = sim.tabs.indexOf( tab );
      var largeIcon = new Node( {children: [tab.icon], scale: HEIGHT / tab.icon.height * 2} );
      var largeIconWithFrame = new Node( {children: [new Frame( largeIcon ), largeIcon]} );
      var large = new VBox( {cursor: 'pointer', children: [
        largeIconWithFrame,
        new Text( tab.name, {fontSize: 42, fill: 'yellow'} )
      ]} );

      //TODO: Switch to buttonListener, but make sure you test it because on 7/17/2013 there is a problem where ButtonListener won't fire if a node has appeared under the pointer
      large.addInputListener( {
        down: function() {
          sim.simModel.showHomeScreen = false;
          highlightedIndex.value = -1;
        }
      } );

      var small = new VBox( {spacing: 3, cursor: 'pointer', children: [
        new Node( {opacity: 0.5, children: [tab.icon], scale: sim.tabs.length === 4 ? HEIGHT / tab.icon.height :
                                                              sim.tabs.length === 3 ? 1.25 * HEIGHT / tab.icon.height :
                                                              sim.tabs.length === 2 ? 1.75 * HEIGHT / tab.icon.height :
                                                              HEIGHT / tab.icon.height} ),
        new Text( tab.name, {fontSize: 18, fill: 'gray'} )
      ]} );
      small.mouseArea = small.touchArea = Shape.bounds( small.bounds ); //cover the gap in the vbox
      small.addInputListener( {
        down: function() { sim.simModel.tabIndex = index; },

        //On the home screen if you touch an inactive tab thumbnail, it grows.  If then without lifting your finger you swipe over
        // to the next thumbnail, that one would grow.
        over: function( event ) {
          if ( !event.pointer.isMouse ) {
            sim.simModel.tabIndex = index;
          }
        }
      } );

      var smallHighlight = Highlight.createHighlight( small.width + 6, small.height );
      smallHighlight.centerX = small.centerX;
      small.addChild( smallHighlight );
      var highlightListener = {
        over: function( event ) {
          if ( event.pointer.isMouse ) {
            highlightedIndex.value = index;
          }
        },
        out: function( event ) { if ( event.pointer.isMouse ) {highlightedIndex.value = -1;} }
      };
      highlightedIndex.valueEquals( index ).linkAttribute( smallHighlight, 'visible' );
      small.addInputListener( highlightListener );

      var largeHighlight = Highlight.createHighlight( large.width + 8, large.height );
      largeHighlight.centerX = large.centerX;
      highlightedIndex.valueEquals( index ).linkAttribute( largeHighlight, 'visible' );
      large.addChild( largeHighlight );
      large.addInputListener( highlightListener );
      large.mouseArea = large.touchArea = Shape.bounds( large.bounds ); //cover the gap in the vbox

      //TODO: Add accessibility peers
      //      tabChild.addPeer( '<input type="button" aria-label="' + tabChild.tab.name + '">', {click: function() {
//        var tab = tabChild.tab;
//        if ( sim.simModel.tabIndex === tab.index ) {
//          sim.simModel.showHomeScreen = false;
//        }
//        else {
//          sim.simModel.tabIndex = tab.index;
//        }
//      }} );
//    } );

      return {tab: tab, small: small, large: large, index: index};
    } );

    var center = new Node( {y: 170} );
    homeScreen.addChild( center );
    sim.simModel.tabIndexProperty.link( function( tabIndex ) {

      //Space the icons out more if there are fewer, so they will be spaced nicely
      //Cannot have only 1 tab because for 1-tab sims there is no home screen.
      var spacing = sim.tabs.length === 2 ? 60 :
                    sim.tabs.length === 3 ? 60 :
                    33;

      var icons = _.map( tabChildren, function( tabChild ) {return tabChild.index === tabIndex ? tabChild.large : tabChild.small;} );
      center.children = [new HBox( {spacing: spacing, children: icons, align: 'top'} )];
      center.centerX = homeScreen.layoutBounds.width / 2;
    } );

    //Only show the full screen button on supported platforms
    var deviceIsAndroid = navigator.userAgent.indexOf( 'Android' ) > 0;
    var deviceIsIOS = /iP(ad|hone|od)/.test( navigator.userAgent );

    function isIE( v ) {
      var r = new RegExp( 'msie' + (!isNaN( v ) ? ('\\s' + v) : ''), 'i' );
      return r.test( navigator.userAgent );
    }

    var ie = isIE( 9 ) || isIE( 10 );//might work on IE11

    var showFullScreenButton = !deviceIsAndroid && !deviceIsIOS && !ie;
    if ( showFullScreenButton && false ) {
      var fullScreenButton = new FullScreenButton();
      var phetButton = new PhetButton( sim );
      this.addChild( new HBox( {spacing: 10, children: [fullScreenButton, phetButton], right: this.layoutBounds.maxX - 5, bottom: this.layoutBounds.maxY - 5} ) );
    }
    else {
      this.addChild( new PhetButton( sim, {phetLogo: 'phet-logo.svg', phetLogoScale: 0.55} ).mutate( {right: this.layoutBounds.maxX - 5, bottom: this.layoutBounds.maxY - 5} ) );
    }
  }

  return inherit( TabView, HomeScreen );
} );<commit_msg>Changed to short logo for home screen, and reduced its size<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Shows the home screen for a multi-tab simulation, which lets the user see all of the tabs and select one.
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  var PhetButton = require( 'JOIST/PhetButton' );
  var Node = require( 'SCENERY/nodes/Node' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var VBox = require( 'SCENERY/nodes/VBox' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Shape = require( 'KITE/Shape' );
  var inherit = require( 'PHET_CORE/inherit' );
  var TabView = require( 'JOIST/TabView' );
  var Frame = require( 'JOIST/Frame' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Highlight = require( 'JOIST/Highlight' );
  var Property = require( 'AXON/Property' );
  var ButtonListener = require( 'SCENERY/input/ButtonListener' );
  var FullScreenButton = require( 'JOIST/FullScreenButton' );

  var HEIGHT = 70;

  function HomeScreen( sim ) {
    var homeScreen = this;

    //Rendering in SVG seems to solve the problem that the home screen consumes 100% disk and crashes, see https://github.com/phetsims/joist/issues/17
    //Also makes it more responsive (and crisper on retina displays)
    TabView.call( this, {renderer: 'svg'} );

    this.backgroundColor = 'black';

    //iPad doesn't support Century Gothic, so fall back to Futura, see http://wordpress.org/support/topic/font-not-working-on-ipad-browser
    var title = new Text( sim.name, {fontSize: 52, fontFamily: '"Century Gothic", "Futura", sans-serif', fill: 'white', y: 110, centerX: this.layoutBounds.width / 2} );
    this.addChild( title );

    //Keep track of which tab is highlighted so the same tab can remain highlighted even if nodes are replaced (say when one grows larger or smaller)
    var highlightedIndex = new Property( -1 );

    var tabChildren = _.map( sim.tabs, function( tab ) {
      var index = sim.tabs.indexOf( tab );
      var largeIcon = new Node( {children: [tab.icon], scale: HEIGHT / tab.icon.height * 2} );
      var largeIconWithFrame = new Node( {children: [new Frame( largeIcon ), largeIcon]} );
      var large = new VBox( {cursor: 'pointer', children: [
        largeIconWithFrame,
        new Text( tab.name, {fontSize: 42, fill: 'yellow'} )
      ]} );

      //TODO: Switch to buttonListener, but make sure you test it because on 7/17/2013 there is a problem where ButtonListener won't fire if a node has appeared under the pointer
      large.addInputListener( {
        down: function() {
          sim.simModel.showHomeScreen = false;
          highlightedIndex.value = -1;
        }
      } );

      var small = new VBox( {spacing: 3, cursor: 'pointer', children: [
        new Node( {opacity: 0.5, children: [tab.icon], scale: sim.tabs.length === 4 ? HEIGHT / tab.icon.height :
                                                              sim.tabs.length === 3 ? 1.25 * HEIGHT / tab.icon.height :
                                                              sim.tabs.length === 2 ? 1.75 * HEIGHT / tab.icon.height :
                                                              HEIGHT / tab.icon.height} ),
        new Text( tab.name, {fontSize: 18, fill: 'gray'} )
      ]} );
      small.mouseArea = small.touchArea = Shape.bounds( small.bounds ); //cover the gap in the vbox
      small.addInputListener( {
        down: function() { sim.simModel.tabIndex = index; },

        //On the home screen if you touch an inactive tab thumbnail, it grows.  If then without lifting your finger you swipe over
        // to the next thumbnail, that one would grow.
        over: function( event ) {
          if ( !event.pointer.isMouse ) {
            sim.simModel.tabIndex = index;
          }
        }
      } );

      var smallHighlight = Highlight.createHighlight( small.width + 6, small.height );
      smallHighlight.centerX = small.centerX;
      small.addChild( smallHighlight );
      var highlightListener = {
        over: function( event ) {
          if ( event.pointer.isMouse ) {
            highlightedIndex.value = index;
          }
        },
        out: function( event ) { if ( event.pointer.isMouse ) {highlightedIndex.value = -1;} }
      };
      highlightedIndex.valueEquals( index ).linkAttribute( smallHighlight, 'visible' );
      small.addInputListener( highlightListener );

      var largeHighlight = Highlight.createHighlight( large.width + 8, large.height );
      largeHighlight.centerX = large.centerX;
      highlightedIndex.valueEquals( index ).linkAttribute( largeHighlight, 'visible' );
      large.addChild( largeHighlight );
      large.addInputListener( highlightListener );
      large.mouseArea = large.touchArea = Shape.bounds( large.bounds ); //cover the gap in the vbox

      //TODO: Add accessibility peers
      //      tabChild.addPeer( '<input type="button" aria-label="' + tabChild.tab.name + '">', {click: function() {
//        var tab = tabChild.tab;
//        if ( sim.simModel.tabIndex === tab.index ) {
//          sim.simModel.showHomeScreen = false;
//        }
//        else {
//          sim.simModel.tabIndex = tab.index;
//        }
//      }} );
//    } );

      return {tab: tab, small: small, large: large, index: index};
    } );

    var center = new Node( {y: 170} );
    homeScreen.addChild( center );
    sim.simModel.tabIndexProperty.link( function( tabIndex ) {

      //Space the icons out more if there are fewer, so they will be spaced nicely
      //Cannot have only 1 tab because for 1-tab sims there is no home screen.
      var spacing = sim.tabs.length === 2 ? 60 :
                    sim.tabs.length === 3 ? 60 :
                    33;

      var icons = _.map( tabChildren, function( tabChild ) {return tabChild.index === tabIndex ? tabChild.large : tabChild.small;} );
      center.children = [new HBox( {spacing: spacing, children: icons, align: 'top'} )];
      center.centerX = homeScreen.layoutBounds.width / 2;
    } );

    //Only show the full screen button on supported platforms
    var deviceIsAndroid = navigator.userAgent.indexOf( 'Android' ) > 0;
    var deviceIsIOS = /iP(ad|hone|od)/.test( navigator.userAgent );

    function isIE( v ) {
      var r = new RegExp( 'msie' + (!isNaN( v ) ? ('\\s' + v) : ''), 'i' );
      return r.test( navigator.userAgent );
    }

    var ie = isIE( 9 ) || isIE( 10 );//might work on IE11

    var showFullScreenButton = !deviceIsAndroid && !deviceIsIOS && !ie;
    if ( showFullScreenButton && false ) {
      var fullScreenButton = new FullScreenButton();
      var phetButton = new PhetButton( sim );
      this.addChild( new HBox( {spacing: 10, children: [fullScreenButton, phetButton], right: this.layoutBounds.maxX - 5, bottom: this.layoutBounds.maxY - 5} ) );
    }
    else {
      this.addChild( new PhetButton( sim, {phetLogo: 'phet-logo-short.svg', phetLogoScale: 0.4} ).mutate( {right: this.layoutBounds.maxX - 5, bottom: this.layoutBounds.maxY - 5} ) );
    }
  }

  return inherit( TabView, HomeScreen );
} );<|endoftext|>


<commit_before>'use strict';

var expect = require('expect.js'),
	sinon = require('sinon'),
	helpers = require('./helpers'),
	_ = require('underscore');

describe('Distributor main', function() {
	var distributor,
		projects = [{name: 'project1'}];

	var expectUpdateBuild = function(distributor, build, number, conditionsHash) {
		var conditions = conditionsHash[number];
		expect(distributor.queue).length(conditions.queue.length);
		expect(build.status).equal(conditions.build.status);
		if (build.status === 'error') {
			expect(build.error.message).eql(conditions.build.error.message);
		}
	};

	describe('with success project', function() {
		var updateBuildSpy;

		it('instance should be created without errors', function() {
			distributor = helpers.createDistributor({
				projects: projects,
				nodes: [{type: 'local', maxExecutorsCount: 1}]
			});
			updateBuildSpy = sinon.spy(distributor, '_updateBuild');
		});

		it('should run without errors', function(done) {
			distributor.run({projectName: 'project1'}, function(err) {
				expect(err).not.ok();
				done();
			});
		});

		it('build should be queued', function() {
			var changes = updateBuildSpy.getCall(0).args[1];
			expect(changes).only.have.keys(
				'project', 'initiator', 'params', 'createDate', 'status',
				'completed'
			);
			expect(changes.status).equal('queued');
			expect(changes.completed).equal(false);
		});

		it('build should be in-progress', function() {
			var changes = updateBuildSpy.getCall(1).args[1];
			expect(changes).only.have.keys(
				'startDate', 'status', 'waitReason', 'node'
			);
			expect(changes.status).equal('in-progress');
			expect(changes.waitReason).equal('');
			expect(changes.node).eql(_(distributor.nodes[0]).pick('type', 'name'));
		});

		it('build should be done', function() {
			var changes = updateBuildSpy.getCall(2).args[1];
			expect(changes).only.have.keys(
				'endDate', 'status', 'completed', 'error'
			);
			expect(changes.status).equal('done');
			expect(changes.completed).equal(true);
			expect(changes.error).equal(null);
		});

		it('update build called 3 times in total', function() {
			expect(updateBuildSpy.callCount).equal(3);
		});
	});

	describe('with fail project', function() {
		var updateBuildSpy;

		it('instance should be created without errors', function() {
			distributor = helpers.createDistributor({
				projects: projects,
				nodes: [{type: 'local', maxExecutorsCount: 1}],
				executorRun: sinon.stub().callsArgWithAsync(
					0,
					new Error('Some error')
				)
			});
			updateBuildSpy = sinon.spy(distributor, '_updateBuild');
		});

		it('should run without errors', function(done) {
			distributor.run({projectName: 'project1'}, function(err) {
				expect(err).not.ok();
				done();
			});
		});

		it('build should be queued', function() {
			var changes = updateBuildSpy.getCall(0).args[1];
			expect(changes.status).equal('queued');
		});

		it('build should be in-progress', function() {
			var changes = updateBuildSpy.getCall(1).args[1];
			expect(changes.status).equal('in-progress');
		});

		it('build should be fail', function() {
			var changes = updateBuildSpy.getCall(2).args[1];
			expect(changes.status).equal('error');
			expect(changes.completed).equal(true);
			expect(changes.error.message).equal('Some error');
		});

		it('update build called 3 times in total', function() {
			expect(updateBuildSpy.callCount).equal(3);
		});
	});

	describe('with success project and build cancel', function() {
		var distributorParams = {
			projects: projects,
			nodes: [{type: 'local', maxExecutorsCount: 1}],
			saveBuild: function(build, callback) {
				build.id = 1;
				callback(null, build);
			}
		};

		describe('when cancel queued bulid', function() {
			var updateBuildSpy;

			var cancelError;
			it('instance should be created without errors', function() {
				distributor = helpers.createDistributor(distributorParams);

				var originalRunNext = distributor._runNext;
				distributor._runNext = function() {
					distributor.cancel(1, function(err) {
						cancelError = err;
					});
					originalRunNext.apply(distributor, arguments);
				};

				updateBuildSpy = sinon.spy(distributor, '_updateBuild');
			});

			it('should run without errors', function(done) {
				distributor.run({projectName: 'project1'}, function(err) {
					expect(err).not.ok();
					done();
				});
			});

			it('build should be queued', function() {
				var changes = updateBuildSpy.getCall(0).args[1];
				expect(changes).only.have.keys(
					'project', 'initiator', 'params', 'createDate', 'status',
					'completed'
				);
				expect(changes.status).equal('queued');
				expect(changes.completed).equal(false);
			});

			it('should be cancelled without error', function() {
				expect(cancelError).not.ok();
			});

			it('update build called only once', function() {
				expect(updateBuildSpy.callCount).equal(1);
			});
		});

		describe('when try to cancel unexisted build', function() {
			var cancelError;

			it('instance should be created without errors', function() {
				distributor = helpers.createDistributor(distributorParams);

				var originalRunNext = distributor._runNext;
				distributor._runNext = function() {
					distributor.cancel(2, function(err) {
						cancelError = err;
					});
					originalRunNext.apply(distributor, arguments);
				};
			});

			it('should run without errors', function(done) {
				distributor.run({projectName: 'project1'}, function(err) {
					expect(err).not.ok();
					done();
				});
			});

			it('should be cancelled with error (build not found)', function() {
				expect(cancelError).ok();
				expect(cancelError.message).eql(
					'Build with id "2" not found for cancel'
				);
			});
		});
	});

	describe('with success project and buildParams.scmRev', function() {
		var project1 = {
				name: 'project1',
				scm: {type: 'mercurial', rev: '1'}
			},
			distributorParams = {
				projects: [project1],
				nodes: [{type: 'local', maxExecutorsCount: 1}]
			};

		describe('when buildParams.scmRev is not set', function() {
			var updateBuildSpy;

			it('instance should be created without errors', function() {
				distributor = helpers.createDistributor(distributorParams);
				updateBuildSpy = sinon.spy(distributor, '_updateBuild');
			});

			it('should run without errors', function(done) {
				distributor.run({projectName: 'project1'}, function(err) {
					expect(err).not.ok();
					done();
				});
			});

			it('build should be queued with proper params', function() {
				var changes = updateBuildSpy.getCall(0).args[1];
				expect(changes.params).eql({});
				expect(changes.project).eql(project1);
			});
		});

		describe('when buildParams.scmRev is set', function() {
			var updateBuildSpy,
				buildParams = {scmRev: '2'};

			it('instance should be created without errors', function() {
				distributor = helpers.createDistributor(distributorParams);
				updateBuildSpy = sinon.spy(distributor, '_updateBuild');
			});

			it('should run without errors', function(done) {
				distributor.run({
					projectName: 'project1',
					buildParams: buildParams
				}, function(err) {
					expect(err).not.ok();
					done();
				});
			});

			it('build should be queued with proper params', function() {
				var changes = updateBuildSpy.getCall(0).args[1];
				expect(changes.params).eql(buildParams);
				expect(changes.project).eql(
					_({}).extend(
						project1,
						{scm: _({}).extend(project1.scm, {rev: buildParams.scmRev})}
					)
				);
			});
		});
	});

	describe('with success project and build with env', function() {
		var updateBuildSpy;

		it('instance should be created without errors', function() {
			distributor = helpers.createDistributor({
				projects: projects,
				nodes: [{type: 'local', maxExecutorsCount: 1, envs: ['env1']}]
			});
			updateBuildSpy = sinon.spy(distributor, '_updateBuild');
		});

		it('should run without errors', function(done) {
			distributor.run({projectName: 'project1', env: 'env1'}, function(err) {
				expect(err).not.ok();
				done();
			});
		});

		it('build should be queued with env', function() {
			var changes = updateBuildSpy.getCall(0).args[1];
			expect(changes).have.keys('status', 'env');
			expect(changes.status).equal('queued');
			expect(changes.env).eql({name: 'env1'});
		});

		it('build should be in-progress', function() {
			var changes = updateBuildSpy.getCall(1).args[1];
			expect(changes).have.keys('status');
			expect(changes.status).equal('in-progress');
		});

		it('build should be done', function() {
			var changes = updateBuildSpy.getCall(2).args[1];
			expect(changes).have.keys('status');
			expect(changes.status).equal('done');
		});

		it('update build called 3 times in total', function() {
			expect(updateBuildSpy.callCount).equal(3);
		});
	});

	describe('with success project and two project envs', function() {
		var updateBuildSpy;

		it('instance should be created without errors', function() {
			var envs = ['env1', 'env2'];
			distributor = helpers.createDistributor({
				projects: [{name: 'project1', envs: envs}],
				nodes: [{type: 'local', maxExecutorsCount: 1, envs: envs}]
			});
			updateBuildSpy = sinon.spy(distributor, '_updateBuild');
		});

		it('should run without errors', function(done) {
			distributor.run({projectName: 'project1'}, function(err) {
				expect(err).not.ok();
				done();
			});
		});

		it('build for first env should be queued', function() {
			var args = updateBuildSpy.getCall(0).args,
				build = args[0],
				changes = args[1];
			expect(build.env).eql({name: 'env1'});
			expect(changes).have.keys('status');
			expect(changes.status).equal('queued');
		});

		it('build for second env should be queued', function() {
			var args = updateBuildSpy.getCall(1).args,
				build = args[0],
				changes = args[1];
			expect(build.env).eql({name: 'env2'});
			expect(changes).have.keys('status');
			expect(changes.status).equal('queued');
		});

		it('build for first env should be in-progress', function() {
			var args = updateBuildSpy.getCall(2).args,
				build = args[0],
				changes = args[1];
			expect(build.env).eql({name: 'env1'});
			expect(changes).have.keys('status');
			expect(changes.status).equal('in-progress');
		});

		it('build for first should be done', function() {
			var args = updateBuildSpy.getCall(3).args,
				build = args[0],
				changes = args[1];
			expect(build.env).eql({name: 'env1'});
			expect(changes).have.keys('status');
			expect(changes.status).equal('done');
		});

		it('build for second env should be in-progress', function() {
			var args = updateBuildSpy.getCall(4).args,
				build = args[0],
				changes = args[1];
			expect(build.env).eql({name: 'env2'});
			expect(changes).have.keys('status');
			expect(changes.status).equal('in-progress');
		});

		it('build for second should be done', function() {
			var args = updateBuildSpy.getCall(5).args,
				build = args[0],
				changes = args[1];
			expect(build.env).eql({name: 'env2'});
			expect(changes).have.keys('status');
			expect(changes.status).equal('done');
		});

		it('update build called 6 times in total', function() {
			expect(updateBuildSpy.callCount).equal(6);
		});
	});

});
<commit_msg>remove unused expectUpdateBuild function from tests<commit_after>'use strict';

var expect = require('expect.js'),
	sinon = require('sinon'),
	helpers = require('./helpers'),
	_ = require('underscore');

describe('Distributor main', function() {
	var distributor,
		projects = [{name: 'project1'}];

	describe('with success project', function() {
		var updateBuildSpy;

		it('instance should be created without errors', function() {
			distributor = helpers.createDistributor({
				projects: projects,
				nodes: [{type: 'local', maxExecutorsCount: 1}]
			});
			updateBuildSpy = sinon.spy(distributor, '_updateBuild');
		});

		it('should run without errors', function(done) {
			distributor.run({projectName: 'project1'}, function(err) {
				expect(err).not.ok();
				done();
			});
		});

		it('build should be queued', function() {
			var changes = updateBuildSpy.getCall(0).args[1];
			expect(changes).only.have.keys(
				'project', 'initiator', 'params', 'createDate', 'status',
				'completed'
			);
			expect(changes.status).equal('queued');
			expect(changes.completed).equal(false);
		});

		it('build should be in-progress', function() {
			var changes = updateBuildSpy.getCall(1).args[1];
			expect(changes).only.have.keys(
				'startDate', 'status', 'waitReason', 'node'
			);
			expect(changes.status).equal('in-progress');
			expect(changes.waitReason).equal('');
			expect(changes.node).eql(_(distributor.nodes[0]).pick('type', 'name'));
		});

		it('build should be done', function() {
			var changes = updateBuildSpy.getCall(2).args[1];
			expect(changes).only.have.keys(
				'endDate', 'status', 'completed', 'error'
			);
			expect(changes.status).equal('done');
			expect(changes.completed).equal(true);
			expect(changes.error).equal(null);
		});

		it('update build called 3 times in total', function() {
			expect(updateBuildSpy.callCount).equal(3);
		});
	});

	describe('with fail project', function() {
		var updateBuildSpy;

		it('instance should be created without errors', function() {
			distributor = helpers.createDistributor({
				projects: projects,
				nodes: [{type: 'local', maxExecutorsCount: 1}],
				executorRun: sinon.stub().callsArgWithAsync(
					0,
					new Error('Some error')
				)
			});
			updateBuildSpy = sinon.spy(distributor, '_updateBuild');
		});

		it('should run without errors', function(done) {
			distributor.run({projectName: 'project1'}, function(err) {
				expect(err).not.ok();
				done();
			});
		});

		it('build should be queued', function() {
			var changes = updateBuildSpy.getCall(0).args[1];
			expect(changes.status).equal('queued');
		});

		it('build should be in-progress', function() {
			var changes = updateBuildSpy.getCall(1).args[1];
			expect(changes.status).equal('in-progress');
		});

		it('build should be fail', function() {
			var changes = updateBuildSpy.getCall(2).args[1];
			expect(changes.status).equal('error');
			expect(changes.completed).equal(true);
			expect(changes.error.message).equal('Some error');
		});

		it('update build called 3 times in total', function() {
			expect(updateBuildSpy.callCount).equal(3);
		});
	});

	describe('with success project and build cancel', function() {
		var distributorParams = {
			projects: projects,
			nodes: [{type: 'local', maxExecutorsCount: 1}],
			saveBuild: function(build, callback) {
				build.id = 1;
				callback(null, build);
			}
		};

		describe('when cancel queued bulid', function() {
			var updateBuildSpy;

			var cancelError;
			it('instance should be created without errors', function() {
				distributor = helpers.createDistributor(distributorParams);

				var originalRunNext = distributor._runNext;
				distributor._runNext = function() {
					distributor.cancel(1, function(err) {
						cancelError = err;
					});
					originalRunNext.apply(distributor, arguments);
				};

				updateBuildSpy = sinon.spy(distributor, '_updateBuild');
			});

			it('should run without errors', function(done) {
				distributor.run({projectName: 'project1'}, function(err) {
					expect(err).not.ok();
					done();
				});
			});

			it('build should be queued', function() {
				var changes = updateBuildSpy.getCall(0).args[1];
				expect(changes).only.have.keys(
					'project', 'initiator', 'params', 'createDate', 'status',
					'completed'
				);
				expect(changes.status).equal('queued');
				expect(changes.completed).equal(false);
			});

			it('should be cancelled without error', function() {
				expect(cancelError).not.ok();
			});

			it('update build called only once', function() {
				expect(updateBuildSpy.callCount).equal(1);
			});
		});

		describe('when try to cancel unexisted build', function() {
			var cancelError;

			it('instance should be created without errors', function() {
				distributor = helpers.createDistributor(distributorParams);

				var originalRunNext = distributor._runNext;
				distributor._runNext = function() {
					distributor.cancel(2, function(err) {
						cancelError = err;
					});
					originalRunNext.apply(distributor, arguments);
				};
			});

			it('should run without errors', function(done) {
				distributor.run({projectName: 'project1'}, function(err) {
					expect(err).not.ok();
					done();
				});
			});

			it('should be cancelled with error (build not found)', function() {
				expect(cancelError).ok();
				expect(cancelError.message).eql(
					'Build with id "2" not found for cancel'
				);
			});
		});
	});

	describe('with success project and buildParams.scmRev', function() {
		var project1 = {
				name: 'project1',
				scm: {type: 'mercurial', rev: '1'}
			},
			distributorParams = {
				projects: [project1],
				nodes: [{type: 'local', maxExecutorsCount: 1}]
			};

		describe('when buildParams.scmRev is not set', function() {
			var updateBuildSpy;

			it('instance should be created without errors', function() {
				distributor = helpers.createDistributor(distributorParams);
				updateBuildSpy = sinon.spy(distributor, '_updateBuild');
			});

			it('should run without errors', function(done) {
				distributor.run({projectName: 'project1'}, function(err) {
					expect(err).not.ok();
					done();
				});
			});

			it('build should be queued with proper params', function() {
				var changes = updateBuildSpy.getCall(0).args[1];
				expect(changes.params).eql({});
				expect(changes.project).eql(project1);
			});
		});

		describe('when buildParams.scmRev is set', function() {
			var updateBuildSpy,
				buildParams = {scmRev: '2'};

			it('instance should be created without errors', function() {
				distributor = helpers.createDistributor(distributorParams);
				updateBuildSpy = sinon.spy(distributor, '_updateBuild');
			});

			it('should run without errors', function(done) {
				distributor.run({
					projectName: 'project1',
					buildParams: buildParams
				}, function(err) {
					expect(err).not.ok();
					done();
				});
			});

			it('build should be queued with proper params', function() {
				var changes = updateBuildSpy.getCall(0).args[1];
				expect(changes.params).eql(buildParams);
				expect(changes.project).eql(
					_({}).extend(
						project1,
						{scm: _({}).extend(project1.scm, {rev: buildParams.scmRev})}
					)
				);
			});
		});
	});

	describe('with success project and build with env', function() {
		var updateBuildSpy;

		it('instance should be created without errors', function() {
			distributor = helpers.createDistributor({
				projects: projects,
				nodes: [{type: 'local', maxExecutorsCount: 1, envs: ['env1']}]
			});
			updateBuildSpy = sinon.spy(distributor, '_updateBuild');
		});

		it('should run without errors', function(done) {
			distributor.run({projectName: 'project1', env: 'env1'}, function(err) {
				expect(err).not.ok();
				done();
			});
		});

		it('build should be queued with env', function() {
			var changes = updateBuildSpy.getCall(0).args[1];
			expect(changes).have.keys('status', 'env');
			expect(changes.status).equal('queued');
			expect(changes.env).eql({name: 'env1'});
		});

		it('build should be in-progress', function() {
			var changes = updateBuildSpy.getCall(1).args[1];
			expect(changes).have.keys('status');
			expect(changes.status).equal('in-progress');
		});

		it('build should be done', function() {
			var changes = updateBuildSpy.getCall(2).args[1];
			expect(changes).have.keys('status');
			expect(changes.status).equal('done');
		});

		it('update build called 3 times in total', function() {
			expect(updateBuildSpy.callCount).equal(3);
		});
	});

	describe('with success project and two project envs', function() {
		var updateBuildSpy;

		it('instance should be created without errors', function() {
			var envs = ['env1', 'env2'];
			distributor = helpers.createDistributor({
				projects: [{name: 'project1', envs: envs}],
				nodes: [{type: 'local', maxExecutorsCount: 1, envs: envs}]
			});
			updateBuildSpy = sinon.spy(distributor, '_updateBuild');
		});

		it('should run without errors', function(done) {
			distributor.run({projectName: 'project1'}, function(err) {
				expect(err).not.ok();
				done();
			});
		});

		it('build for first env should be queued', function() {
			var args = updateBuildSpy.getCall(0).args,
				build = args[0],
				changes = args[1];
			expect(build.env).eql({name: 'env1'});
			expect(changes).have.keys('status');
			expect(changes.status).equal('queued');
		});

		it('build for second env should be queued', function() {
			var args = updateBuildSpy.getCall(1).args,
				build = args[0],
				changes = args[1];
			expect(build.env).eql({name: 'env2'});
			expect(changes).have.keys('status');
			expect(changes.status).equal('queued');
		});

		it('build for first env should be in-progress', function() {
			var args = updateBuildSpy.getCall(2).args,
				build = args[0],
				changes = args[1];
			expect(build.env).eql({name: 'env1'});
			expect(changes).have.keys('status');
			expect(changes.status).equal('in-progress');
		});

		it('build for first should be done', function() {
			var args = updateBuildSpy.getCall(3).args,
				build = args[0],
				changes = args[1];
			expect(build.env).eql({name: 'env1'});
			expect(changes).have.keys('status');
			expect(changes.status).equal('done');
		});

		it('build for second env should be in-progress', function() {
			var args = updateBuildSpy.getCall(4).args,
				build = args[0],
				changes = args[1];
			expect(build.env).eql({name: 'env2'});
			expect(changes).have.keys('status');
			expect(changes.status).equal('in-progress');
		});

		it('build for second should be done', function() {
			var args = updateBuildSpy.getCall(5).args,
				build = args[0],
				changes = args[1];
			expect(build.env).eql({name: 'env2'});
			expect(changes).have.keys('status');
			expect(changes.status).equal('done');
		});

		it('update build called 6 times in total', function() {
			expect(updateBuildSpy.callCount).equal(6);
		});
	});

});
<|endoftext|>


<commit_before>var KNOWN_APPS = {
    // MonoDev
    '00000000000000000000000000000002': {
        alias: ['monodev', 'md'],
        repo: 'git@github.com:jillix/MonoDev.git'
    },
    // DMS apps
    'cc36hf78HGT965HgtB96KJyg9dfGHtgV': {
        alias: ['cctool', 'cc'],
        repo: 'git@bitbucket.org:jillix/cctool.git'
    },
    'dms30dksd36V0a5PRPzXHgE49J5HlfHF': {
        alias: ['dms'],
        repo: 'git@bitbucket.org:jillix/dms.git'
    },
    'crm6hf78HGT965HgtB96KJyg9dfGHtgV': {
        alias: ['crm'],
        repo: 'git@bitbucket.org:jillix/crm.git'
    },
    'not-known-yet': {
        alias: ['crm'],
        repo: 'git@bitbucket.org:jillix/crm.git'
    },
    // old apps
    '00000000000000000000000000000109': {
        alias: ['ladivina', 'ld'],
        repo: 'git@bitbucket.org:jillix/ladivina.git'
    },
    '00000000000000000000000000000100': {
        alias: ['partnerlogin', 'pl'],
        repo: 'git@bitbucket.org:jillix/partnerlogin.git'
    },
    '00000000000000000000000000000108': {
        alias: ['salongenf', 'sg'],
        repo: 'git@bitbucket.org:jillix/salongenf.git'
    },
    '00000000000000000000000000000101': {
        alias: ['aktionshop', 'as'],
        repo: 'git@bitbucket.org:jillix/aktionshop.git'
    },
    '00000000000000000000000000000051': {
        alias: ['liqshoporders', 'lo'],
        repo: 'git@bitbucket.org:jillix/liqshoporders.git'
    }
};

function find(app) {
    for (var i in KNOWN_APPS) {
        for (var a in KNOWN_APPS[i].alias) {
            if (KNOWN_APPS[i].alias[a] === app.toLowerCase()) {
                return { id: i, repo: KNOWN_APPS[i].repo };
            }
        }
    }
}

exports.KNOWN_APPS = KNOWN_APPS;
exports.find = find;

<commit_msg>Added CampaignVerification to known apps for installation scripts<commit_after>var KNOWN_APPS = {
    // MonoDev
    '00000000000000000000000000000002': {
        alias: ['monodev', 'md'],
        repo: 'git@github.com:jillix/MonoDev.git'
    },
    // DMS apps
    'cc36hf78HGT965HgtB96KJyg9dfGHtgV': {
        alias: ['cctool', 'cc'],
        repo: 'git@bitbucket.org:jillix/cctool.git'
    },
    'dms30dksd36V0a5PRPzXHgE49J5HlfHF': {
        alias: ['dms'],
        repo: 'git@bitbucket.org:jillix/dms.git'
    },
    'crm6hf78HGT965HgtB96KJyg9dfGHtgV': {
        alias: ['crm'],
        repo: 'git@bitbucket.org:jillix/crm.git'
    },
    'cv46hf78HGI965HgtB96KJyg9dfGHtgV': {
        alias: ['cv'],
        repo: 'git@bitbucket.org:jillix/campaignverification.git'
    },
    // old apps
    '00000000000000000000000000000109': {
        alias: ['ladivina', 'ld'],
        repo: 'git@bitbucket.org:jillix/ladivina.git'
    },
    '00000000000000000000000000000100': {
        alias: ['partnerlogin', 'pl'],
        repo: 'git@bitbucket.org:jillix/partnerlogin.git'
    },
    '00000000000000000000000000000108': {
        alias: ['salongenf', 'sg'],
        repo: 'git@bitbucket.org:jillix/salongenf.git'
    },
    '00000000000000000000000000000101': {
        alias: ['aktionshop', 'as'],
        repo: 'git@bitbucket.org:jillix/aktionshop.git'
    },
    '00000000000000000000000000000051': {
        alias: ['liqshoporders', 'lo'],
        repo: 'git@bitbucket.org:jillix/liqshoporders.git'
    }
};

function find(app) {
    for (var i in KNOWN_APPS) {
        for (var a in KNOWN_APPS[i].alias) {
            if (KNOWN_APPS[i].alias[a] === app.toLowerCase()) {
                return { id: i, repo: KNOWN_APPS[i].repo };
            }
        }
    }
}

exports.KNOWN_APPS = KNOWN_APPS;
exports.find = find;

<|endoftext|>


<commit_before>$( document ).ready( function()
{
  var refresh_alerts = setInterval(
  function ()
  {
    $.getJSON( '/alarms', function( data )
    {
      // Remove cleared alerts
      $.each( $( "#alarms > tbody > tr" ), function( row )
      {
        var keep = false;
        $.each( data['alarms'], function( index, element )
        {
          if ( row.id == element._id['$oid'] )
          {
            keep = true;
          }
        });
        if ( keep == false )
        {
          $( this ).remove();
        }
      });

      // Push new alarms to top
      var items = [];
      $.each( data['alarms'], function( index, element )
      {
        if ( $( "#" + element._id['$oid'] ).length == 0 )
        {
          var row = "
";
          row += "" + element.state + "	";
          row += "" + element.hostname + "	";
          row += "" + element.check + "	";
          row += "" + element.alarm + "	";
          row += "" + element.status + "	";
          row += "" + element.timestamp + "	";
          if ( element.acknowledged == true )
          {
            row += "Yes	"
          } else {
            row += "No	"
          }
          row += " 	"
          row += "
";
          items.push(row);
        }
      });
      $( "#alarms > tbody" ).prepend( items.join("\n") );
    });
    $.getJSON( '/events', function( data )
    {
      var table=[];
      $.each( data['events'], function( index, element )
      {
        var row = "
";
        row += "" + element.details.state + "	";
        row += "" + element.entity.label + "	";
        row += "" + element.check.label + "	";
        row += "" + element.alarm.label + "	";
        row += "" + element.details.status + "	";
        row += "" + element.details.timestamp + "	";
        table.push(row)
      });
      $( "#events > tbody").replaceWith( table.join("\n") );
    });
  }, 10000)
});
<commit_msg>Forgot the closing table row for recent events<commit_after>$( document ).ready( function()
{
  var refresh_alerts = setInterval(
  function ()
  {
    $.getJSON( '/alarms', function( data )
    {
      // Remove cleared alerts
      $.each( $( "#alarms > tbody > tr" ), function( row )
      {
        var keep = false;
        $.each( data['alarms'], function( index, element )
        {
          if ( row.id == element._id['$oid'] )
          {
            keep = true;
          }
        });
        if ( keep == false )
        {
          $( this ).remove();
        }
      });

      // Push new alarms to top
      var items = [];
      $.each( data['alarms'], function( index, element )
      {
        if ( $( "#" + element._id['$oid'] ).length == 0 )
        {
          var row = "
";
          row += "" + element.state + "	";
          row += "" + element.hostname + "	";
          row += "" + element.check + "	";
          row += "" + element.alarm + "	";
          row += "" + element.status + "	";
          row += "" + element.timestamp + "	";
          if ( element.acknowledged == true )
          {
            row += "Yes	"
          } else {
            row += "No	"
          }
          row += " 	"
          row += "
";
          items.push(row);
        }
      });
      $( "#alarms > tbody" ).prepend( items.join("\n") );
    });
    $.getJSON( '/events', function( data )
    {
      var table=[];
      $.each( data['events'], function( index, element )
      {
        var row = "
";
        row += "" + element.details.state + "	";
        row += "" + element.entity.label + "	";
        row += "" + element.check.label + "	";
        row += "" + element.alarm.label + "	";
        row += "" + element.details.status + "	";
        row += "" + element.details.timestamp + "	";
        row += "
";
        table.push(row)
      });
      $( "#events > tbody").replaceWith( table.join("\n") );
    });
  }, 10000)
});
<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Shows the home screen for a multi-screen simulation, which lets the user see all of the screens and select one.
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  // modules
  var PhetButton = require( 'JOIST/PhetButton' );
  var Node = require( 'SCENERY/nodes/Node' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var VBox = require( 'SCENERY/nodes/VBox' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Shape = require( 'KITE/Shape' );
  var inherit = require( 'PHET_CORE/inherit' );
  var platform = require( 'PHET_CORE/platform' );
  var ScreenView = require( 'JOIST/ScreenView' );
  var Frame = require( 'JOIST/Frame' );
  var Property = require( 'AXON/Property' );
  var FullScreenButton = require( 'JOIST/FullScreenButton' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Bounds2 = require( 'DOT/Bounds2' );
  var AdaptedFromPhETText = require( 'JOIST/AdaptedFromPhETText' );
  var Brand = require( 'BRAND/Brand' );
  var Input = require( 'SCENERY/input/Input' );

  // constants
  var HEIGHT = 70; //TODO what is this? is it the height of large icons?
  var TITLE_FONT_FAMILY = 'Century Gothic, Futura';
  var LAYOUT_BOUNDS = new Bounds2( 0, 0, 768, 504 );

  function HomeScreen( sim, options ) {
    var homeScreen = this;

    options = _.extend( {
      showSmallHomeScreenIconFrame: false,
      warningNode: null // {Node | null}, to display below the icons as a warning if available
    }, options );

    //Rendering in SVG seems to solve the problem that the home screen consumes 100% disk and crashes, see https://github.com/phetsims/joist/issues/17
    //Also makes it more responsive (and crisper on retina displays)
    //Renderer must be specified here because the node is added directly to the scene (instead of to some other node that already has svg renderer
    ScreenView.call( this, { renderer: sim.joistRenderer, layoutBounds: LAYOUT_BOUNDS } );

    this.backgroundColor = 'black';

    //iPad doesn't support Century Gothic, so fall back to Futura, see http://wordpress.org/support/topic/font-not-working-on-ipad-browser
    var title = new Text( sim.name, {
      font: new PhetFont( { size: 52, family: TITLE_FONT_FAMILY } ),
      fill: 'white',
      y: 110
    } );
    this.addChild( title );
    title.scale( Math.min( 1, 0.9 * this.layoutBounds.width / title.width ) );
    title.centerX = this.layoutBounds.centerX;

    //Keep track of which screen is highlighted so the same screen can remain highlighted even if nodes are replaced (say when one grows larger or smaller)
    var highlightedIndex = new Property( -1 ).setSendPhetEvents( false );

    var screenChildren = _.map( sim.screens, function( screen ) {
      var index = sim.screens.indexOf( screen );
      var largeIcon = new Node( {
        children: [ screen.homeScreenIcon ],
        scale: HEIGHT / screen.homeScreenIcon.height * 2
      } );
      var frame = new Frame( largeIcon );

      highlightedIndex.link( function( highlightedIndex ) { frame.setHighlighted( highlightedIndex === index ); } );

      var largeIconWithFrame = new Node( { children: [ frame, largeIcon ] } );
      var largeText = new Text( screen.name, { font: new PhetFont( 42 ), fill: '#f2e916' } );//Color match with the PhET Logo yellow

      //Shrink the text if it goes beyond the edge of the image
      if ( largeText.width > largeIconWithFrame.width ) {
        largeText.scale( largeIconWithFrame.width / largeText.width );
      }
      var largeScreenButton = new VBox( {
        //Don't 40 the VBox or it will shift down when the border becomes thicker
        resize: false,

        cursor: 'pointer', children: [
          largeIconWithFrame,
          largeText
        ],
        focusable: true,
        textDescription: screen.name + ' Screen: Button'
      } );

      //TODO: Switch to buttonListener, but make sure you test it because on 7/17/2013 there is a problem where
      // ButtonListener won't fire if a node has appeared under the pointer
      largeScreenButton.addInputListener( {
        down: function( event ) {
          if ( event.pointer.isMouse ) {
            sim.simModel.showHomeScreen = false;
            highlightedIndex.value = -1;
          }
        }
      } );

      largeScreenButton.addInputListener( {
        keydown: function( event ) {
          var keyCode = event.domEvent.keyCode;
          if ( keyCode === Input.KEY_ENTER || keyCode === Input.KEY_SPACE ) {
            sim.simModel.showHomeScreen = false;
            highlightedIndex.value = -1;

            // TODO: A way to automatically move focus
            // TODO: Need a way to provide overview descriptive text.  Perhaps a focusable for the entire screen?
            Input.focusedInstanceProperty.value = Input.getAllFocusableInstances()[ 0 ];
          }
        }
      } );

      //Show a small (unselected) screen icon.  In some cases (if the icon has a black background), a border may be shown around it as well.  See https://github.com/phetsims/color-vision/issues/49
      var smallIconContent = new Node( {
        opacity: 0.5,
        children: [ screen.homeScreenIcon ],
        scale: sim.screens.length === 4 ? HEIGHT / screen.homeScreenIcon.height :
               sim.screens.length === 3 ? 1.25 * HEIGHT / screen.homeScreenIcon.height :
               sim.screens.length === 2 ? 1.75 * HEIGHT / screen.homeScreenIcon.height :
               HEIGHT / screen.homeScreenIcon.height
      } );

      var smallFrame = new Rectangle( 0, 0, smallIconContent.width, smallIconContent.height, {
        stroke: options.showSmallHomeScreenIconFrame ? '#dddddd' : null,
        lineWidth: 0.7
      } );
      var smallIcon = new Node( { opacity: 0.5, children: [ smallFrame, smallIconContent ] } );

      var smallText = new Text( screen.name, { font: new PhetFont( 18 ), fill: 'gray' } );

      //Shrink the text if it goes beyond the edge of the image
      if ( smallText.width > smallIcon.width ) {
        smallText.scale( smallIcon.width / smallText.width );
      }

      var smallScreenButton = new VBox( {
        spacing: 3, cursor: 'pointer', children: [
          smallIcon,
          smallText
        ],
        focusable: true,
        textDescription: screen.name + ' Screen: Button'
      } );
      smallScreenButton.mouseArea = smallScreenButton.touchArea = Shape.bounds( smallScreenButton.bounds ); //cover the gap in the vbox
      smallScreenButton.addInputListener( {
        down: function( event ) {
          if ( event.pointer.isMouse ) {
            sim.simModel.screenIndex = index;
          }
        },

        //On the home screen if you touch an inactive screen thumbnail, it grows.  If then without lifting your finger you swipe over
        // to the next thumbnail, that one would grow.
        over: function( event ) {
          if ( !event.pointer.isMouse ) {
            sim.simModel.screenIndex = index;
          }
        }
      } );

      smallScreenButton.addInputListener( {
        keydown: function( event ) {
          var keyCode = event.domEvent.keyCode;
          if ( keyCode === Input.KEY_ENTER || keyCode === Input.KEY_SPACE ) {
            sim.simModel.screenIndex = index;
            sim.simModel.showHomeScreen = false;
            highlightedIndex.value = -1;

            // TODO: A way to automatically move focus
            // TODO: Need a way to provide overview descriptive text.  Perhaps a focusable for the entire screen?
            Input.focusedInstanceProperty.value = Input.getAllFocusableInstances()[ 0 ];
          }
        }
      } );

      var highlightListener = {
        over: function( event ) {
          if ( event.pointer.isMouse ) {
            highlightedIndex.value = index;

            //TODO: use named children instead of child indices?
            smallScreenButton.children[ 0 ].opacity = 1;
            smallScreenButton.children[ 1 ].fill = 'white';
          }
        },
        out: function( event ) {
          if ( event.pointer.isMouse ) {
            highlightedIndex.value = -1;
            smallScreenButton.children[ 0 ].opacity = 0.5;
            smallScreenButton.children[ 1 ].fill = 'gray';
          }
        }
      };
      smallScreenButton.addInputListener( highlightListener );
      largeScreenButton.addInputListener( highlightListener );
      largeScreenButton.mouseArea = largeScreenButton.touchArea = Shape.bounds( largeScreenButton.bounds ); //cover the gap in the vbox

      return { screen: screen, small: smallScreenButton, large: largeScreenButton, index: index };
    } );

    var center = new Node( { y: 170 } );
    homeScreen.addChild( center );
    sim.simModel.screenIndexProperty.link( function( screenIndex ) {

      //Space the icons out more if there are fewer, so they will be spaced nicely
      //Cannot have only 1 screen because for 1-screen sims there is no home screen.
      var spacing = sim.screens.length === 2 ? 60 :
                    sim.screens.length === 3 ? 60 :
                    33;

      var icons = _.map( screenChildren, function( screenChild ) {return screenChild.index === screenIndex ? screenChild.large : screenChild.small;} );
      center.children = [ new HBox( { spacing: spacing, children: icons, align: 'top', resize: false } ) ];
      center.centerX = homeScreen.layoutBounds.width / 2;
    } );

    //Only show the full screen button on supported platforms
    var showFullScreenButton = !platform.android && !platform.mobileSafari && !platform.ie; // might work on IE11 in the future
    if ( showFullScreenButton && false ) {
      var fullScreenButton = new FullScreenButton();
      this.addChild( new HBox( {
        spacing: 10,
        children: [ fullScreenButton, new PhetButton( sim ) ],
        right: this.layoutBounds.maxX - 5,
        bottom: this.layoutBounds.maxY - 5
      } ) );
    }
    else {
      this.phetButton = new PhetButton( sim );
      this.addChild( this.phetButton );
    }

    // if the branding specifies to show "adapted from PhET" in the navbar, show it here
    if ( Brand.adaptedFromPhET === true ) {
      this.adaptedFromText = new AdaptedFromPhETText( new Property( false ) );
      this.addChild( this.adaptedFromText );
    }

    if ( options.warningNode ) {
      var warningNode = options.warningNode;

      this.addChild( warningNode );
      warningNode.centerX = this.layoutBounds.centerX;
      warningNode.bottom = this.layoutBounds.maxY - 20;
    }
  }

  return inherit( ScreenView, HomeScreen, {
      layoutWithScale: function( scale, width, height ) {
        HomeScreen.prototype.layout.call( this, width, height );

        //Position the phetButton.
        //It is tricky since it is in the coordinate frame of the HomeScreen (which is a ScreenView, and hence translated and scaled)
        //We want to match its location with the location in the NavigationBar
        this.phetButton.right = (width - PhetButton.HORIZONTAL_INSET) / scale;
        this.phetButton.bottom = (height - PhetButton.VERTICAL_INSET) / scale;

        //Undo the vertical centering done in ScreenView so the button can be positioned globally
        if ( scale === width / this.layoutBounds.width ) {
          this.phetButton.translate( 0, -(height - this.layoutBounds.height * scale) / 2 / scale );
        }

        //Undo the horizontal centering done in ScreenView so the button can be positioned globally
        else if ( scale === height / this.layoutBounds.height ) {
          this.phetButton.translate( -(width - this.layoutBounds.width * scale) / 2 / scale, 0 );
        }

        if ( this.adaptedFromText ) {
          this.adaptedFromText.updateLayout( 1, this.phetButton );
        }
      }
    },

    //statics
    {
      TITLE_FONT_FAMILY: TITLE_FONT_FAMILY,
      LAYOUT_BOUNDS: LAYOUT_BOUNDS
    }
  );
} );<commit_msg>Fixing keydown events (they do not fire as down) and a number of isMouse guards that caused major problems with touch devices. fixes https://github.com/phetsims/scenery/issues/333, fixes https://github.com/phetsims/joist/issues/200<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Shows the home screen for a multi-screen simulation, which lets the user see all of the screens and select one.
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  // modules
  var PhetButton = require( 'JOIST/PhetButton' );
  var Node = require( 'SCENERY/nodes/Node' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var VBox = require( 'SCENERY/nodes/VBox' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Shape = require( 'KITE/Shape' );
  var inherit = require( 'PHET_CORE/inherit' );
  var platform = require( 'PHET_CORE/platform' );
  var ScreenView = require( 'JOIST/ScreenView' );
  var Frame = require( 'JOIST/Frame' );
  var Property = require( 'AXON/Property' );
  var FullScreenButton = require( 'JOIST/FullScreenButton' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Bounds2 = require( 'DOT/Bounds2' );
  var AdaptedFromPhETText = require( 'JOIST/AdaptedFromPhETText' );
  var Brand = require( 'BRAND/Brand' );
  var Input = require( 'SCENERY/input/Input' );

  // constants
  var HEIGHT = 70; //TODO what is this? is it the height of large icons?
  var TITLE_FONT_FAMILY = 'Century Gothic, Futura';
  var LAYOUT_BOUNDS = new Bounds2( 0, 0, 768, 504 );

  function HomeScreen( sim, options ) {
    var homeScreen = this;

    options = _.extend( {
      showSmallHomeScreenIconFrame: false,
      warningNode: null // {Node | null}, to display below the icons as a warning if available
    }, options );

    //Rendering in SVG seems to solve the problem that the home screen consumes 100% disk and crashes, see https://github.com/phetsims/joist/issues/17
    //Also makes it more responsive (and crisper on retina displays)
    //Renderer must be specified here because the node is added directly to the scene (instead of to some other node that already has svg renderer
    ScreenView.call( this, { renderer: sim.joistRenderer, layoutBounds: LAYOUT_BOUNDS } );

    this.backgroundColor = 'black';

    //iPad doesn't support Century Gothic, so fall back to Futura, see http://wordpress.org/support/topic/font-not-working-on-ipad-browser
    var title = new Text( sim.name, {
      font: new PhetFont( { size: 52, family: TITLE_FONT_FAMILY } ),
      fill: 'white',
      y: 110
    } );
    this.addChild( title );
    title.scale( Math.min( 1, 0.9 * this.layoutBounds.width / title.width ) );
    title.centerX = this.layoutBounds.centerX;

    //Keep track of which screen is highlighted so the same screen can remain highlighted even if nodes are replaced (say when one grows larger or smaller)
    var highlightedIndex = new Property( -1 ).setSendPhetEvents( false );

    var screenChildren = _.map( sim.screens, function( screen ) {
      var index = sim.screens.indexOf( screen );
      var largeIcon = new Node( {
        children: [ screen.homeScreenIcon ],
        scale: HEIGHT / screen.homeScreenIcon.height * 2
      } );
      var frame = new Frame( largeIcon );

      highlightedIndex.link( function( highlightedIndex ) { frame.setHighlighted( highlightedIndex === index ); } );

      var largeIconWithFrame = new Node( { children: [ frame, largeIcon ] } );
      var largeText = new Text( screen.name, { font: new PhetFont( 42 ), fill: '#f2e916' } );//Color match with the PhET Logo yellow

      //Shrink the text if it goes beyond the edge of the image
      if ( largeText.width > largeIconWithFrame.width ) {
        largeText.scale( largeIconWithFrame.width / largeText.width );
      }
      var largeScreenButton = new VBox( {
        //Don't 40 the VBox or it will shift down when the border becomes thicker
        resize: false,

        cursor: 'pointer', children: [
          largeIconWithFrame,
          largeText
        ],
        focusable: true,
        textDescription: screen.name + ' Screen: Button'
      } );

      //TODO: Switch to buttonListener, but make sure you test it because on 7/17/2013 there is a problem where
      // ButtonListener won't fire if a node has appeared under the pointer
      largeScreenButton.addInputListener( {
        down: function( event ) {
          sim.simModel.showHomeScreen = false;
          highlightedIndex.value = -1;
        }
      } );

      largeScreenButton.addInputListener( {
        keydown: function( event ) {
          var keyCode = event.domEvent.keyCode;
          if ( keyCode === Input.KEY_ENTER || keyCode === Input.KEY_SPACE ) {
            sim.simModel.showHomeScreen = false;
            highlightedIndex.value = -1;

            // TODO: A way to automatically move focus
            // TODO: Need a way to provide overview descriptive text.  Perhaps a focusable for the entire screen?
            Input.focusedInstanceProperty.value = Input.getAllFocusableInstances()[ 0 ];
          }
        }
      } );

      //Show a small (unselected) screen icon.  In some cases (if the icon has a black background), a border may be shown around it as well.  See https://github.com/phetsims/color-vision/issues/49
      var smallIconContent = new Node( {
        opacity: 0.5,
        children: [ screen.homeScreenIcon ],
        scale: sim.screens.length === 4 ? HEIGHT / screen.homeScreenIcon.height :
               sim.screens.length === 3 ? 1.25 * HEIGHT / screen.homeScreenIcon.height :
               sim.screens.length === 2 ? 1.75 * HEIGHT / screen.homeScreenIcon.height :
               HEIGHT / screen.homeScreenIcon.height
      } );

      var smallFrame = new Rectangle( 0, 0, smallIconContent.width, smallIconContent.height, {
        stroke: options.showSmallHomeScreenIconFrame ? '#dddddd' : null,
        lineWidth: 0.7
      } );
      var smallIcon = new Node( { opacity: 0.5, children: [ smallFrame, smallIconContent ] } );

      var smallText = new Text( screen.name, { font: new PhetFont( 18 ), fill: 'gray' } );

      //Shrink the text if it goes beyond the edge of the image
      if ( smallText.width > smallIcon.width ) {
        smallText.scale( smallIcon.width / smallText.width );
      }

      var smallScreenButton = new VBox( {
        spacing: 3, cursor: 'pointer', children: [
          smallIcon,
          smallText
        ],
        focusable: true,
        textDescription: screen.name + ' Screen: Button'
      } );
      smallScreenButton.mouseArea = smallScreenButton.touchArea = Shape.bounds( smallScreenButton.bounds ); //cover the gap in the vbox
      smallScreenButton.addInputListener( {
        down: function( event ) {
          sim.simModel.screenIndex = index;
        },

        //On the home screen if you touch an inactive screen thumbnail, it grows.  If then without lifting your finger you swipe over
        // to the next thumbnail, that one would grow.
        over: function( event ) {
          if ( event.pointer.isTouch ) {
            sim.simModel.screenIndex = index;
          }
        }
      } );

      smallScreenButton.addInputListener( {
        keydown: function( event ) {
          var keyCode = event.domEvent.keyCode;
          if ( keyCode === Input.KEY_ENTER || keyCode === Input.KEY_SPACE ) {
            sim.simModel.screenIndex = index;
            sim.simModel.showHomeScreen = false;
            highlightedIndex.value = -1;

            // TODO: A way to automatically move focus
            // TODO: Need a way to provide overview descriptive text.  Perhaps a focusable for the entire screen?
            Input.focusedInstanceProperty.value = Input.getAllFocusableInstances()[ 0 ];
          }
        }
      } );

      var highlightListener = {
        over: function( event ) {
          highlightedIndex.value = index;

          //TODO: use named children instead of child indices?
          smallScreenButton.children[ 0 ].opacity = 1;
          smallScreenButton.children[ 1 ].fill = 'white';
        },
        out: function( event ) {
          highlightedIndex.value = -1;
          smallScreenButton.children[ 0 ].opacity = 0.5;
          smallScreenButton.children[ 1 ].fill = 'gray';
        }
      };
      smallScreenButton.addInputListener( highlightListener );
      largeScreenButton.addInputListener( highlightListener );
      largeScreenButton.mouseArea = largeScreenButton.touchArea = Shape.bounds( largeScreenButton.bounds ); //cover the gap in the vbox

      return { screen: screen, small: smallScreenButton, large: largeScreenButton, index: index };
    } );

    var center = new Node( { y: 170 } );
    homeScreen.addChild( center );
    sim.simModel.screenIndexProperty.link( function( screenIndex ) {

      //Space the icons out more if there are fewer, so they will be spaced nicely
      //Cannot have only 1 screen because for 1-screen sims there is no home screen.
      var spacing = sim.screens.length === 2 ? 60 :
                    sim.screens.length === 3 ? 60 :
                    33;

      var icons = _.map( screenChildren, function( screenChild ) {return screenChild.index === screenIndex ? screenChild.large : screenChild.small;} );
      center.children = [ new HBox( { spacing: spacing, children: icons, align: 'top', resize: false } ) ];
      center.centerX = homeScreen.layoutBounds.width / 2;
    } );

    //Only show the full screen button on supported platforms
    var showFullScreenButton = !platform.android && !platform.mobileSafari && !platform.ie; // might work on IE11 in the future
    if ( showFullScreenButton && false ) {
      var fullScreenButton = new FullScreenButton();
      this.addChild( new HBox( {
        spacing: 10,
        children: [ fullScreenButton, new PhetButton( sim ) ],
        right: this.layoutBounds.maxX - 5,
        bottom: this.layoutBounds.maxY - 5
      } ) );
    }
    else {
      this.phetButton = new PhetButton( sim );
      this.addChild( this.phetButton );
    }

    // if the branding specifies to show "adapted from PhET" in the navbar, show it here
    if ( Brand.adaptedFromPhET === true ) {
      this.adaptedFromText = new AdaptedFromPhETText( new Property( false ) );
      this.addChild( this.adaptedFromText );
    }

    if ( options.warningNode ) {
      var warningNode = options.warningNode;

      this.addChild( warningNode );
      warningNode.centerX = this.layoutBounds.centerX;
      warningNode.bottom = this.layoutBounds.maxY - 20;
    }
  }

  return inherit( ScreenView, HomeScreen, {
      layoutWithScale: function( scale, width, height ) {
        HomeScreen.prototype.layout.call( this, width, height );

        //Position the phetButton.
        //It is tricky since it is in the coordinate frame of the HomeScreen (which is a ScreenView, and hence translated and scaled)
        //We want to match its location with the location in the NavigationBar
        this.phetButton.right = (width - PhetButton.HORIZONTAL_INSET) / scale;
        this.phetButton.bottom = (height - PhetButton.VERTICAL_INSET) / scale;

        //Undo the vertical centering done in ScreenView so the button can be positioned globally
        if ( scale === width / this.layoutBounds.width ) {
          this.phetButton.translate( 0, -(height - this.layoutBounds.height * scale) / 2 / scale );
        }

        //Undo the horizontal centering done in ScreenView so the button can be positioned globally
        else if ( scale === height / this.layoutBounds.height ) {
          this.phetButton.translate( -(width - this.layoutBounds.width * scale) / 2 / scale, 0 );
        }

        if ( this.adaptedFromText ) {
          this.adaptedFromText.updateLayout( 1, this.phetButton );
        }
      }
    },

    //statics
    {
      TITLE_FONT_FAMILY: TITLE_FONT_FAMILY,
      LAYOUT_BOUNDS: LAYOUT_BOUNDS
    }
  );
} );<|endoftext|>


<commit_before>import widgets from 'jupyter-js-widgets'
import _ from 'underscore'

import GoogleMapsLoader from 'google-maps'

function reloadGoogleMaps(configuration) {
    GoogleMapsLoader.release();
    GoogleMapsLoader.LIBRARIES = ["visualization"] ;
    if (configuration["api_key"] !== null &&
        configuration["api_key"] !== undefined) {
            GoogleMapsLoader.KEY = configuration["api_key"];
    };
}

reloadGoogleMaps({}) ;

function gPointToList(gpoint) {
    return [gpoint.lat(), gpoint.lng()]
}

function gBoundsToList(gbounds) {
    const sw = gPointToList(gbounds.getSouthWest())
    const ne = gPointToList(gbounds.getNorthEast())
    return [sw, ne]
}

// Mixins

const ConfigurationMixin = {
    loadConfiguration() {
        const modelConfiguration = this.model.get("configuration")
        reloadGoogleMaps(modelConfiguration)
    }
}

// Views

const GMapsLayerView = widgets.WidgetView.extend({
    initialize(parameters) {
        GMapsLayerView.__super__.initialize.apply(this, arguments)
        this.mapView = this.options.mapView
    }
})

export const DirectionsLayerView = GMapsLayerView.extend({
    render() {
        const rendererOptions = { map: this.mapView.map }

        this.directionsDisplay = new google.maps.DirectionsRenderer(rendererOptions)

        const modelData = this.model.get("data");

        const request = {
            origin: this.getOrigin(modelData),
            destination: this.getDestination(modelData),
            waypoints: this.getWaypoints(modelData),
            travelMode: google.maps.TravelMode.DRIVING
        };

        const directionsService = new google.maps.DirectionsService();

        directionsService.route(request, (response, status) => {
            // print to the browser console (mostly for debugging)
            console.log(`Direction service returned: ${status}`) ;
            // set a flag in the model
            this.model.set("layer_status", status) ;
            this.touch() ; // push `layer_status` changes to the model
            if (status == google.maps.DirectionsStatus.OK) {
                this.response = this.directionsDisplay ;
                this.directionsDisplay.setDirections(response);
            }
        });
    },

    addToMapView(mapView) { },

    getOrigin(modelData) {
        const [lat, lng] = _.first(modelData)
        return new google.maps.LatLng(lat, lng)
    },

    getDestination(modelData) {
        const [lat, lng] = _.last(modelData)
        return new google.maps.LatLng(lat, lng)
    },

    getWaypoints(modelData) {
        const withoutFirst = _.tail(modelData)
        const withoutLast = _.initial(withoutFirst)
        const dataAsGoogle = withoutLast.map(([lat, lng]) => {
            return {location: new google.maps.LatLng(lat, lng)}
        })
        return dataAsGoogle
    }
})

const HeatmapLayerBaseView = GMapsLayerView.extend({
    render() {
        this.modelEvents() ;
        GoogleMapsLoader.load((google) => {
            this.heatmap = new google.maps.visualization.HeatmapLayer({
                data: this.getData(),
                radius: this.model.get("point_radius"),
                maxIntensity: this.model.get("max_intensity"),
                dissipating: this.model.get("dissipating"),
                opacity: this.model.get("opacity"),
                gradient: this.model.get("gradient")
            }) ;
        });
    },

    addToMapView(mapView) {
        this.heatmap.setMap(mapView.map)
    },

    modelEvents() {
        // Simple properties:
        // [nameInView, nameInModel]
        const properties = [
            ['maxIntensity', 'max_intensity'],
            ['opacity', 'opacity'],
            ['radius', 'point_radius'],
            ['dissipating', 'dissipating'],
            ['gradient', 'gradient']
        ]
        properties.forEach(([nameInView, nameInModel]) => {
            const callback = (
                () => this.heatmap.set(nameInView, this.model.get(nameInModel))
            )
            this.model.on(`change:${nameInModel}`, callback, this)
        })
    },

    get_data() {},

})

export const SimpleHeatmapLayerView = HeatmapLayerBaseView.extend({
    getData() {
        const data = this.model.get("data")
        const dataAsGoogle = new google.maps.MVCArray(
            data.map(([lat, lng]) => new google.maps.LatLng(lat, lng))
        )
        return dataAsGoogle
    }
})

export const WeightedHeatmapLayerView = HeatmapLayerBaseView.extend({
    getData() {
        const data = this.model.get("data")
        const dataAsGoogle = new google.maps.MVCArray(
            data.map(([lat, lng, weight]) => {
                const location = new google.maps.LatLng(lat, lng)
                return { location: location, weight: weight }
            })
        );
        return dataAsGoogle
    }
})

/* Base class for markers.
 * This sets options common to the different types of markers.
 *
 * Subclasses are responsible for implementing the `getStyleOptions`
 * method, which must return an object of additional options
 * to add to the marker, and `setStyleEvents`, which must set
 * up events for those styles.
 */
export const BaseMarkerView = widgets.WidgetView.extend({
    render() {
        const [lat, lng] = this.model.get("location")
        const title = this.model.get("hover_text")
        const infoBoxHtml = this.model.get("info_html")
        const styleOptions = this.getStyleOptions()
        const markerOptions = {
            position: {lat, lng},
            draggable: false,
            title,
            ...styleOptions
        }
        this.marker = new google.maps.Marker(markerOptions)

        this.infoWindow = new google.maps.InfoWindow({
            content: infoBoxHtml
        });
        this.modelEvents()
    },

    addToMapView(mapView) {
        let marker = this.marker;
        let infoWindow = this.infoWindow;
        marker.setMap(mapView.map);
        marker.addListener('click', function() {
            infoWindow.open(mapView.map, marker);
        });
    },

    modelEvents() {
        // Simple properties:
        const properties = [
            ['title', 'hover_text', 'info_html']
        ]
        properties.forEach(([nameInView, nameInModel]) => {
            const callback = (
                () => {
                  this.marker.set(
                  nameInView, this.model.get(nameInModel))
                }
            )
            this.model.on(`change:${nameInModel}`, callback, this)
        })

        this.setStyleEvents()
    }

})

export const SymbolView = BaseMarkerView.extend({

    getStyleOptions() {
        const fillColor = this.model.get("fill_color")
        const strokeColor = this.model.get("stroke_color")
        const fillOpacity = this.model.get("fill_opacity")
        const strokeOpacity = this.model.get("stroke_opacity")
        const scale = this.model.get("scale")
        return {
            icon: {
                path: google.maps.SymbolPath.CIRCLE,
                scale,
                fillColor,
                strokeColor,
                fillOpacity,
                strokeOpacity
            }
        }
    },

    setStyleEvents() {
        const iconProperties = [
            ['strokeColor', 'stroke_color'],
            ['fillColor', 'fill_color'],
            ['scale', 'scale'],
            ['stroke_opacity', 'stroke_opacity'],
            ['fillOpacity', 'fill_opacity']
        ]
        iconProperties.forEach(([nameInView, nameInModel]) => {
            const callback = ( () => {
                const newIcon = Object.assign({}, this.marker.getIcon())
                newIcon[nameInView] = this.model.get(nameInModel)
                this.marker.setIcon(newIcon)
            })
            this.model.on(`change:${nameInModel}`, callback, this)
        })
    }
})

export const MarkerView = BaseMarkerView.extend({

    getStyleOptions() {
        this.modelEvents()
        const label = this.model.get("label")
        return { label }
    },

    setStyleEvents() {
        const properties = [
            ['label', 'label']
        ]
        properties.forEach(([nameInView, nameInModel]) => {
            const callback = (
                () => {
                  this.marker.set(
                  nameInView, this.model.get(nameInModel))
                }
            )
            this.model.on(`change:${nameInModel}`, callback, this)
        })
    }

})

export const MarkerLayerView = GMapsLayerView.extend({
    render() {
        this.markerViews = new widgets.ViewList(this.addMarker, null, this)
        this.markerViews.update(this.model.get("markers"))
    },

    addToMapView(mapView) {
        this.markerViews.forEach(view => view.addToMapView(mapView))
    },

    addMarker(childModel) {
        return this.create_child_view(childModel)
            .then((childView) => {
                childView.addToMapView(this.mapView)
                return childView
            })
    }
})

export const PlainmapView = widgets.DOMWidgetView.extend({
    render() {
        this.loadConfiguration();
        this.el.style["width"] = this.model.get("width");
        this.el.style["height"] = this.model.get("height");

        const initialBounds = this.model.get("data_bounds");

        this.layerViews = new widgets.ViewList(this.addLayerModel, null, this);
        this.modelEvents() ;

        this.on("displayed", () => {
            GoogleMapsLoader.load((google) => {
                this.map = new google.maps.Map(this.el) ;
                this.updateBounds(initialBounds);

                this.layerViews.update(this.model.get("layers"));

                // hack to force the map to redraw
                setTimeout(() => {
                    google.maps.event.trigger(this.map, 'resize') ;
                    this.updateBounds(initialBounds);
                }, 500);
            })
        })
    },

    modelEvents() {
        this.model.on("change:data_bounds", this.updateBounds, this);
    },

    updateBounds() {
        const [[latBL, lngBL], [latTR, lngTR]] = this.model.get("data_bounds")
        const boundBL = new google.maps.LatLng(latBL, lngBL)
        const boundTR = new google.maps.LatLng(latTR, lngTR)
        const bounds = new google.maps.LatLngBounds(boundBL, boundTR)
        this.map.fitBounds(bounds);
    },

    addLayerModel(childModel) {
        return this.create_child_view(
            childModel, {mapView: this}
        ).then((childView) => {
            childView.addToMapView(this) ;
            return childView;
        })
    },

})

_.extend(PlainmapView.prototype, ConfigurationMixin);

// Models

export const GMapsLayerModel = widgets.WidgetModel.extend({
    defaults: _.extend({}, widgets.WidgetModel.prototype.defaults, {
        _view_name : 'GMapsLayerView',
        _model_name : 'GMapsLayerModel',
        _view_module : 'jupyter-gmaps',
        _model_module : 'jupyter-gmaps'
    })
});

export const DirectionsLayerModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "DirectionsLayerView",
        _model_name: "DirectionsLayerModel"
    })
});

export const SimpleHeatmapLayerModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "SimpleHeatmapLayerView",
        _model_name: "SimpleHeatmapLayerModel"
    })
});

export const WeightedHeatmapLayerModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "WeightedHeatmapLayerView",
        _model_name: "WeightedHeatmapLayerModel"
    })
});

export const SymbolModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "SymbolView",
        _model_name: "SymbolModel"
    })
})

export const MarkerModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "MarkerView",
        _model_name: "MarkerModel"
    })
})

export const MarkerLayerModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "MarkerLayerView",
        _model_name: "MarkerLayerModel"
    }),
}, {
    serializers: _.extend({
            markers: {deserialize: widgets.unpack_models}
    }, widgets.DOMWidgetModel.serializers)
})

export const PlainmapModel = widgets.DOMWidgetModel.extend({
    defaults: _.extend({}, widgets.DOMWidgetModel.prototype.defaults, {
        _view_name: "PlainmapView",
        _model_name: "PlainmapModel",
        _view_module : 'jupyter-gmaps',
        _model_module : 'jupyter-gmaps',
        width: "600px",
        height: "400px"

    })
}, {
    serializers: _.extend({
            layers: {deserialize: widgets.unpack_models}
    }, widgets.DOMWidgetModel.serializers)
});
<commit_msg>Feature: Allow python objects trigger changes on infoboxes<commit_after>import widgets from 'jupyter-js-widgets'
import _ from 'underscore'

import GoogleMapsLoader from 'google-maps'

function reloadGoogleMaps(configuration) {
    GoogleMapsLoader.release();
    GoogleMapsLoader.LIBRARIES = ["visualization"] ;
    if (configuration["api_key"] !== null &&
        configuration["api_key"] !== undefined) {
            GoogleMapsLoader.KEY = configuration["api_key"];
    };
}

reloadGoogleMaps({}) ;

function gPointToList(gpoint) {
    return [gpoint.lat(), gpoint.lng()]
}

function gBoundsToList(gbounds) {
    const sw = gPointToList(gbounds.getSouthWest())
    const ne = gPointToList(gbounds.getNorthEast())
    return [sw, ne]
}

// Mixins

const ConfigurationMixin = {
    loadConfiguration() {
        const modelConfiguration = this.model.get("configuration")
        reloadGoogleMaps(modelConfiguration)
    }
}

// Views

const GMapsLayerView = widgets.WidgetView.extend({
    initialize(parameters) {
        GMapsLayerView.__super__.initialize.apply(this, arguments)
        this.mapView = this.options.mapView
    }
})

export const DirectionsLayerView = GMapsLayerView.extend({
    render() {
        const rendererOptions = { map: this.mapView.map }

        this.directionsDisplay = new google.maps.DirectionsRenderer(rendererOptions)

        const modelData = this.model.get("data");

        const request = {
            origin: this.getOrigin(modelData),
            destination: this.getDestination(modelData),
            waypoints: this.getWaypoints(modelData),
            travelMode: google.maps.TravelMode.DRIVING
        };

        const directionsService = new google.maps.DirectionsService();

        directionsService.route(request, (response, status) => {
            // print to the browser console (mostly for debugging)
            console.log(`Direction service returned: ${status}`) ;
            // set a flag in the model
            this.model.set("layer_status", status) ;
            this.touch() ; // push `layer_status` changes to the model
            if (status == google.maps.DirectionsStatus.OK) {
                this.response = this.directionsDisplay ;
                this.directionsDisplay.setDirections(response);
            }
        });
    },

    addToMapView(mapView) { },

    getOrigin(modelData) {
        const [lat, lng] = _.first(modelData)
        return new google.maps.LatLng(lat, lng)
    },

    getDestination(modelData) {
        const [lat, lng] = _.last(modelData)
        return new google.maps.LatLng(lat, lng)
    },

    getWaypoints(modelData) {
        const withoutFirst = _.tail(modelData)
        const withoutLast = _.initial(withoutFirst)
        const dataAsGoogle = withoutLast.map(([lat, lng]) => {
            return {location: new google.maps.LatLng(lat, lng)}
        })
        return dataAsGoogle
    }
})

const HeatmapLayerBaseView = GMapsLayerView.extend({
    render() {
        this.modelEvents() ;
        GoogleMapsLoader.load((google) => {
            this.heatmap = new google.maps.visualization.HeatmapLayer({
                data: this.getData(),
                radius: this.model.get("point_radius"),
                maxIntensity: this.model.get("max_intensity"),
                dissipating: this.model.get("dissipating"),
                opacity: this.model.get("opacity"),
                gradient: this.model.get("gradient")
            }) ;
        });
    },

    addToMapView(mapView) {
        this.heatmap.setMap(mapView.map)
    },

    modelEvents() {
        // Simple properties:
        // [nameInView, nameInModel]
        const properties = [
            ['maxIntensity', 'max_intensity'],
            ['opacity', 'opacity'],
            ['radius', 'point_radius'],
            ['dissipating', 'dissipating'],
            ['gradient', 'gradient']
        ]
        properties.forEach(([nameInView, nameInModel]) => {
            const callback = (
                () => this.heatmap.set(nameInView, this.model.get(nameInModel))
            )
            this.model.on(`change:${nameInModel}`, callback, this)
        })
    },

    get_data() {},

})

export const SimpleHeatmapLayerView = HeatmapLayerBaseView.extend({
    getData() {
        const data = this.model.get("data")
        const dataAsGoogle = new google.maps.MVCArray(
            data.map(([lat, lng]) => new google.maps.LatLng(lat, lng))
        )
        return dataAsGoogle
    }
})

export const WeightedHeatmapLayerView = HeatmapLayerBaseView.extend({
    getData() {
        const data = this.model.get("data")
        const dataAsGoogle = new google.maps.MVCArray(
            data.map(([lat, lng, weight]) => {
                const location = new google.maps.LatLng(lat, lng)
                return { location: location, weight: weight }
            })
        );
        return dataAsGoogle
    }
})

/* Base class for markers.
 * This sets options common to the different types of markers.
 *
 * Subclasses are responsible for implementing the `getStyleOptions`
 * method, which must return an object of additional options
 * to add to the marker, and `setStyleEvents`, which must set
 * up events for those styles.
 */
export const BaseMarkerView = widgets.WidgetView.extend({
    render() {
        const [lat, lng] = this.model.get("location")
        const title = this.model.get("hover_text")
        const infoBoxHtml = this.model.get("info_html")
        const styleOptions = this.getStyleOptions()
        const markerOptions = {
            position: {lat, lng},
            draggable: false,
            title,
            ...styleOptions
        }
        this.marker = new google.maps.Marker(markerOptions)
        this.infoWindow = new google.maps.InfoWindow({
            content: infoBoxHtml
        });
        this.modelEvents()
    },

    addToMapView(mapView) {
        let marker = this.marker;
        let infoWindow = this.infoWindow;
        marker.setMap(mapView.map);
        marker.addListener('click', function() {
            infoWindow.open(mapView.map, marker);
        });
    },

    modelEvents() {
        // Simple properties:
        const properties = [
            ['title', 'hover_text']
        ]
        const infoBoxProperties = [
             ['content', 'info_html']
         ]

        properties.forEach(([nameInView, nameInModel]) => {
            const callback = (
                () => {
                  this.marker.set(
                  nameInView, this.model.get(nameInModel))
                }
            )
            this.model.on(`change:${nameInModel}`, callback, this)
        })

         infoBoxProperties.forEach(([nameInView, nameInModel]) => {
             const callback = (
                 () => {
                     this.infoWindow.set(
                     nameInView, this.model.get(nameInModel))
                 }
             )
             this.model.on(`change:${nameInModel}`, callback, this)
         })

        this.setStyleEvents()
    }

})

export const SymbolView = BaseMarkerView.extend({

    getStyleOptions() {
        const fillColor = this.model.get("fill_color")
        const strokeColor = this.model.get("stroke_color")
        const fillOpacity = this.model.get("fill_opacity")
        const strokeOpacity = this.model.get("stroke_opacity")
        const scale = this.model.get("scale")
        return {
            icon: {
                path: google.maps.SymbolPath.CIRCLE,
                scale,
                fillColor,
                strokeColor,
                fillOpacity,
                strokeOpacity
            }
        }
    },

    setStyleEvents() {
        const iconProperties = [
            ['strokeColor', 'stroke_color'],
            ['fillColor', 'fill_color'],
            ['scale', 'scale'],
            ['stroke_opacity', 'stroke_opacity'],
            ['fillOpacity', 'fill_opacity']
        ]
        iconProperties.forEach(([nameInView, nameInModel]) => {
            const callback = ( () => {
                const newIcon = Object.assign({}, this.marker.getIcon())
                newIcon[nameInView] = this.model.get(nameInModel)
                this.marker.setIcon(newIcon)
            })
            this.model.on(`change:${nameInModel}`, callback, this)
        })
    }
})

export const MarkerView = BaseMarkerView.extend({

    getStyleOptions() {
        this.modelEvents()
        const label = this.model.get("label")
        return { label }
    },

    setStyleEvents() {
        const properties = [
            ['label', 'label']
        ]
        properties.forEach(([nameInView, nameInModel]) => {
            const callback = (
                () => {
                  this.marker.set(
                  nameInView, this.model.get(nameInModel))
                }
            )
            this.model.on(`change:${nameInModel}`, callback, this)
        })
    }

})

export const MarkerLayerView = GMapsLayerView.extend({
    render() {
        this.markerViews = new widgets.ViewList(this.addMarker, null, this)
        this.markerViews.update(this.model.get("markers"))
    },

    addToMapView(mapView) {
        this.markerViews.forEach(view => view.addToMapView(mapView))
    },

    addMarker(childModel) {
        return this.create_child_view(childModel)
            .then((childView) => {
                childView.addToMapView(this.mapView)
                return childView
            })
    }
})

export const PlainmapView = widgets.DOMWidgetView.extend({
    render() {
        this.loadConfiguration();
        this.el.style["width"] = this.model.get("width");
        this.el.style["height"] = this.model.get("height");

        const initialBounds = this.model.get("data_bounds");

        this.layerViews = new widgets.ViewList(this.addLayerModel, null, this);
        this.modelEvents() ;

        this.on("displayed", () => {
            GoogleMapsLoader.load((google) => {
                this.map = new google.maps.Map(this.el) ;
                this.updateBounds(initialBounds);

                this.layerViews.update(this.model.get("layers"));

                // hack to force the map to redraw
                setTimeout(() => {
                    google.maps.event.trigger(this.map, 'resize') ;
                    this.updateBounds(initialBounds);
                }, 500);
            })
        })
    },

    modelEvents() {
        this.model.on("change:data_bounds", this.updateBounds, this);
    },

    updateBounds() {
        const [[latBL, lngBL], [latTR, lngTR]] = this.model.get("data_bounds")
        const boundBL = new google.maps.LatLng(latBL, lngBL)
        const boundTR = new google.maps.LatLng(latTR, lngTR)
        const bounds = new google.maps.LatLngBounds(boundBL, boundTR)
        this.map.fitBounds(bounds);
    },

    addLayerModel(childModel) {
        return this.create_child_view(
            childModel, {mapView: this}
        ).then((childView) => {
            childView.addToMapView(this) ;
            return childView;
        })
    },

})

_.extend(PlainmapView.prototype, ConfigurationMixin);

// Models

export const GMapsLayerModel = widgets.WidgetModel.extend({
    defaults: _.extend({}, widgets.WidgetModel.prototype.defaults, {
        _view_name : 'GMapsLayerView',
        _model_name : 'GMapsLayerModel',
        _view_module : 'jupyter-gmaps',
        _model_module : 'jupyter-gmaps'
    })
});

export const DirectionsLayerModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "DirectionsLayerView",
        _model_name: "DirectionsLayerModel"
    })
});

export const SimpleHeatmapLayerModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "SimpleHeatmapLayerView",
        _model_name: "SimpleHeatmapLayerModel"
    })
});

export const WeightedHeatmapLayerModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "WeightedHeatmapLayerView",
        _model_name: "WeightedHeatmapLayerModel"
    })
});

export const SymbolModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "SymbolView",
        _model_name: "SymbolModel"
    })
})

export const MarkerModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "MarkerView",
        _model_name: "MarkerModel"
    })
})

export const MarkerLayerModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "MarkerLayerView",
        _model_name: "MarkerLayerModel"
    }),
}, {
    serializers: _.extend({
            markers: {deserialize: widgets.unpack_models}
    }, widgets.DOMWidgetModel.serializers)
})

export const PlainmapModel = widgets.DOMWidgetModel.extend({
    defaults: _.extend({}, widgets.DOMWidgetModel.prototype.defaults, {
        _view_name: "PlainmapView",
        _model_name: "PlainmapModel",
        _view_module : 'jupyter-gmaps',
        _model_module : 'jupyter-gmaps',
        width: "600px",
        height: "400px"

    })
}, {
    serializers: _.extend({
            layers: {deserialize: widgets.unpack_models}
    }, widgets.DOMWidgetModel.serializers)
});
<|endoftext|>