<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 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 HomeScreenView( sim, options ) {
    var homeScreenView = 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, { 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 highlightedScreenIndexProperty = new Property( -1 );

    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 );

      highlightedScreenIndexProperty.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
      // TODO: ButtonListener won't fire if a node has appeared under the pointer
      largeScreenButton.addInputListener( {
        down: function() {
          largeScreenButton.trigger0( 'startedCallbacksForPressed' );
          sim.showHomeScreen = false;
          highlightedScreenIndexProperty.value = -1;
          largeScreenButton.trigger0( 'endedCallbacksForPressed' );
        }
      } );

      // TODO: Switch to button listener--this will also clean up the keyboard accessibility
      largeScreenButton.addInputListener( {
        keydown: function( event ) {
          var keyCode = event.domEvent.keyCode;
          if ( keyCode === Input.KEY_ENTER || keyCode === Input.KEY_SPACE ) {
            sim.showHomeScreen = false;
            highlightedScreenIndexProperty.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.focusedTrailProperty.value = Input.getAllFocusableTrails()[ 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 ? 1.00 * 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.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.screenIndex = index;
          }
        }
      } );

      smallScreenButton.addInputListener( {
        keydown: function( event ) {
          var keyCode = event.domEvent.keyCode;
          if ( keyCode === Input.KEY_ENTER || keyCode === Input.KEY_SPACE ) {
            sim.screenIndex = index;
            sim.showHomeScreen = false;
            highlightedScreenIndexProperty.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.focusedTrailProperty.value = Input.getAllFocusableTrails()[ 0 ];
          }
        }
      } );

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

          //TODO: use named children instead of child indices?
          smallScreenButton.children[ 0 ].opacity = 1;
          smallScreenButton.children[ 1 ].fill = 'white';
        },
        out: function( event ) {
          highlightedScreenIndexProperty.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 } );
    homeScreenView.addChild( center );
    sim.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 = homeScreenView.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 ) { //TODO joist#261 why is this short-circuited? Is this block even relevant anymore?
      var fullScreenButton = new FullScreenButton();
      this.addChild( new HBox( {
        spacing: 10,
        children: [ fullScreenButton, new PhetButton( sim, new Property( 'white' ) ) ], //TODO joist#261 why is PhetButton created and below, in 2 different ways?
        right: this.layoutBounds.maxX - 5,
        bottom: this.layoutBounds.maxY - 5
      } ) );
    }
    else {
      this.phetButton = new PhetButton( sim, new Property( 'white' ), {
        tandem: options.tandem ? options.tandem.createTandem( 'phetButton' ) : null
      } );
      this.addChild( this.phetButton );
    }

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

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

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

        //Position the phetButton.
        //It is tricky since it is in the coordinate frame of the HomeScreenView (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 );
        }
      }
    },

    //statics
    {
      TITLE_FONT_FAMILY: TITLE_FONT_FAMILY,
      LAYOUT_BOUNDS: LAYOUT_BOUNDS
    }
  );
} );<commit_msg>#261 delete suspected cruft related to fullscreen feature<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 ScreenView = require( 'JOIST/ScreenView' );
  var Frame = require( 'JOIST/Frame' );
  var Property = require( 'AXON/Property' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Bounds2 = require( 'DOT/Bounds2' );
  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 HomeScreenView( sim, options ) {
    var homeScreenView = 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, { 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 highlightedScreenIndexProperty = new Property( -1 );

    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 );

      highlightedScreenIndexProperty.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
      // TODO: ButtonListener won't fire if a node has appeared under the pointer
      largeScreenButton.addInputListener( {
        down: function() {
          largeScreenButton.trigger0( 'startedCallbacksForPressed' );
          sim.showHomeScreen = false;
          highlightedScreenIndexProperty.value = -1;
          largeScreenButton.trigger0( 'endedCallbacksForPressed' );
        }
      } );

      // TODO: Switch to button listener--this will also clean up the keyboard accessibility
      largeScreenButton.addInputListener( {
        keydown: function( event ) {
          var keyCode = event.domEvent.keyCode;
          if ( keyCode === Input.KEY_ENTER || keyCode === Input.KEY_SPACE ) {
            sim.showHomeScreen = false;
            highlightedScreenIndexProperty.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.focusedTrailProperty.value = Input.getAllFocusableTrails()[ 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 ? 1.00 * 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.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.screenIndex = index;
          }
        }
      } );

      smallScreenButton.addInputListener( {
        keydown: function( event ) {
          var keyCode = event.domEvent.keyCode;
          if ( keyCode === Input.KEY_ENTER || keyCode === Input.KEY_SPACE ) {
            sim.screenIndex = index;
            sim.showHomeScreen = false;
            highlightedScreenIndexProperty.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.focusedTrailProperty.value = Input.getAllFocusableTrails()[ 0 ];
          }
        }
      } );

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

          //TODO: use named children instead of child indices?
          smallScreenButton.children[ 0 ].opacity = 1;
          smallScreenButton.children[ 1 ].fill = 'white';
        },
        out: function( event ) {
          highlightedScreenIndexProperty.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 } );
    homeScreenView.addChild( center );
    sim.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 = homeScreenView.layoutBounds.width / 2;
    } );

    this.phetButton = new PhetButton( sim, new Property( 'white' ), {
      tandem: options.tandem ? options.tandem.createTandem( 'phetButton' ) : null
    } );
    this.addChild( this.phetButton );

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

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

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

        //Position the phetButton.
        //It is tricky since it is in the coordinate frame of the HomeScreenView (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 );
        }
      }
    },

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


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

/**
 * A scenery node that is used to control which kit is selected within a kit
 * selection carousel node.  It contains the back and forward buttons and,
 * optionally, a title node.
 *
 * @author John Blanco
 * @author Sam Reid
 */

define( function( require ) {
  'use strict';

  // Imports
  var Color = require( 'SCENERY/util/Color' );
  var inherit = require( 'PHET_CORE/inherit' );
  var RoundShinyButton = require( 'SCENERY_PHET/RoundShinyButton' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Shape = require( 'KITE/Shape' );

  /**
   * @param {number} numKits
   * @param {Property} selectedKit - A property that tracks the selected kit as an integer
   * @param {Object} options
   * @constructor
   */
  function KitControlNode( numKits, selectedKit, options ) {
    Node.call( this );
    options = _.extend(
      {
        // Default values
        titleNode: null,
        inset: 5,
        buttonColor: 'rgb( 255, 184, 77 )',
        minButtonXSpace: 30
      }, options );

    var baseColor = new Color( 255, 204, 0 );
    var buttonOptions = {
      radius: 12,
      upFill: baseColor,
      overFill: baseColor.colorUtilsBrighter( 0.2 ),
      disabledFill: new Color( 180, 180, 180 ),
      downFill: baseColor.colorUtilsDarker( 0.2 ),
      xMargin: 5,
      yMargin: 3
    };

    var iconOptions = { stroke: 'black', lineWidth: 3, lineCap: 'round' };
    var nextIcon = new Path( new Shape().moveTo( 0, 0 ).lineTo( 5, 5 ).lineTo( 0, 10 ), iconOptions );
    var previousIcon = new Path( new Shape().moveTo( 0, 0 ).lineTo( -5, 5 ).lineTo( 0, 10 ), iconOptions );

    var nextKitButton = new RoundShinyButton( function() {
      selectedKit.value = selectedKit.value + 1;
    }, nextIcon, _.extend( { iconOffsetX: 1 }, buttonOptions ) );
    this.addChild( nextKitButton );

    var previousKitButton = new RoundShinyButton( function() {
      selectedKit.value = selectedKit.value - 1;
    }, previousIcon, _.extend( { iconOffsetX: -1 }, buttonOptions ) );
    this.addChild( previousKitButton );

    // Control button enabled state
    selectedKit.link( function( kitNum ) {
      nextKitButton.enabled = ( kitNum < numKits - 1 );
      previousKitButton.enabled = ( kitNum !== 0 );
    } );

    // Layout
    var interButtonXSpace = Math.max( options.minButtonXSpace, 2 * options.inset + ( options.titleNode === null ? 0 : options.titleNode.width ) );
    nextKitButton.left = previousKitButton.right + interButtonXSpace;
    if ( options.titleNode !== null ) {
      this.addChild( options.titleNode, { centerX: ( previousKitButton.centerX + nextKitButton.centerX ) / 2 } );
    }

    // If there is only one kit, show the title but not the control buttons.
    // Leave the buttons in the scene graph for keeping layout consistent.
    previousKitButton.visible = numKits > 1;
    nextKitButton.visible = numKits > 1;

    this.mutate( options );
  }

  return inherit( Node, KitControlNode );
} );
<commit_msg>Changed the touch area radius of the KitControlNode to be smaller so that it doesn't overlap with other controls, fixes #15.<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * A scenery node that is used to control which kit is selected within a kit
 * selection carousel node.  It contains the back and forward buttons and,
 * optionally, a title node.
 *
 * @author John Blanco
 * @author Sam Reid
 */

define( function( require ) {
  'use strict';

  // Imports
  var Color = require( 'SCENERY/util/Color' );
  var inherit = require( 'PHET_CORE/inherit' );
  var RoundShinyButton = require( 'SCENERY_PHET/RoundShinyButton' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Shape = require( 'KITE/Shape' );

  /**
   * @param {number} numKits
   * @param {Property} selectedKit - A property that tracks the selected kit as an integer
   * @param {Object} options
   * @constructor
   */
  function KitControlNode( numKits, selectedKit, options ) {
    Node.call( this );
    options = _.extend(
      {
        // Default values
        titleNode: null,
        inset: 5,
        buttonColor: 'rgb( 255, 184, 77 )',
        minButtonXSpace: 30
      }, options );

    var baseColor = new Color( 255, 204, 0 );
    var buttonOptions = {
      radius: 12,
      touchAreaRadius: 20,
      upFill: baseColor,
      overFill: baseColor.colorUtilsBrighter( 0.2 ),
      disabledFill: new Color( 180, 180, 180 ),
      downFill: baseColor.colorUtilsDarker( 0.2 ),
      xMargin: 5,
      yMargin: 3
    };

    var iconOptions = { stroke: 'black', lineWidth: 3, lineCap: 'round' };
    var nextIcon = new Path( new Shape().moveTo( 0, 0 ).lineTo( 5, 5 ).lineTo( 0, 10 ), iconOptions );
    var previousIcon = new Path( new Shape().moveTo( 0, 0 ).lineTo( -5, 5 ).lineTo( 0, 10 ), iconOptions );

    var nextKitButton = new RoundShinyButton( function() {
      selectedKit.value = selectedKit.value + 1;
    }, nextIcon, _.extend( { iconOffsetX: 1 }, buttonOptions ) );
    this.addChild( nextKitButton );

    var previousKitButton = new RoundShinyButton( function() {
      selectedKit.value = selectedKit.value - 1;
    }, previousIcon, _.extend( { iconOffsetX: -1 }, buttonOptions ) );
    this.addChild( previousKitButton );

    // Control button enabled state
    selectedKit.link( function( kitNum ) {
      nextKitButton.enabled = ( kitNum < numKits - 1 );
      previousKitButton.enabled = ( kitNum !== 0 );
    } );

    // Layout
    var interButtonXSpace = Math.max( options.minButtonXSpace, 2 * options.inset + ( options.titleNode === null ? 0 : options.titleNode.width ) );
    nextKitButton.left = previousKitButton.right + interButtonXSpace;
    if ( options.titleNode !== null ) {
      this.addChild( options.titleNode, { centerX: ( previousKitButton.centerX + nextKitButton.centerX ) / 2 } );
    }

    // If there is only one kit, show the title but not the control buttons.
    // Leave the buttons in the scene graph for keeping layout consistent.
    previousKitButton.visible = numKits > 1;
    nextKitButton.visible = numKits > 1;

    this.mutate( options );
  }

  return inherit( Node, KitControlNode );
} );
<|endoftext|>


<commit_before>var channelBuffer = {};
var currentChannel = null;

function getChannel(name){
	if(typeof channelBuffer[name] == "undefined"){
		return null;
	}

	return channelBuffer[name];
}

function appendChannel(name){
	channelBuffer[name] = new ChannelPage(name);
	savePage(channelBuffer[name]);
}

var ChannelPage = (function(){
	function ChannelPage(name){
		this.name = name;
		this.exit = false;
                this.cache = [];
                this.users = {};
                //wee wish to get the title from here. The command from the user should be /title. To set title /title title context
                var self = this;
                title(this.name, "");
                //wee get our data :)
                online(this.name, function(users){
                  for(var i=0;i<users.length;i++){
                     self.appendUser(users[i]);
                  }
                  //wee got all commands from appendUser in buffer let send them now
                  sendBuffer.flush();
                }, function(msg){
                     self.error(msg);
                });
                //wee got all the command wee need now in the buffer let send the commands now
                sendBuffer.flush();
	}

        ChannelPage.prototype.updateNick = function(old, n){
           if(typeof this.users[old] !== "undefined"){
                this.users[n] = this.users[old];
                delete this.users[old];
                this.appendHTML(language("%s change nick to %s", old, n));
           }
        };

	ChannelPage.prototype.removeInaktiv = function(name){
		if(typeof this.users[name] !== "undefined" && this.users[name].inaktiv()){
			this.users[name].inaktiv(false);
			var dom = document.getElementsByClassName("user");
			for(var i=0;i<dom.length;i++){
				if(dom[i].getAttribute("nick") == name){
					dom[i].getElementsByClassName("inaktiv")[0].style.display = "none";
					return;
				}
			}
		}
	}

	ChannelPage.prototype.makeInaktiv = function(name){
		if(typeof this.users[name] !== "undefined"){
			this.users[name].inaktiv(true);
			var dom = document.getElementsByClassName("user");
			for(var i=0;i<dom.length;i++){
				if(dom[i].getAttribute("nick") == name){
					dom[i].getElementsByClassName("inaktiv")[0].style.display = 'block';
					return;
				}
			}
		}else{
			console.log("Unknown user in inaktive: "+name);
		}
	};

        ChannelPage.prototype.appendUser = function(user){
                var self = this;
                userInfo(this.name, user, function(info){
                   self.appendOnlineList(self.users[user] = new UserData(user, info));
                   inaktiv(self.name, user, function(respons){
                	  if(respons.message() == "YES"){
                		  self.makeInaktiv(user);
                	  }
                   });
                   if(user == myNick){
                	   self.initLeftMenu(self.users[user]);
                   }
                   sendBuffer.flush();
               }, function(msg){
                   self.error(msg);
               });
        };

	ChannelPage.prototype.focus = function(){
		currentChannel = this;
		for(var nick in this.users){
			this.appendOnlineList(this.users[nick]);
		}

		//wee append context to the chat place
		for(var i=0;i<this.cache.length;i++){
			this.pushChat(this.cache[i]);
		}
	};

        ChannelPage.prototype.appendOnlineList = function(user){
            if(!pageFocus(this)){//if this channel is not on the focus wee dont add it
               return;
            }
            var html = "
" +
            "
[I]"+user.nick+"
";

            document.getElementById("online").innerHTML += html+"
";
        };

	ChannelPage.prototype.blur = function(){
		currentChannel = null;
	};

	ChannelPage.prototype.title = function(){
		return this.name;
	};

	ChannelPage.prototype.setTitle = function(title){
		this.appendHTML("Topic: "+title+"")
		this.t = title;
	};

        ChannelPage.prototype.write = function(msg){
            function template(user, context){
               return ""+user+": "+context+"";
            }

            switch(msg.command()){
               case "MESSAGE":
                 this.appendHTML(template(msg.nick(), parseMsg(msg.message())));
               break;
               case "TITLE":
            	   this.setTitle(msg.message());
               break;
               case "INAKTIV":
            	   console.log("["+msg.nick()+"]");
            	   if(msg.message() == "YES"){
            		   this.makeInaktiv(msg.nick());
            		   this.appendHTML(language("%s is inaktiv", msg.nick()));
            	   }else if(msg.message() == "NO"){
            		   this.removeInaktiv(msg.nick);
            		   this.appendHTML(language("%s is no longer inaktiv", msg.nick()));
            	   }
               break;
               case "FLOOD":
            	   this.error(language("You has type message to fast. Please wait a little couple of time and try agian"));
              break;
            }
        };

        ChannelPage.prototype.error = function(msg){
        	this.appendHTML("[Error]"+language(msg)+"");
        }

        ChannelPage.prototype.appendHTML = function(html){
        	var date = new Date();
            var n = "
["+controleDataNumber(date.getHours())+":"+controleDataNumber(date.getMinutes())+"]"+html+"
";
            this.cache.push(n);
            if(pageFocus(this)){
              this.pushChat(n);
            }
        };

    ChannelPage.prototype.pushChat = function(msg){
    	var dom = document.getElementById("chat");
    	dom.innerHTML += msg;
    	dom.scrollTop = dom.scrollHeight;
    };

	ChannelPage.prototype.onClose = function(){
		if(!this.exit){
			leave(this.name, function(){
				delete channelBuffer[this.name];
			}, function(respons){
				//this is error
				this.write("[Error]"+respons.message()+"");
			});
			sendBuffer.flush();
		}
	};

	ChannelPage.prototype.initLeftMenu = function(user){
		//title menu
		if(user.allowChangeTitle()){
			appendLeftMenu("
"+language("Change title")+"
");
		}
	};

	return ChannelPage;
})();

function parseMsg(msg){
   //first wee take all single single block first
   msg = msg.replace(/\[(.*)\/\]/g, function(all, item){
      switch(item){
         case "br":
           return "
";
      }
      return all;
   });

   msg = msg.replace(/([a-zA-Z]*)/g, function(all, nick){
	   if(currentChannel != null){
		   if(typeof currentChannel.users[nick] !== "undefined"){
			   //this is a user nick :)
			   return ""+nick+"";
		   }
	   }
	   return nick;
   });

   msg = msg.replace(/(#[a-zA-Z]*?)/g, function(all, channel){

   });

   //wee has a lot of bb code first [i][b][url]
   var regex = /\[([a-zA-Z]*)(.*?)\](.*?)\[\/\1\]/g;
   return msg.replace(regex, function(all, identify, extra, context){
	  switch(identify){
	  case "url":
		  return ""+parseMsg(context)+"";
	  case "b":
		  return ""+parseMsg(context)+"";
	  case "u":
		  return ""+parseMsg(context)+"";
	  }
	  console.log(arg)
	  return all;
   });
}

function controleDataNumber(n){
	if(n < 10){
		return "0"+n;
	}

	return n;
}
<commit_msg>Update channelcontrol.js<commit_after>var channelBuffer = {};
var currentChannel = null;

function getChannel(name){
	if(typeof channelBuffer[name] == "undefined"){
		return null;
	}

	return channelBuffer[name];
}

function appendChannel(name){
	channelBuffer[name] = new ChannelPage(name);
	savePage(channelBuffer[name]);
}

var ChannelPage = (function(){
	function ChannelPage(name){
		this.name = name;
		this.exit = false;
                this.cache = [];
                this.users = {};
                //wee wish to get the title from here. The command from the user should be /title. To set title /title title context
                var self = this;
                title(this.name, "");
                //wee get our data :)
                online(this.name, function(users){
                  for(var i=0;i<users.length;i++){
                     self.appendUser(users[i]);
                  }
                  //wee got all commands from appendUser in buffer let send them now
                  sendBuffer.flush();
                }, function(msg){
                     self.error(msg);
                });
                //wee got all the command wee need now in the buffer let send the commands now
                sendBuffer.flush();
	}

        ChannelPage.prototype.updateNick = function(old, n){
           if(typeof this.users[old] !== "undefined"){
                this.users[n] = this.users[old];
                delete this.users[old];
                this.appendHTML(language("%s change nick to %s", old, n));
           }
        };

	ChannelPage.prototype.removeInaktiv = function(name){
		if(typeof this.users[name] !== "undefined" && this.users[name].inaktiv()){
			this.users[name].inaktiv(false);
			var dom = document.getElementsByClassName("user");
			for(var i=0;i<dom.length;i++){
				if(dom[i].getAttribute("nick") == name){
					dom[i].getElementsByClassName("inaktiv")[0].style.display = "none";
					return;
				}
			}
		}
	}

	ChannelPage.prototype.makeInaktiv = function(name){
		if(typeof this.users[name] !== "undefined"){
			this.users[name].inaktiv(true);
			var dom = document.getElementsByClassName("user");
			for(var i=0;i<dom.length;i++){
				if(dom[i].getAttribute("nick") == name){
					dom[i].getElementsByClassName("inaktiv")[0].style.display = 'block';
					return;
				}
			}
		}else{
			console.log("Unknown user in inaktive: "+name);
		}
	};

        ChannelPage.prototype.appendUser = function(user){
                var self = this;
                userInfo(this.name, user, function(info){
                   self.appendOnlineList(self.users[user] = new UserData(user, info));
                   inaktiv(self.name, user, function(respons){
                	  if(respons.message() == "YES"){
                		  self.makeInaktiv(user);
                	  }
                   });
                   if(user == myNick){
                	   self.initLeftMenu(self.users[user]);
                   }
                   sendBuffer.flush();
               }, function(msg){
                   self.error(msg);
               });
        };

	ChannelPage.prototype.focus = function(){
		currentChannel = this;
		for(var nick in this.users){
			this.appendOnlineList(this.users[nick]);
		}

		//wee append context to the chat place
		for(var i=0;i<this.cache.length;i++){
			this.pushChat(this.cache[i]);
		}
	};

        ChannelPage.prototype.appendOnlineList = function(user){
            if(!pageFocus(this)){//if this channel is not on the focus wee dont add it
               return;
            }
            var html = "
" +
            "
[I]"+user.nick+"
";

            document.getElementById("online").innerHTML += html+"
";
        };

	ChannelPage.prototype.blur = function(){
		currentChannel = null;
	};

	ChannelPage.prototype.title = function(){
		return this.name;
	};

	ChannelPage.prototype.setTitle = function(title){
		this.appendHTML("Topic: "+title+"")
		this.t = title;
	};

        ChannelPage.prototype.write = function(msg){
            function template(user, context){
               return ""+user+": "+context+"";
            }

            switch(msg.command()){
               case "MESSAGE":
                 this.appendHTML(template(msg.nick(), parseMsg(msg.message())));
               break;
               case "TITLE":
            	   this.setTitle(msg.message());
               break;
               case "INAKTIV":
            	   console.log("["+msg.nick()+"]");
            	   if(msg.message() == "YES"){
            		   this.makeInaktiv(msg.nick());
            		   this.appendHTML(language("%s is inaktiv", msg.nick()));
            	   }else if(msg.message() == "NO"){
            		   this.removeInaktiv(msg.nick);
            		   this.appendHTML(language("%s is no longer inaktiv", msg.nick()));
            	   }
               break;
               case "FLOOD":
            	   this.error(language("You has type message to fast. Please wait a little couple of time and try again"));
               break;
               case "NICK":
                  this.updateNick(msg.nick(), msg.messasge());
               break;
            }
        };

        ChannelPage.prototype.error = function(msg){
        	this.appendHTML("[Error]"+language(msg)+"");
        }

        ChannelPage.prototype.appendHTML = function(html){
        	var date = new Date();
            var n = "
["+controleDataNumber(date.getHours())+":"+controleDataNumber(date.getMinutes())+"]"+html+"
";
            this.cache.push(n);
            if(pageFocus(this)){
              this.pushChat(n);
            }
        };

    ChannelPage.prototype.pushChat = function(msg){
    	var dom = document.getElementById("chat");
    	dom.innerHTML += msg;
    	dom.scrollTop = dom.scrollHeight;
    };

	ChannelPage.prototype.onClose = function(){
		if(!this.exit){
			leave(this.name, function(){
				delete channelBuffer[this.name];
			}, function(respons){
				//this is error
				this.write("[Error]"+respons.message()+"");
			});
			sendBuffer.flush();
		}
	};

	ChannelPage.prototype.initLeftMenu = function(user){
		//title menu
		if(user.allowChangeTitle()){
			appendLeftMenu("
"+language("Change title")+"
");
		}
	};

	return ChannelPage;
})();

function parseMsg(msg){
   //first wee take all single single block first
   msg = msg.replace(/\[(.*)\/\]/g, function(all, item){
      switch(item){
         case "br":
           return "
";
      }
      return all;
   });

   msg = msg.replace(/([a-zA-Z]*)/g, function(all, nick){
	   if(currentChannel != null){
		   if(typeof currentChannel.users[nick] !== "undefined"){
			   //this is a user nick :)
			   return ""+nick+"";
		   }
	   }
	   return nick;
   });

   msg = msg.replace(/(#[a-zA-Z]*?)/g, function(all, channel){

   });

   //wee has a lot of bb code first [i][b][url]
   var regex = /\[([a-zA-Z]*)(.*?)\](.*?)\[\/\1\]/g;
   return msg.replace(regex, function(all, identify, extra, context){
	  switch(identify){
	  case "url":
		  return ""+parseMsg(context)+"";
	  case "b":
		  return ""+parseMsg(context)+"";
	  case "u":
		  return ""+parseMsg(context)+"";
	  }
	  console.log(arg)
	  return all;
   });
}

function controleDataNumber(n){
	if(n < 10){
		return "0"+n;
	}

	return n;
}
<|endoftext|>


<commit_before>define(["backbone", "courses/views/SearchView", "courses/views/CoursesView", "courses/views/CourseView", "courses/collections/CourseCollection"], function(Backbone, SearchView, CoursesView, CourseView, Courses){
    var CoursesRouter = Backbone.SubRoute.extend({

        routes: {
            "": "search",
            "detail/:id": "course",
            ":query": "courses"
        },

        search: function(params) {
            console.log("Search called");
            results = new Courses();
            searchView = new SearchView({
				collection: results,
                params: params,
				router: this,
            });
            searchView.render();
        },

        courses: function(query, params) {
            console.log("Courses called");
            coursesView = new CoursesView({
                params: params,
                query: query
            });
            coursesView.render();
        },

        course: function(id, params) {
            console.log("Course called");
            courseView = new CourseView({
                params: params,
                id: id
            });
            courseView.render();
        }

    });

    return CoursesRouter;
});
<commit_msg>Removed console.log(s).<commit_after>define(["backbone", "courses/views/SearchView", "courses/views/CoursesView", "courses/views/CourseView", "courses/collections/CourseCollection"], function(Backbone, SearchView, CoursesView, CourseView, Courses){
    var CoursesRouter = Backbone.SubRoute.extend({

        routes: {
            "": "search",
            "detail/:id": "course",
            ":query": "courses"
        },

        search: function(params) {
            results = new Courses();
            searchView = new SearchView({
				collection: results,
                params: params,
				router: this,
            });
            searchView.render();
        },

        courses: function(query, params) {
            coursesView = new CoursesView({
                params: params,
                query: query
            });
            coursesView.render();
        },

        course: function(id, params) {
            courseView = new CourseView({
                params: params,
                id: id
            });
            courseView.render();
        }

    });

    return CoursesRouter;
});
<|endoftext|>


<commit_before>"use strict";

/*global module, require*/

var d3 = require("d3"),
    classes = require("./classes.js"),
    hideClass = classes.hide,
    dialogueClass = "floating-dialogue",
    closeButtonClass = "close-button",
    resizeButtonClass = "resize-dialgoue",
    bringToFrontClass = "bring-to-front",

    stickyness = 20,

    z = 10,
    getNextZ = function() {
	return z++;
    },

    noop = function() {
    },

    intersects1D = function(leftA, rightA, leftB, rightB) {
	if (leftA <= leftB) {
	    // A's sides either side of leftB
	    return rightA >= leftB;
	} else {
	    // B's sides either side of leftA
	    return leftA <= rightB
	    // A contained within B.
		|| rightA <= rightB;
	}
    },

    intersects = function(a, b) {
	return intersects1D(a.left, a.right, b.left, b.right)
	    && intersects1D(a.top, a.bottom, b.top, b.bottom);
    };

module.exports = function(container, getDataById, redraw, typeId, options, drawDialogueContent) {
    /*
     Properties used during drag action.
     */
    var originalBBox,
	originalCSS,
	dragDistance;

    /*
     Sets the size and position of the dialogue element based on the data.
     */
    var drawSizeAndPosition = function(el, datum) {
	var bbox = (options.lockToScreen || options.findSpace) ? el.node().getBoundingClientRect() : null,
	    manuallyPositioned = datum.manuallyPositioned(),
	    manuallySized = datum.manuallySized();

	drawSize(
	    el,
	    manuallySized,
	    manuallySized ? datum.getWidth() : null,
	    manuallySized ? datum.getHeight() : null,
	    bbox
	);

	drawPosition(
	    el,
	    manuallyPositioned,
	    manuallyPositioned ? datum.getLeft() : null,
	    manuallyPositioned ? datum.getTop() : null,
	    bbox);
    },

	drawPosition = function(el, manuallyPositioned, x, y, bbox) {
	    if (manuallyPositioned) {
		if (options.lockToScreen) {
		    // Make sure that our dialogue fits within the browser window.

		    var dx = x - bbox.left,
			dy = y - bbox.top;

		    x = bbox.left +
			Math.min(
			    dx,
			    window.innerWidth - bbox.right
			);

		    y = bbox.top +
			Math.min(
			    dy,
			    window.innerHeight - bbox.bottom
			);

		    x = Math.max(x, 0);
		    y = Math.max(y, 0);
		}

		el
		    .style("left", x + "px")
		    .style("top", y + "px")
		    .style("bottom", null)
		    .style("right", null);

	    } else {
		// Position the dialogue according to whatever CSS applies to it.
		el.style("left", null)
		    .style("top", null);
	    }
	},

	drawSize = function(el, manuallySized, width, height, bbox) {
	    if (manuallySized) {
		if (options.lockToScreen) {
		    var dWidth = width - bbox.width,
			dHeight = height - bbox.height;

		    width = bbox.width +
			Math.min(
			    dWidth,
			    window.innerWidth - bbox.right
			);

		    height = bbox.height +
			Math.min(
			    dHeight,
			    window.innerHeight - bbox.bottom
			);
		}

		// No need to set a minimum width, since this is already handled by a css property.

		el
		    .style("width", width + "px")
		    .style("height", height + "px");

	    } else {
		el.style("width", null)
		    .style("height", null);
	    }
	},

	maybeFindSpace = function(el, d) {
	    if (!d.manuallyPositioned()) {
		var dialogues = d3.selectAll("." + dialogueClass),
		    directionVector = [0, 1],
	    	    len = dialogues.size(),
	    	    i = 0,
		    bbox = el.node().getBoundingClientRect(),

	    	    x = parseInt(el.style("left")) || 0,
	    	    y = parseInt(el.style("top")) || 0;

	    	while (i < len) {
	    	    var dialogue = d3.select(dialogues[0][i]);

	    	    if (el.datum().id !== dialogue.datum().id) {
	    		var target = dialogue.node().getBoundingClientRect();

	    		if (intersects(bbox, target)) {
	    		    // Move far enough to avoid the box we're colliding with.
	    		    var dx = directionVector[0] >= 0 ? (target.right - bbox.left) : (target.left - bbox.right),
	    			dy = directionVector[1] >= 0 ? (target.bottom - bbox.top) : (target.right - bbox.left);

	    		    // Add a little clearance.
	    		    dx += 1;
	    		    dy += 1;

	    		    // Make sure we're going in the right direction.
	    		    dx *= directionVector[0];
	    		    dy *= directionVector[1];

	    		    x += dx;
	    		    y += dy;

	    		    el
	    			.style("left", x + "px")
	    			.style("top", y + "px")
	    			.style("right", null)
	    			.style("bottom", null);

	    		    bbox = el.node().getBoundingClientRect();
	    		    i = 0;
	    		}
	    	    }

	    	    i++;
		}
	    }
	},

	enlarge = function(bbox) {
	    return {
		left: bbox.left - stickyness,
		top: bbox.top - stickyness,
		right: bbox.right + stickyness,
		bottom: bbox.bottom + stickyness
	    };
	},

	maybeDockSide = function(modify, original, target) {
	    var val = target - original;

	    if (Math.abs(val) < stickyness) {
		modify(val);
		return false;
	    } else {
		return true;
	    }
	},

	maybeDock = function(id, bbox, modifyLeft, modifyTop, modifyRight, modifyBottom) {
	    if (options.sticky) {

		var stickHorizontal = true,
		    stickVertical = true,
		    enlarged = enlarge(bbox),
		    dialogues = d3.selectAll("." + dialogueClass),
		    i = 0,
		    len = dialogues.size();

		while ((stickHorizontal || stickVertical) && i < len) {
		    var dialogue = d3.select(
			dialogues[0][i]
		    );

		    if (dialogue.datum().getVisibility() && dialogue.datum().id !== id)  {
			var target = dialogue.node().getBoundingClientRect();

			if (intersects(enlarged, target)) {

			    if (stickHorizontal) {
				stickHorizontal = maybeDockSide(modifyLeft, bbox.left, target.right) && maybeDockSide(modifyRight, bbox.right, target.left)
				    && maybeDockSide(modifyLeft, bbox.left, target.left) && maybeDockSide(modifyRight, bbox.right, target.right);
			    }

			    if (stickVertical) {
				stickVertical = maybeDockSide(modifyTop, bbox.top, target.bottom) && maybeDockSide(modifyBottom, bbox.bottom, target.top)
				    && maybeDockSide(modifyTop, bbox.top, target.top) && maybeDockSide(modifyBottom, bbox.bottom, target.bottom);
			    }
			}
		    }

		    i++;
		}
	    }
	},

	/*
	 'typeId-dialogueId' if dialogue is present, otherwise just 'typeId'
	 */
	dialogueId = function(d) {
	    return typeId + (d.id ? ("-" + d.id) : "");
	},

	fromSelection = function(dialogues, newDialogues) {
	    newDialogues = newDialogues.append("div")
	    	.classed(dialogueClass, true)
		.classed(typeId, true)
		.attr("id", dialogueId);

	    dialogues.classed(hideClass, function(d, i) {
		return !d.getVisibility();
	    });

	    if (options.reposition) {
		newDialogues
	    	    .style("cursor", "move")
	    	    .call(
	    		d3.behavior.drag()
	    		    .origin(function() {
				var el = d3.select(this);

	    			originalBBox = this.getBoundingClientRect();
				originalCSS = [
				    parseInt(el.style("left")) || 0,
				    parseInt(el.style("top")) || 0
				];
				dragDistance = null;

	    			return {
	    			    x: 0,
	    			    y: 0
	    			};
	    		    })
	    		    .on("drag", function() {
	    			d3.event.sourceEvent.preventDefault();

	    			// Accumulated change over this whole drag gesture.
	    			dragDistance = [d3.event.x, d3.event.y];

	    			var el = d3.select(this),
				    translated = {
	    				left: originalBBox.left + dragDistance[0],
	    				right : originalBBox.right + dragDistance[0],
	    				top: originalBBox.top + dragDistance[1],
	    				bottom: originalBBox.bottom + dragDistance[1],
	    				width: originalBBox.width,
	    				height: originalBBox.height
	    			    };

				maybeDock(
				    el.datum().id,
				    translated,
				    function(leftChange) {
					dragDistance[0] += leftChange;
				    },
				    function(topChange) {
					dragDistance[1] += topChange;
				    },
				    function(rightChange) {
					dragDistance[0] += rightChange;
				    },
				    function(bottomChange) {
					dragDistance[1] += bottomChange;
				    }
				);

				drawPosition(
				    el,
				    true,
				    originalCSS[0] + dragDistance[0],
				    originalCSS[1] + dragDistance[1],
				    translated
				);
	    		    })
			    .on("dragend", function(d, i) {
				if (dragDistance) {
				    getDataById(d.id)
					.setPosition([
					    originalCSS[0] + dragDistance[0],
					    originalCSS[1] + dragDistance[1]
					]);
				}
			    })
		    );
	    }

	    if (options.resize) {
		var resizeBehaviour = d3.behavior.drag()
			.origin(function() {
			    var el = d3.select(this.parentNode);

			    originalBBox = el.node().getBoundingClientRect();
			    originalCSS = [
				parseInt(el.style("width")) || 0,
				parseInt(el.style("height")) || 0
			    ];
			    dragDistance = null;

			    return {
				x: 0,
				y: 0
			    };
			})
			.on("dragstart", function(d) {
			    d3.event.sourceEvent.stopPropagation();
			})
			.on("drag", function(d) {
			    // Accumulated change over this whole drag gesture.
			    var el = d3.select(this.parentNode),
				dragDistance = [d3.event.x, d3.event.y],
				translated = {
				    left: originalBBox.left,
				    top: originalBBox.top,
				    right: originalBBox.right + dragDistance[0],
				    bottom: originalBBox.bottom + dragDistance[1],
				    width: originalBBox.width + dragDistance[0],
				    height: originalBBox.height + dragDistance[1]
				};

			    maybeDock(
				el.datum().id,
				translated,
				noop,
				noop,
				function(deltaRight) {
				    dragDistance[0] += deltaRight;
				},
				function(deltaTop) {
				    dragDistance[1] += deltaTop;
				}
			    );

			    drawSize(
				d3.select(this.parentNode),
				true,
				originalCSS[0] + dragDistance[0],
				originalCSS[1] + dragDistance[1],
				translated
			    );
			})
			.on("dragend", function(d) {
			    if (dragDistance) {
				getDataById(d.id)
				    .setSize([
					originalCSS[0] + dragDistance[0],
					originalCSS[1] + dragDistance[1]
				    ]);
			    }
			});

		newDialogues
		    .append("span")
		    .classed("resize-dialogue-horizontal", true)
		    .classed("resize-dialogue", true)
		    .call(resizeBehaviour);

		newDialogues
		    .append("span")
		    .classed("resize-dialogue-vertical", true)
		    .classed("resize-dialogue", true)
		    .call(resizeBehaviour);
	    }

	    if (options.close) {
		var newCloseButtons = newDialogues
			.append("span")
			.classed("close-button", true)
			.html("X");

		newCloseButtons.on("click", function(d, i) {
		    var data = getDataById(d.id);
		    data.setVisibility(false);
		    redraw(data);
		});
	    }

	    if (options.bringToFront) {
		newDialogues
		    .append("span")
		    .classed("bring-to-front", true)
		    .text("TOP")
		    .on("click", function(d, i) {
			d3.select(this.parentNode)
			    .style("z-index", getNextZ());
		    });
	    }

	    dialogues
		.each(function(d, i) {
		    drawSizeAndPosition(d3.select(this), d);
		});

	    drawDialogueContent(dialogues, newDialogues);

	    if (options.findSpace) {
		dialogues.each(function(d, i) {
		    maybeFindSpace(d3.select(this), d);
		});
	    }
	};

    return {
	id: dialogueId,

	fromData: function(data) {
	    var dialogues = container.selectAll("." + typeId)
		    .data(
			data,
			function(d, i) {
			    return d.id || "";
			}
		    );

	    dialogues.exit().remove();

	    fromSelection(dialogues, dialogues.enter());
	},

	fromSelection: fromSelection
    };
};
<commit_msg>Fixed dialogue size changes not getting saved.<commit_after>"use strict";

/*global module, require*/

var d3 = require("d3"),
    classes = require("./classes.js"),
    hideClass = classes.hide,
    dialogueClass = "floating-dialogue",
    closeButtonClass = "close-button",
    resizeButtonClass = "resize-dialgoue",
    bringToFrontClass = "bring-to-front",

    stickyness = 20,

    z = 10,
    getNextZ = function() {
	return z++;
    },

    noop = function() {
    },

    intersects1D = function(leftA, rightA, leftB, rightB) {
	if (leftA <= leftB) {
	    // A's sides either side of leftB
	    return rightA >= leftB;
	} else {
	    // B's sides either side of leftA
	    return leftA <= rightB
	    // A contained within B.
		|| rightA <= rightB;
	}
    },

    intersects = function(a, b) {
	return intersects1D(a.left, a.right, b.left, b.right)
	    && intersects1D(a.top, a.bottom, b.top, b.bottom);
    };

module.exports = function(container, getDataById, redraw, typeId, options, drawDialogueContent) {
    /*
     Properties used during drag action.
     */
    var originalBBox,
	originalCSS,
	dragDistance;

    /*
     Sets the size and position of the dialogue element based on the data.
     */
    var drawSizeAndPosition = function(el, datum) {
	var bbox = (options.lockToScreen || options.findSpace) ? el.node().getBoundingClientRect() : null,
	    manuallyPositioned = datum.manuallyPositioned(),
	    manuallySized = datum.manuallySized();

	drawSize(
	    el,
	    manuallySized,
	    manuallySized ? datum.getWidth() : null,
	    manuallySized ? datum.getHeight() : null,
	    bbox
	);

	drawPosition(
	    el,
	    manuallyPositioned,
	    manuallyPositioned ? datum.getLeft() : null,
	    manuallyPositioned ? datum.getTop() : null,
	    bbox);
    },

	drawPosition = function(el, manuallyPositioned, x, y, bbox) {
	    if (manuallyPositioned) {
		if (options.lockToScreen) {
		    // Make sure that our dialogue fits within the browser window.

		    var dx = x - bbox.left,
			dy = y - bbox.top;

		    x = bbox.left +
			Math.min(
			    dx,
			    window.innerWidth - bbox.right
			);

		    y = bbox.top +
			Math.min(
			    dy,
			    window.innerHeight - bbox.bottom
			);

		    x = Math.max(x, 0);
		    y = Math.max(y, 0);
		}

		el
		    .style("left", x + "px")
		    .style("top", y + "px")
		    .style("bottom", null)
		    .style("right", null);

	    } else {
		// Position the dialogue according to whatever CSS applies to it.
		el.style("left", null)
		    .style("top", null);
	    }
	},

	drawSize = function(el, manuallySized, width, height, bbox) {
	    if (manuallySized) {
		if (options.lockToScreen) {
		    var dWidth = width - bbox.width,
			dHeight = height - bbox.height;

		    width = bbox.width +
			Math.min(
			    dWidth,
			    window.innerWidth - bbox.right
			);

		    height = bbox.height +
			Math.min(
			    dHeight,
			    window.innerHeight - bbox.bottom
			);
		}

		// No need to set a minimum width, since this is already handled by a css property.

		el
		    .style("width", width + "px")
		    .style("height", height + "px");

	    } else {
		el.style("width", null)
		    .style("height", null);
	    }
	},

	maybeFindSpace = function(el, d) {
	    if (!d.manuallyPositioned()) {
		var dialogues = d3.selectAll("." + dialogueClass),
		    directionVector = [0, 1],
	    	    len = dialogues.size(),
	    	    i = 0,
		    bbox = el.node().getBoundingClientRect(),

	    	    x = parseInt(el.style("left")) || 0,
	    	    y = parseInt(el.style("top")) || 0;

	    	while (i < len) {
	    	    var dialogue = d3.select(dialogues[0][i]);

	    	    if (el.datum().id !== dialogue.datum().id) {
	    		var target = dialogue.node().getBoundingClientRect();

	    		if (intersects(bbox, target)) {
	    		    // Move far enough to avoid the box we're colliding with.
	    		    var dx = directionVector[0] >= 0 ? (target.right - bbox.left) : (target.left - bbox.right),
	    			dy = directionVector[1] >= 0 ? (target.bottom - bbox.top) : (target.right - bbox.left);

	    		    // Add a little clearance.
	    		    dx += 1;
	    		    dy += 1;

	    		    // Make sure we're going in the right direction.
	    		    dx *= directionVector[0];
	    		    dy *= directionVector[1];

	    		    x += dx;
	    		    y += dy;

	    		    el
	    			.style("left", x + "px")
	    			.style("top", y + "px")
	    			.style("right", null)
	    			.style("bottom", null);

	    		    bbox = el.node().getBoundingClientRect();
	    		    i = 0;
	    		}
	    	    }

	    	    i++;
		}
	    }
	},

	enlarge = function(bbox) {
	    return {
		left: bbox.left - stickyness,
		top: bbox.top - stickyness,
		right: bbox.right + stickyness,
		bottom: bbox.bottom + stickyness
	    };
	},

	maybeDockSide = function(modify, original, target) {
	    var val = target - original;

	    if (Math.abs(val) < stickyness) {
		modify(val);
		return false;
	    } else {
		return true;
	    }
	},

	maybeDock = function(id, bbox, modifyLeft, modifyTop, modifyRight, modifyBottom) {
	    if (options.sticky) {

		var stickHorizontal = true,
		    stickVertical = true,
		    enlarged = enlarge(bbox),
		    dialogues = d3.selectAll("." + dialogueClass),
		    i = 0,
		    len = dialogues.size();

		while ((stickHorizontal || stickVertical) && i < len) {
		    var dialogue = d3.select(
			dialogues[0][i]
		    );

		    if (dialogue.datum().getVisibility() && dialogue.datum().id !== id)  {
			var target = dialogue.node().getBoundingClientRect();

			if (intersects(enlarged, target)) {

			    if (stickHorizontal) {
				stickHorizontal = maybeDockSide(modifyLeft, bbox.left, target.right) && maybeDockSide(modifyRight, bbox.right, target.left)
				    && maybeDockSide(modifyLeft, bbox.left, target.left) && maybeDockSide(modifyRight, bbox.right, target.right);
			    }

			    if (stickVertical) {
				stickVertical = maybeDockSide(modifyTop, bbox.top, target.bottom) && maybeDockSide(modifyBottom, bbox.bottom, target.top)
				    && maybeDockSide(modifyTop, bbox.top, target.top) && maybeDockSide(modifyBottom, bbox.bottom, target.bottom);
			    }
			}
		    }

		    i++;
		}
	    }
	},

	/*
	 'typeId-dialogueId' if dialogue is present, otherwise just 'typeId'
	 */
	dialogueId = function(d) {
	    return typeId + (d.id ? ("-" + d.id) : "");
	},

	fromSelection = function(dialogues, newDialogues) {
	    newDialogues = newDialogues.append("div")
	    	.classed(dialogueClass, true)
		.classed(typeId, true)
		.attr("id", dialogueId);

	    dialogues.classed(hideClass, function(d, i) {
		return !d.getVisibility();
	    });

	    if (options.reposition) {
		newDialogues
	    	    .style("cursor", "move")
	    	    .call(
	    		d3.behavior.drag()
	    		    .origin(function() {
				var el = d3.select(this);

	    			originalBBox = this.getBoundingClientRect();
				originalCSS = [
				    parseInt(el.style("left")) || 0,
				    parseInt(el.style("top")) || 0
				];
				dragDistance = null;

	    			return {
	    			    x: 0,
	    			    y: 0
	    			};
	    		    })
	    		    .on("drag", function() {
	    			d3.event.sourceEvent.preventDefault();

	    			// Accumulated change over this whole drag gesture.
	    			dragDistance = [d3.event.x, d3.event.y];

	    			var el = d3.select(this),
				    translated = {
	    				left: originalBBox.left + dragDistance[0],
	    				right : originalBBox.right + dragDistance[0],
	    				top: originalBBox.top + dragDistance[1],
	    				bottom: originalBBox.bottom + dragDistance[1],
	    				width: originalBBox.width,
	    				height: originalBBox.height
	    			    };

				maybeDock(
				    el.datum().id,
				    translated,
				    function(leftChange) {
					dragDistance[0] += leftChange;
				    },
				    function(topChange) {
					dragDistance[1] += topChange;
				    },
				    function(rightChange) {
					dragDistance[0] += rightChange;
				    },
				    function(bottomChange) {
					dragDistance[1] += bottomChange;
				    }
				);

				drawPosition(
				    el,
				    true,
				    originalCSS[0] + dragDistance[0],
				    originalCSS[1] + dragDistance[1],
				    translated
				);
	    		    })
			    .on("dragend", function(d, i) {
				if (dragDistance) {
				    getDataById(d.id)
					.setPosition([
					    originalCSS[0] + dragDistance[0],
					    originalCSS[1] + dragDistance[1]
					]);
				}
			    })
		    );
	    }

	    if (options.resize) {
		/*
		 The accumulated total drag distance over the whole event.
		 */
		var dragDistance = null,

		    resizeBehaviour = d3.behavior.drag()
			.origin(function() {
			    var el = d3.select(this.parentNode);

			    originalBBox = el.node().getBoundingClientRect();
			    originalCSS = [
				parseInt(el.style("width")) || 0,
				parseInt(el.style("height")) || 0
			    ];
			    dragDistance = null;

			    return {
				x: 0,
				y: 0
			    };
			})
			.on("dragstart", function(d) {
			    d3.event.sourceEvent.stopPropagation();
			})
			.on("drag", function(d) {
			    dragDistance = [d3.event.x, d3.event.y];

			    var el = d3.select(this.parentNode),
				translated = {
				    left: originalBBox.left,
				    top: originalBBox.top,
				    right: originalBBox.right + dragDistance[0],
				    bottom: originalBBox.bottom + dragDistance[1],
				    width: originalBBox.width + dragDistance[0],
				    height: originalBBox.height + dragDistance[1]
				};

			    maybeDock(
				el.datum().id,
				translated,
				noop,
				noop,
				function(deltaRight) {
				    dragDistance[0] += deltaRight;
				},
				function(deltaTop) {
				    dragDistance[1] += deltaTop;
				}
			    );

			    drawSize(
				d3.select(this.parentNode),
				true,
				originalCSS[0] + dragDistance[0],
				originalCSS[1] + dragDistance[1],
				translated
			    );
			})
			.on("dragend", function(d) {
			    if (dragDistance && dragDistance[0] !== 0 && dragDistance[1] !== 0) {
				getDataById(d.id)
				    .setSize([
					originalCSS[0] + dragDistance[0],
					originalCSS[1] + dragDistance[1]
				    ]);
			    }
			});

		newDialogues
		    .append("span")
		    .classed("resize-dialogue-horizontal", true)
		    .classed("resize-dialogue", true)
		    .call(resizeBehaviour);

		newDialogues
		    .append("span")
		    .classed("resize-dialogue-vertical", true)
		    .classed("resize-dialogue", true)
		    .call(resizeBehaviour);
	    }

	    if (options.close) {
		var newCloseButtons = newDialogues
			.append("span")
			.classed("close-button", true)
			.html("X");

		newCloseButtons.on("click", function(d, i) {
		    var data = getDataById(d.id);
		    data.setVisibility(false);
		    redraw(data);
		});
	    }

	    if (options.bringToFront) {
		newDialogues
		    .append("span")
		    .classed("bring-to-front", true)
		    .text("TOP")
		    .on("click", function(d, i) {
			d3.select(this.parentNode)
			    .style("z-index", getNextZ());
		    });
	    }

	    dialogues
		.each(function(d, i) {
		    drawSizeAndPosition(d3.select(this), d);
		});

	    drawDialogueContent(dialogues, newDialogues);

	    if (options.findSpace) {
		dialogues.each(function(d, i) {
		    maybeFindSpace(d3.select(this), d);
		});
	    }
	};

    return {
	id: dialogueId,

	fromData: function(data) {
	    var dialogues = container.selectAll("." + typeId)
		    .data(
			data,
			function(d, i) {
			    return d.id || "";
			}
		    );

	    dialogues.exit().remove();

	    fromSelection(dialogues, dialogues.enter());
	},

	fromSelection: fromSelection
    };
};
<|endoftext|>


<commit_before>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016-2017 The Regents of the University of California
 * Author: Jim Robinson
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * Created by dat on 4/8/18.
 */
var igv = (function (igv) {
    igv.FileLoadWidget = function ($buttonParent, $widgetParent) {
        var self = this,
            config,
            $header,
            $div;

        this.$parent = $widgetParent;

        this.fileLoadManager = new igv.FileLoadManager(this);

        // file load navbar button
        this.$presentationButton = $("<div>", { id:"igv-drag-and-drop-presentation-button", class:'igv-nav-bar-button' });
        $buttonParent.append(this.$presentationButton);

        this.$presentationButton.text('Load File');

        this.$presentationButton.on('click', function () {

            if (self.$container.is(':visible')) {
                doDismiss.call(self);
            } else {
                doPresent.call(self);
            }

        });

        // file load widget
        this.$container = $("<div>", { id:"igv-file-load-widget-container" });
        $widgetParent.append(this.$container);

        // header
        $header = $("<div>", { id:"igv-file-load-widget-header" });
        this.$container.append($header);
        // header - dismiss button
        igv.attachDialogCloseHandlerWithParent($header, function () {
            doDismiss.call(self);
        });

        // local data/index
        config =
            {
                dataTitle: 'Local data file',
                indexTitle: 'Local index file'
            };
        createInputContainer.call(this, this.$container, config);

        // url data/index
        config =
            {
                doURL: true,
                dataTitle: 'Data URL',
                indexTitle: 'Index URL'
            };
        createInputContainer.call(this, this.$container, config);

        // error message container
        this.$error_message = $("<div>", { id:"igv-flw-error-message-container" });
        this.$container.append(this.$error_message);
        // error message
        this.$error_message.append($("<div>", { id:"igv-flw-error-message" }));
        // error dismiss button
        igv.attachDialogCloseHandlerWithParent(this.$error_message, function () {
            self.dismissErrorMessage();
        });
        this.dismissErrorMessage();

        // ok | cancel - container
        $div = $("<div>", { id:"igv-file-load-widget-ok-cancel" });
        this.$container.append($div);

        // ok
        this.$ok = $('<div>');
        $div.append(this.$ok);
        this.$ok.text('OK');
        this.$ok.on('click', function () {
            var config;
            config = self.fileLoadManager.trackLoadConfiguration();
            if (config) {
                igv.browser.loadTrackList( [ config ] );
                doDismiss.call(self);
            }

        });

        // cancel
        this.$cancel = $('<div>');
        $div.append(this.$cancel);
        this.$cancel.text('Cancel');
        this.$cancel.on('click', function () {
            doDismiss.call(self);
        });

        this.$container.hide();

    };

    igv.FileLoadWidget.prototype.presentErrorMessage = function(message) {
        this.$error_message.find('#igv-flw-error-message').text(message);
        this.$error_message.show();
    };

    igv.FileLoadWidget.prototype.dismissErrorMessage = function() {
        this.$error_message.hide();
        this.$error_message.find('#igv-flw-error-message').text('');
    };

    function createInputContainer($parent, config) {
        var $container,
            $input_data_row,
            $input_index_row,
            $label;

        // container
        $container = $("<div>", { class:"igv-flw-input-container" });
        $parent.append($container);

        // data
        $input_data_row = $("<div>", { class:"igv-flw-input-row" });
        $container.append($input_data_row);
        // label
        $label = $("<div>", { class:"igv-flw-input-label" });
        $input_data_row.append($label);
        $label.text(config.dataTitle);

        if (true === config.doURL) {
            createURLContainer.call(this, $input_data_row, 'igv-flw-data-url', false);
        } else {
            createLocalFileContainer.call(this, $input_data_row, 'igv-flw-local-data-file', false);
        }

        // index
        $input_index_row = $("<div>", { class:"igv-flw-input-row" });
        $container.append($input_index_row);
        // label
        $label = $("<div>", { class:"igv-flw-input-label" });
        $input_index_row.append($label);
        $label.text(config.indexTitle);

        if (true === config.doURL) {
            createURLContainer.call(this, $input_index_row, 'igv-flw-index-url', true);
        } else {
            createLocalFileContainer.call(this, $input_index_row, 'igv-flw-local-index-file', true);
        }

    }

    function createURLContainer($parent, id, isIndexFile) {
        var self = this,
            $data_drop_target,
            $input;

        $input = $('<input>', { type:'text', placeholder:(true === isIndexFile ? 'Enter index URL' : 'Enter data URL') });
        $parent.append($input);

        $input.on('change', function (e) {
            self.fileLoadManager.dictionary[ true === isIndexFile ? 'index' : 'data' ] = $(this).val();
        });

        $data_drop_target = $("<div>", { class:"igv-flw-drag-drop-target" });
        $parent.append($data_drop_target);
        $data_drop_target.text('or drop URL');

        $parent
            .on('drag dragstart dragend dragover dragenter dragleave drop', function (e) {
                e.preventDefault();
                e.stopPropagation();
            })
            .on('dragover dragenter', function () {
                $(this).addClass('igv-flw-input-row-hover-state');
            })
            .on('dragleave dragend drop', function () {
                $(this).removeClass('igv-flw-input-row-hover-state');
            })
            .on('drop', function (e) {
                var url;
                url = e.originalEvent.dataTransfer.getData('text/uri-list');
                self.fileLoadManager.dictionary[ true === isIndexFile ? 'index' : 'data' ] = url;
                $input.val(url);
            });

    }

    function createLocalFileContainer($parent, id, isIndexFile) {
        var self = this,
            $file_chooser_container,
            $data_drop_target,
            $label,
            $input,
            $file_name;

        $file_chooser_container = $("<div>", { class:"igv-flw-file-chooser-container" });
        $parent.append($file_chooser_container);

        $label = $('<label>', { for:id });
        $file_chooser_container.append($label);
        $label.text('Choose file...');

        $input = $('<input>', { class:"igv-flw-file-chooser-input", id:id, name:id, type:'file' });
        $file_chooser_container.append($input);

        $data_drop_target = $("<div>", { class:"igv-flw-drag-drop-target" });
        $parent.append($data_drop_target);
        $data_drop_target.text('or drop file');

        $file_name = $("<div>", { class:"igv-flw-local-file-name-container" });
        $parent.append($file_name);

        $file_name.hide();

        $input.on('change', function (e) {
            self.fileLoadManager.dictionary[ true === isIndexFile ? 'index' : 'data' ] = e.target.files[ 0 ];
            $file_name.text(e.target.files[ 0 ].name);
            $file_name.show();
        });

        $parent
            .on('drag dragstart dragend dragover dragenter dragleave drop', function (e) {
                e.preventDefault();
                e.stopPropagation();
            })
            .on('dragover dragenter', function () {
                $(this).addClass('igv-flw-input-row-hover-state');
            })
            .on('dragleave dragend drop', function () {
                $(this).removeClass('igv-flw-input-row-hover-state');
            })
            .on('drop', function (e) {
                var f;
                f = e.originalEvent.dataTransfer.files[ 0 ];
                self.fileLoadManager.dictionary[ true === isIndexFile ? 'index' : 'data' ] = f;
                $file_name.text(f.name);
                $file_name.show();
            });

    }

    function doDismiss() {
        this.$container.find('input').val(undefined);
        this.$container.find('.igv-flw-local-file-name-container').hide();
        this.$container.hide();
    }

    function doPresent() {

        var obj;

        obj =
            {
                left: (this.$parent.width() - this.$container.width())/2,
                top: (this.$parent.height() - this.$container.height())/2

            };
        this.$container.css(obj);

        this.$container.show();
    }

    igv.FileLoadManager = function (fileLoadWidget) {

        this.fileLoadWidget = fileLoadWidget;

        this.dictionary = {};

        this.keyToIndexExtension =
            {
                bam: { extension: 'bai', optional: false },
                any: { extension: 'idx', optional: true  },
                gz: { extension: 'tbi', optional: true  }
            };

        this.indexExtensionToKey = _.invert(_.mapObject(this.keyToIndexExtension, function (val) {
            return val.extension;
        }));

    };

    igv.FileLoadManager.prototype.trackLoadConfiguration = function () {
        var extension,
            key,
            config,
            _isIndexFile,
            _isIndexable,
            indexFileStatus;

        if (undefined === this.dictionary.data) {
            this.fileLoadWidget.presentErrorMessage('Error: No data file');
            return undefined;
        } else {

            _isIndexFile = isAnIndexFile(this.dictionary.data);
            if (true === _isIndexFile) {
                this.fileLoadWidget.presentErrorMessage('Error: data file is in fact an index file.');
                return undefined;
            } else {

                _isIndexable = isIndexable(this.dictionary.data);

                extension = igv.getExtension({ url: this.dictionary.data });

                key = (this.keyToIndexExtension[ extension ]) ? extension : 'any';

                indexFileStatus = this.keyToIndexExtension[ key ];

                if (true === _isIndexable && false === indexFileStatus.optional) {

                    if (undefined === this.dictionary.index) {
                        this.fileLoadWidget.presentErrorMessage('Error: index file must be provided.');
                        return undefined;

                    } else {
                        return { url: this.dictionary.data, indexURL: this.dictionary.index }
                    }

                } else {

                    config =
                        {
                            url: this.dictionary.data,
                            indexURL: this.dictionary.index || undefined
                        };

                    if (undefined === this.dictionary.index) {
                        config.indexed = false;
                    }

                    return config;
                }
            }

        }

    };

    function isAnIndexFile(fileOrURL) {
        var extension;

        extension = igv.getExtension({ url: fileOrURL });
        return _.contains(_.keys(this.indexExtensionToKey), extension);
    }

    function isIndexable(fileOrURL) {

        var extension;

        if (true === isAnIndexFile(fileOrURL)) {
            return false;
        } else {
            extension = igv.getExtension({ url: fileOrURL });
            return (extension !== 'wig' && extension !== 'seg');
        }

    }

    return igv;
})(igv || {});<commit_msg>generalize drag drop. url or file can be dragged onto any drag target.<commit_after>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016-2017 The Regents of the University of California
 * Author: Jim Robinson
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * Created by dat on 4/8/18.
 */
var igv = (function (igv) {
    igv.FileLoadWidget = function ($buttonParent, $widgetParent) {
        var self = this,
            config,
            $header,
            $div;

        this.$parent = $widgetParent;

        this.fileLoadManager = new igv.FileLoadManager(this);

        // file load navbar button
        this.$presentationButton = $("<div>", { id:"igv-drag-and-drop-presentation-button", class:'igv-nav-bar-button' });
        $buttonParent.append(this.$presentationButton);

        this.$presentationButton.text('Load File');

        this.$presentationButton.on('click', function () {

            if (self.$container.is(':visible')) {
                doDismiss.call(self);
            } else {
                doPresent.call(self);
            }

        });

        // file load widget
        this.$container = $("<div>", { id:"igv-file-load-widget-container" });
        $widgetParent.append(this.$container);

        // header
        $header = $("<div>", { id:"igv-file-load-widget-header" });
        this.$container.append($header);
        // header - dismiss button
        igv.attachDialogCloseHandlerWithParent($header, function () {
            doDismiss.call(self);
        });

        // local data/index
        config =
            {
                dataTitle: 'Local data file',
                indexTitle: 'Local index file'
            };
        createInputContainer.call(this, this.$container, config);

        // url data/index
        config =
            {
                doURL: true,
                dataTitle: 'Data URL',
                indexTitle: 'Index URL'
            };
        createInputContainer.call(this, this.$container, config);

        // error message container
        this.$error_message = $("<div>", { id:"igv-flw-error-message-container" });
        this.$container.append(this.$error_message);
        // error message
        this.$error_message.append($("<div>", { id:"igv-flw-error-message" }));
        // error dismiss button
        igv.attachDialogCloseHandlerWithParent(this.$error_message, function () {
            self.dismissErrorMessage();
        });
        this.dismissErrorMessage();

        // ok | cancel - container
        $div = $("<div>", { id:"igv-file-load-widget-ok-cancel" });
        this.$container.append($div);

        // ok
        this.$ok = $('<div>');
        $div.append(this.$ok);
        this.$ok.text('OK');
        this.$ok.on('click', function () {
            var config;
            config = self.fileLoadManager.trackLoadConfiguration();
            if (config) {
                igv.browser.loadTrackList( [ config ] );
                doDismiss.call(self);
            }

        });

        // cancel
        this.$cancel = $('<div>');
        $div.append(this.$cancel);
        this.$cancel.text('Cancel');
        this.$cancel.on('click', function () {
            doDismiss.call(self);
        });

        this.$container.hide();

    };

    igv.FileLoadWidget.prototype.presentErrorMessage = function(message) {
        this.$error_message.find('#igv-flw-error-message').text(message);
        this.$error_message.show();
    };

    igv.FileLoadWidget.prototype.dismissErrorMessage = function() {
        this.$error_message.hide();
        this.$error_message.find('#igv-flw-error-message').text('');
    };

    function createInputContainer($parent, config) {
        var $container,
            $input_data_row,
            $input_index_row,
            $label;

        // container
        $container = $("<div>", { class:"igv-flw-input-container" });
        $parent.append($container);

        // data
        $input_data_row = $("<div>", { class:"igv-flw-input-row" });
        $container.append($input_data_row);
        // label
        $label = $("<div>", { class:"igv-flw-input-label" });
        $input_data_row.append($label);
        $label.text(config.dataTitle);

        if (true === config.doURL) {
            createURLContainer.call(this, $input_data_row, 'igv-flw-data-url', false);
        } else {
            createLocalFileContainer.call(this, $input_data_row, 'igv-flw-local-data-file', false);
        }

        // index
        $input_index_row = $("<div>", { class:"igv-flw-input-row" });
        $container.append($input_index_row);
        // label
        $label = $("<div>", { class:"igv-flw-input-label" });
        $input_index_row.append($label);
        $label.text(config.indexTitle);

        if (true === config.doURL) {
            createURLContainer.call(this, $input_index_row, 'igv-flw-index-url', true);
        } else {
            createLocalFileContainer.call(this, $input_index_row, 'igv-flw-local-index-file', true);
        }

    }

    function createURLContainer($parent, id, isIndexFile) {
        var self = this,
            $data_drop_target,
            $input;

        $input = $('<input>', { type:'text', placeholder:(true === isIndexFile ? 'Enter index URL' : 'Enter data URL') });
        $parent.append($input);

        $input.on('change', function (e) {
            self.fileLoadManager.dictionary[ true === isIndexFile ? 'index' : 'data' ] = $(this).val();
        });

        $data_drop_target = $("<div>", { class:"igv-flw-drag-drop-target" });
        $parent.append($data_drop_target);
        $data_drop_target.text('or drop URL');

        $parent
            .on('drag dragstart dragend dragover dragenter dragleave drop', function (e) {
                e.preventDefault();
                e.stopPropagation();
            })
            .on('dragover dragenter', function () {
                $(this).addClass('igv-flw-input-row-hover-state');
            })
            .on('dragleave dragend drop', function () {
                $(this).removeClass('igv-flw-input-row-hover-state');
            })
            .on('drop', function (e) {
                self.fileLoadManager.ingestDataTransfer(e.originalEvent.dataTransfer, isIndexFile);
                $input.val(isIndexFile ? self.fileLoadManager.indexName() : self.fileLoadManager.dataName());
            });

    }

    function createLocalFileContainer($parent, id, isIndexFile) {
        var self = this,
            $file_chooser_container,
            $data_drop_target,
            $label,
            $input,
            $file_name;

        $file_chooser_container = $("<div>", { class:"igv-flw-file-chooser-container" });
        $parent.append($file_chooser_container);

        $label = $('<label>', { for:id });
        $file_chooser_container.append($label);
        $label.text('Choose file...');

        $input = $('<input>', { class:"igv-flw-file-chooser-input", id:id, name:id, type:'file' });
        $file_chooser_container.append($input);

        $data_drop_target = $("<div>", { class:"igv-flw-drag-drop-target" });
        $parent.append($data_drop_target);
        $data_drop_target.text('or drop file');

        $file_name = $("<div>", { class:"igv-flw-local-file-name-container" });
        $parent.append($file_name);

        $file_name.hide();

        $input.on('change', function (e) {
            self.fileLoadManager.dictionary[ true === isIndexFile ? 'index' : 'data' ] = e.target.files[ 0 ];
            $file_name.text(e.target.files[ 0 ].name);
            $file_name.show();
        });

        $parent
            .on('drag dragstart dragend dragover dragenter dragleave drop', function (e) {
                e.preventDefault();
                e.stopPropagation();
            })
            .on('dragover dragenter', function () {
                $(this).addClass('igv-flw-input-row-hover-state');
            })
            .on('dragleave dragend drop', function () {
                $(this).removeClass('igv-flw-input-row-hover-state');
            })
            .on('drop', function (e) {
                self.fileLoadManager.ingestDataTransfer(e.originalEvent.dataTransfer, isIndexFile);
                $file_name.text(isIndexFile ? self.fileLoadManager.indexName() : self.fileLoadManager.dataName());
                $file_name.show();
            });

    }

    function doDismiss() {
        this.$container.find('input').val(undefined);
        this.$container.find('.igv-flw-local-file-name-container').hide();
        this.$container.hide();

        this.fileLoadManager.reset();
    }

    function doPresent() {

        var obj;

        obj =
            {
                left: (this.$parent.width() - this.$container.width())/2,
                top: (this.$parent.height() - this.$container.height())/2

            };
        this.$container.css(obj);

        this.$container.show();
    }

    igv.FileLoadManager = function (fileLoadWidget) {

        this.fileLoadWidget = fileLoadWidget;

        this.dictionary = {};

        this.keyToIndexExtension =
            {
                bam: { extension: 'bai', optional: false },
                any: { extension: 'idx', optional: true  },
                gz: { extension: 'tbi', optional: true  }
            };

        this.indexExtensionToKey = _.invert(_.mapObject(this.keyToIndexExtension, function (val) {
            return val.extension;
        }));

    };

    igv.FileLoadManager.prototype.ingestDataTransfer = function (dataTransfer, isIndexFile) {
        var url,
            files;

        url = dataTransfer.getData('text/uri-list');
        files = dataTransfer.files;

        if (files && files.length > 0) {
            this.dictionary[ true === isIndexFile ? 'index' : 'data' ] = files[ 0 ];
        } else if (url && '' !== url) {
            this.dictionary[ true === isIndexFile ? 'index' : 'data' ] = url;
        }

    };

    igv.FileLoadManager.prototype.indexName = function () {
        return itemName(this.dictionary.index);
    };

    igv.FileLoadManager.prototype.dataName = function () {
        return itemName(this.dictionary.data);
    };

    function itemName (item) {
        return igv.isFilePath(item) ? item.name : item;
    }

    igv.FileLoadManager.prototype.reset = function () {
        this.dictionary = {};
    };

    igv.FileLoadManager.prototype.trackLoadConfiguration = function () {
        var extension,
            key,
            config,
            _isIndexFile,
            _isIndexable,
            indexFileStatus;

        if (undefined === this.dictionary.data) {
            this.fileLoadWidget.presentErrorMessage('Error: No data file');
            return undefined;
        } else {

            _isIndexFile = isAnIndexFile(this.dictionary.data);
            if (true === _isIndexFile) {
                this.fileLoadWidget.presentErrorMessage('Error: data file is in fact an index file.');
                return undefined;
            } else {

                _isIndexable = isIndexable(this.dictionary.data);

                extension = igv.getExtension({ url: this.dictionary.data });

                key = (this.keyToIndexExtension[ extension ]) ? extension : 'any';

                indexFileStatus = this.keyToIndexExtension[ key ];

                if (true === _isIndexable && false === indexFileStatus.optional) {

                    if (undefined === this.dictionary.index) {
                        this.fileLoadWidget.presentErrorMessage('Error: index file must be provided.');
                        return undefined;

                    } else {
                        return { url: this.dictionary.data, indexURL: this.dictionary.index }
                    }

                } else {

                    config =
                        {
                            url: this.dictionary.data,
                            indexURL: this.dictionary.index || undefined
                        };

                    if (undefined === this.dictionary.index) {
                        config.indexed = false;
                    }

                    return config;
                }
            }

        }

    };

    function isAnIndexFile(fileOrURL) {
        var extension;

        extension = igv.getExtension({ url: fileOrURL });
        return _.contains(_.keys(this.indexExtensionToKey), extension);
    }

    function isIndexable(fileOrURL) {

        var extension;

        if (true === isAnIndexFile(fileOrURL)) {
            return false;
        } else {
            extension = igv.getExtension({ url: fileOrURL });
            return (extension !== 'wig' && extension !== 'seg');
        }

    }

    return igv;
})(igv || {});<|endoftext|>