<commit_before>/* =============================================================================

  Product: PoziTone module for SoundCloud
  Author: PoziWorld
  Copyright: (c) 2016 PoziWorld
  License: pozitone.com/license

  Table of Contents:

    PageWatcher
      init()
      initObserver()
      initPlayerStatusObserver()
      initTrackTitleContainerWrapperObserver()
      initFavoriteBtnObserver()
      addRuntimeOnMessageListener()
      convertNotificationLogoUrl()
      sendMediaEvent()
      triggerButtonClick()
      triggerPlayerAction_favorite()
      triggerPlayerAction_next()
      triggerPlayerAction_previous()
      triggerPlayerAction_playStop()
      triggerPlayerAction_mute()
      triggerPlayerAction_unmute()
      triggerPlayerAction_muteUnmute()
      triggerPlayerAction_showNotification()
      isMuted()

 ============================================================================ */

( function() {
  'use strict';

  function PageWatcher() {
    const
        strModule = 'com_soundcloud'
      , strImgPath = 'modules/' + strModule + '/img/'
      ;

    /* DOM-related variables */
    this.$player = document.querySelector( '.playControls' );
    this.$playPauseBtn = document.querySelector( '.playControl' );
    this.$playNextTrackBtn = document.querySelector( '.skipControl__next' );
    this.$playPreviousTrackBtn = document.querySelector( '.skipControl__previous' );
    this.$muteUnmuteBtn = document.querySelector( '.volume__button' );
    this.$muteUnmuteBtnContainer = document.querySelector( '.volume' );
    this.$trackTitleContainerWrapper = document.querySelector( '.playbackSoundBadge' );

    this.strPlayerVisibleClass = 'm-visible';
    this.strPlayPauseBtnPlayingClass = 'playing';
    this.strMuteUnmuteBtnContainerMutedClass = 'muted';
    this.strTrackTitleContainerClass = 'playbackSoundBadge__titleContextContainer';

    this.strUserMenuSelector = '.header__userNav';
    this.strFavoriteBtnSelector = '.playbackSoundBadge__like';
    this.strFavoriteBtnSuccessClass = 'sc-button-selected';

    this.DisconnectableObserver = null;
    /* DOM-related variables - END */

    this.boolIsUserLoggedIn = false;
    this.boolHadPlayedBefore = false;
    this.boolWasPageJustLoaded = true;
    this.boolDisregardSameMessage = true;

    this.objPlayerInfo = {
        strModule : strModule
      , boolIsReady : true
      , boolIsPlaying : false
      , boolIsMuted : false
      , intVolume : 10 // site uses 0-10 scale
      , boolCanPlayNextTrackLoggedOut : true
      , boolCanPlayPreviousTrackLoggedOut : true
    };

    this.objStationInfo = {
        strStationName : 'SoundCloud' // TODO: Change
      , strStationNamePlusDesc : document.title // TODO: Change
      , strLogoUrl : '/' + strImgPath + 'soundcloud-logo-48.svg'
      , strLogoDataUri : strImgPath + 'soundcloud-logo-80.png'
      , strTrackInfo : ''
      , strAdditionalInfo : ''
      , boolHasAddToPlaylistButton : false
    };

    this.init();
  }

  /**
   * Set event listeners, initialize API.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.init = function () {
    var self = this;

    self.initPlayerStatusObserver();
    self.addRuntimeOnMessageListener();
    pozitoneModule.api.init( 'built-in', self );
    self.convertNotificationLogoUrl();
  };

  /**
   * Init observer.
   *
   * @type    method
   * @param   $target
   *            The Node on which to observe DOM mutations.
   * @param   objOptions
   *            A MutationObserverInit object, specifies which DOM mutations
   *            should be reported.
   * @param   funcCallback
   *            The function which will be called on each DOM mutation.
   * @param   boolIsDisconnectable
   *            If this observer should be disconnected later.
   * @return  void
   **/

  PageWatcher.prototype.initObserver = function (
      $target
    , objOptions
    , funcCallback
    , boolIsDisconnectable
  ) {
    var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

    if (  typeof boolIsDisconnectable === 'undefined'
      &&  ! boolIsDisconnectable
    ) {
      var observer = new MutationObserver( funcCallback );

      observer.observe( $target, objOptions );
    }
    else {
      // Disconnect the one set previously
      if ( this.DisconnectableObserver ) {
        this.DisconnectableObserver.disconnect();
      }

      this.DisconnectableObserver = new MutationObserver( funcCallback );
      this.DisconnectableObserver.observe( $target, objOptions );
    }
  };

  /**
   * Init player status changes observer.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.initPlayerStatusObserver = function() {
    var self = this
      , objOptions = {
            attributes : true
          , attributeFilter : [ 'class' ]
        }
      , funcCallback  = function( arrMutations ) {
          for ( var i = 0, l = arrMutations.length; i < l; i++ ) {
            var $target = arrMutations[ i ].target;

            var boolIsPlaying = $target.classList.contains( self.strPlayPauseBtnPlayingClass )
              , strUpdatedPreviousStatus = boolIsPlaying ? 'play' : 'stop'
              ;

            // Sometimes mutation happens even without player status change
            if ( strUpdatedPreviousStatus === self.objPlayerInfo.strPreviousStatus ) {
              return;
            }

            self.objPlayerInfo.boolIsPlaying = boolIsPlaying;

            // Follow the 101.ru logic
            self.objPlayerInfo.strPreviousStatus = strUpdatedPreviousStatus;

            if ( boolIsPlaying ) {
              var strLangStartedOrResumed = 'onPlay';

              if ( self.boolWasPageJustLoaded ) {
                strLangStartedOrResumed = 'onFirstPlay';

                self.initTrackTitleContainerWrapperObserver();
              }

              self.sendMediaEvent( strLangStartedOrResumed );

              self.boolHadPlayedBefore = true;
              self.boolWasPageJustLoaded = false;
            }
            else if ( ! boolIsPlaying && self.boolHadPlayedBefore ) {
              self.sendMediaEvent( 'onPause', undefined, false );
            }

            return;
          }
        }
      ;

    self.initObserver( self.$playPauseBtn, objOptions, funcCallback );
  };

  /**
   * Init track title container observer.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.initTrackTitleContainerWrapperObserver = function () {
    var self = this
      , objOptions = {
            characterData : true
          , childList : true
          , subtree : true
        }
      , funcCallback  = function( arrMutations ) {
          for ( var i = 0, l = arrMutations.length; i < l; i++ ) {
            var objMutationRecord = arrMutations[ i ]
              , arrAddedNodes = objMutationRecord.addedNodes
              , intAddedNodesCount = arrAddedNodes.length
              ;

            if ( intAddedNodesCount ) {
              for ( var j = 0; i < intAddedNodesCount; i++ ) {
                var arrClassList = arrAddedNodes[ i ].classList;

                if (  arrClassList
                  &&  arrClassList.contains( self.strTrackTitleContainerClass )
                  &&  ! self.boolWasPageJustLoaded
                ) {
                  self.sendMediaEvent( undefined, undefined, true, false );
                }

                self.initFavoriteBtnObserver();
              }
            }
          }
        }
      ;

    self.initObserver( self.$trackTitleContainerWrapper, objOptions, funcCallback );
  };

  /**
   * Init "Favorite/Like" button observer.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.initFavoriteBtnObserver = function () {
    var self = this
      , DisconnectableObserver = self.DisconnectableObserver
      , $target = document.querySelector( self.strFavoriteBtnSelector )
      , objOptions = {
            attributes : true
          , attributeFilter : [ 'class' ]
        }
      , funcCallback  = function( arrMutations ) {
          for ( var i = 0, l = arrMutations.length; i < l; i++ ) {
            if ( arrMutations[ i ].target.classList.contains( self.strFavoriteBtnSuccessClass ) ) {
              self.sendMediaEvent( 'onFavorite', undefined, false );

              return;
            }
          }
        }
      ;

    // Reset "Favorite/Like" button observer
    if ( DisconnectableObserver ) {
      DisconnectableObserver.disconnect();
    }

    self.initObserver( $target, objOptions, funcCallback, true );
  };

  /**
   * Listen for commands sent from PoziTone.
   * If requested function found, call it.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.addRuntimeOnMessageListener = function () {

    /**
     * Listens for command sent from Background.
     * If requested function found, call it.
     *
     * @type    method
     * @param   objMessage
     *            Message received.
     * @param   objSender
     *            Sender of the message.
     * @param   funcSendResponse
     *            Function used for callback.
     * @return  void
     **/

    chrome.runtime.onMessage.addListener(
      function( objMessage, objSender, funcSendResponse ) {
        pozitoneModule.api.processRequest(
            objMessage
          , objSender
          , funcSendResponse
        );

        // Indicate that the response function will be called asynchronously
        return true;
      }
    );
  };

  /**
   * Provide relative notification logo URL/src, get data URL.
   *
   * PoziTone can't access image files from other extensions.
   * Thus, image URLs have to be data URLs.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.convertNotificationLogoUrl = function () {
    var self = this;

    pozitoneModule.api.convertImageSrcToDataUrl(
        chrome.runtime.getURL( self.objStationInfo.strLogoDataUri )
      , function ( strDataUri ) {
          self.objStationInfo.strLogoDataUri = strDataUri;
        }
    );
  };

  /**
   * Send media event information to PoziTone.
   *
   * @type    method
   * @param   strFeedback
   *            Optional. Feedback for main actions (play/stop, mute/unmute).
   * @param   strCommand
   *            Optional. What command made this call.
   * @param   boolSetTrackInfo
   *            Optional. Whether to set track info.
   * @param   boolDisregardSameMessage
   *            Optional. Whether to show the same message again or not
   * @return  void
   **/

  PageWatcher.prototype.sendMediaEvent = function (
      strFeedback
    , strCommand
    , boolSetTrackInfo
    , boolDisregardSameMessage
  ) {
    var self = this
      , $userMenu = document.querySelector( this.strUserMenuSelector )
      ;

    if ( typeof boolSetTrackInfo === 'undefined' || boolSetTrackInfo ) {
      this.objStationInfo.strTrackInfo = document.title;
    }

    this.objStationInfo.strAdditionalInfo =
      ( typeof strFeedback === 'string' && strFeedback !== '' )
        ? strFeedback
        : ''
        ;

    if ( strCommand !== 'muteUnmute' ) {
      this.isMuted( function( boolIsMuted ) {
        self.objPlayerInfo.boolIsMuted = boolIsMuted;
        self.objPlayerInfo.intVolume = boolIsMuted ? 0 : 10;
      } );
    }

    // TODO: Is this really needed?
    if ( typeof boolDisregardSameMessage !== 'boolean' ) {
      boolDisregardSameMessage = this.boolDisregardSameMessage;
    }

    var objData = {
        boolIsUserLoggedIn : document.contains( $userMenu )
      , boolDisregardSameMessage : boolDisregardSameMessage
      , objPlayerInfo : this.objPlayerInfo
      , objStationInfo : this.objStationInfo
      , strCommand : strCommand
    };

    pozitoneModule.api.sendMediaEvent( objData );
  };

  /**
   * Trigger button click if the button exists
   *
   * @type    method
   * @param   $btn
   *            The button to be clicked.
   * @return  void
   **/

  PageWatcher.prototype.triggerButtonClick = function( $btn ) {
    if ( document.contains( $btn ) ) {
      $btn.click();
    }
  };

  /**
   * Simulate "Favorite/Like" player method
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.triggerPlayerAction_favorite = function() {
    this.triggerButtonClick( document.querySelector( this.strFavoriteBtnSelector ) );
  };

  /**
   * Simulate "Next" player method
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.triggerPlayerAction_next = function() {
    this.triggerButtonClick( this.$playNextTrackBtn );
  };

  /**
   * Simulate "Previous" player method
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.triggerPlayerAction_previous = function() {
    this.triggerButtonClick( this.$playPreviousTrackBtn );
  };

  /**
   * Toggle the sound.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.triggerPlayerAction_playStop = function() {
    this.triggerButtonClick( this.$playPauseBtn );
  };

  /**
   * Simulate "Mute" player method
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.triggerPlayerAction_mute = function() {
    this.triggerPlayerAction_muteUnmute();
  };

  /**
   * Simulate "Unmute" player method
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.triggerPlayerAction_unmute = function() {
    this.triggerPlayerAction_muteUnmute();
  };

  /**
   * If volume is not 0, then mute. Otherwise, unmute.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.triggerPlayerAction_muteUnmute = function() {
    var self = this;

    this.isMuted( function( boolIsMuted ) {
      // Switching values to the opposite
      self.objPlayerInfo.boolIsMuted = ! boolIsMuted;
      self.objPlayerInfo.intVolume = boolIsMuted ? 10 : 0;

      self.triggerButtonClick( self.$muteUnmuteBtn );
      self.sendMediaEvent( boolIsMuted ? 'onUnmute' : 'onMute', 'muteUnmute' );
    } );
  };

  /**
   * Show the last shown notification again.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.triggerPlayerAction_showNotification = function() {
    this.sendMediaEvent( 'onShowNotification' );
  };

  /**
   * If volume is not 0, then mute. Otherwise, unmute.
   *
   * @type    method
   * @param   funcSuccessCallback
   *            Optional. Function to run on success.
   * @param   funcErrorCallback
   *            Optional. Function to run on error.
   * @return  void
   **/

  PageWatcher.prototype.isMuted = function( funcSuccessCallback, funcErrorCallback ) {
    var $muteUnmuteBtnContainer = this.$muteUnmuteBtnContainer;

    if ( document.contains( $muteUnmuteBtnContainer ) ) {
      var boolIsMuted = $muteUnmuteBtnContainer.classList.contains( this.strMuteUnmuteBtnContainerMutedClass );

      if ( typeof funcSuccessCallback === 'function' ) {
        funcSuccessCallback( boolIsMuted );
      }
    }
    else if ( typeof funcErrorCallback === 'function' ) {
      funcErrorCallback();
    }
  };

  if ( typeof pozitoneModule === 'undefined' ) {
    window.pozitoneModule = {};
  }

  // TODO: Fix cause, not effect
  if ( ! pozitoneModule.pageWatcher ) {
    pozitoneModule.pageWatcher = new PageWatcher();
  }
}() );
<commit_msg>Fix: SoundCloud wouldn't show notification sometimes<commit_after>/* =============================================================================

  Product: PoziTone module for SoundCloud
  Author: PoziWorld
  Copyright: (c) 2016 PoziWorld
  License: pozitone.com/license

  Table of Contents:

    PageWatcher
      init()
      initObserver()
      initPlayerStatusObserver()
      initTrackTitleContainerWrapperObserver()
      initFavoriteBtnObserver()
      addRuntimeOnMessageListener()
      convertNotificationLogoUrl()
      sendMediaEvent()
      triggerButtonClick()
      triggerPlayerAction_favorite()
      triggerPlayerAction_next()
      triggerPlayerAction_previous()
      triggerPlayerAction_playStop()
      triggerPlayerAction_mute()
      triggerPlayerAction_unmute()
      triggerPlayerAction_muteUnmute()
      triggerPlayerAction_showNotification()
      isMuted()

 ============================================================================ */

( function() {
  'use strict';

  function PageWatcher() {
    const
        strModule = 'com_soundcloud'
      , strImgPath = 'modules/' + strModule + '/img/'
      ;

    /* DOM-related variables */
    this.$player = document.querySelector( '.playControls' );
    this.$playPauseBtn = document.querySelector( '.playControl' );
    this.$playNextTrackBtn = document.querySelector( '.skipControl__next' );
    this.$playPreviousTrackBtn = document.querySelector( '.skipControl__previous' );
    this.$muteUnmuteBtn = document.querySelector( '.volume__button' );
    this.$muteUnmuteBtnContainer = document.querySelector( '.volume' );
    this.$trackTitleContainerWrapper = document.querySelector( '.playbackSoundBadge' );

    this.strPlayerVisibleClass = 'm-visible';
    this.strPlayPauseBtnPlayingClass = 'playing';
    this.strMuteUnmuteBtnContainerMutedClass = 'muted';
    this.strTrackTitleContainerClass = 'playbackSoundBadge__titleContextContainer';

    this.strUserMenuSelector = '.header__userNav';
    this.strFavoriteBtnSelector = '.playbackSoundBadge__like';
    this.strFavoriteBtnSuccessClass = 'sc-button-selected';

    this.DisconnectableObserver = null;
    /* DOM-related variables - END */

    this.boolIsUserLoggedIn = false;
    this.boolHadPlayedBefore = false;
    this.boolWasPageJustLoaded = true;
    this.boolDisregardSameMessage = true;

    this.objPlayerInfo = {
        strModule : strModule
      , boolIsReady : true
      , boolIsPlaying : false
      , boolIsMuted : false
      , intVolume : 10 // site uses 0-10 scale
      , boolCanPlayNextTrackLoggedOut : true
      , boolCanPlayPreviousTrackLoggedOut : true
    };

    this.objStationInfo = {
        strStationName : 'SoundCloud' // TODO: Change
      , strStationNamePlusDesc : document.title // TODO: Change
      , strLogoUrl : '/' + strImgPath + 'soundcloud-logo-48.svg'
      , strLogoDataUri : strImgPath + 'soundcloud-logo-80.png'
      , strTrackInfo : ''
      , strAdditionalInfo : ''
      , boolHasAddToPlaylistButton : false
    };

    this.init();
  }

  /**
   * Set event listeners, initialize API.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.init = function () {
    var self = this;

    self.initPlayerStatusObserver();
    self.addRuntimeOnMessageListener();
    pozitoneModule.api.init( 'built-in', self );
    self.convertNotificationLogoUrl();
  };

  /**
   * Init observer.
   *
   * @type    method
   * @param   $target
   *            The Node on which to observe DOM mutations.
   * @param   objOptions
   *            A MutationObserverInit object, specifies which DOM mutations
   *            should be reported.
   * @param   funcCallback
   *            The function which will be called on each DOM mutation.
   * @param   boolIsDisconnectable
   *            If this observer should be disconnected later.
   * @return  void
   **/

  PageWatcher.prototype.initObserver = function (
      $target
    , objOptions
    , funcCallback
    , boolIsDisconnectable
  ) {
    var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

    if (  typeof boolIsDisconnectable === 'undefined'
      &&  ! boolIsDisconnectable
    ) {
      var observer = new MutationObserver( funcCallback );

      observer.observe( $target, objOptions );
    }
    else {
      // Disconnect the one set previously
      if ( this.DisconnectableObserver ) {
        this.DisconnectableObserver.disconnect();
      }

      this.DisconnectableObserver = new MutationObserver( funcCallback );
      this.DisconnectableObserver.observe( $target, objOptions );
    }
  };

  /**
   * Init player status changes observer.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.initPlayerStatusObserver = function() {
    var self = this
      , objOptions = {
            attributes : true
          , attributeFilter : [ 'class' ]
        }
      , funcCallback  = function( arrMutations ) {
          for ( var i = 0, l = arrMutations.length; i < l; i++ ) {
            var $target = arrMutations[ i ].target;

            var boolIsPlaying = $target.classList.contains( self.strPlayPauseBtnPlayingClass )
              , strUpdatedPreviousStatus = boolIsPlaying ? 'play' : 'stop'
              ;

            // Sometimes mutation happens even without player status change
            if ( strUpdatedPreviousStatus === self.objPlayerInfo.strPreviousStatus ) {
              return;
            }

            self.objPlayerInfo.boolIsPlaying = boolIsPlaying;

            // Follow the 101.ru logic
            self.objPlayerInfo.strPreviousStatus = strUpdatedPreviousStatus;

            if ( boolIsPlaying ) {
              var strLangStartedOrResumed = 'onPlay';

              if ( self.boolWasPageJustLoaded ) {
                strLangStartedOrResumed = 'onFirstPlay';

                self.initTrackTitleContainerWrapperObserver();
              }

              self.sendMediaEvent( strLangStartedOrResumed );

              self.boolHadPlayedBefore = true;
              self.boolWasPageJustLoaded = false;
            }
            else if ( ! boolIsPlaying && self.boolHadPlayedBefore ) {
              self.sendMediaEvent( 'onPause', undefined, false );
            }

            return;
          }
        }
      ;

    self.initObserver( self.$playPauseBtn, objOptions, funcCallback );
  };

  /**
   * Init track title container observer.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.initTrackTitleContainerWrapperObserver = function () {
    var self = this
      , objOptions = {
            characterData : true
          , childList : true
          , subtree : true
        }
      , funcCallback  = function( arrMutations ) {
          for ( var i = 0, l = arrMutations.length; i < l; i++ ) {
            var objMutationRecord = arrMutations[ i ]
              , arrAddedNodes = objMutationRecord.addedNodes
              , intAddedNodesCount = arrAddedNodes.length
              ;

            if ( intAddedNodesCount ) {
              // TODO: Is this a bug? j/i
              for ( var j = 0; i < intAddedNodesCount; i++ ) {
                var arrClassList = arrAddedNodes[ i ].classList;

                if (  arrClassList
                  &&  arrClassList.contains( self.strTrackTitleContainerClass )
                  &&  ! self.boolWasPageJustLoaded
                ) {
                  self.sendMediaEvent( undefined, undefined, true, false );
                }

                self.initFavoriteBtnObserver();
              }
            }
          }
        }
      ;

    self.initObserver( self.$trackTitleContainerWrapper, objOptions, funcCallback );
  };

  /**
   * Init "Favorite/Like" button observer.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.initFavoriteBtnObserver = function () {
    var self = this
      , DisconnectableObserver = self.DisconnectableObserver
      , $target = document.querySelector( self.strFavoriteBtnSelector )
      , objOptions = {
            attributes : true
          , attributeFilter : [ 'class' ]
        }
      , funcCallback  = function( arrMutations ) {
          for ( var i = 0, l = arrMutations.length; i < l; i++ ) {
            if ( arrMutations[ i ].target.classList.contains( self.strFavoriteBtnSuccessClass ) ) {
              self.sendMediaEvent( 'onFavorite', undefined, false );

              return;
            }
          }
        }
      ;

    // Reset "Favorite/Like" button observer
    if ( DisconnectableObserver ) {
      DisconnectableObserver.disconnect();
    }

    self.initObserver( $target, objOptions, funcCallback, true );
  };

  /**
   * Listen for commands sent from PoziTone.
   * If requested function found, call it.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.addRuntimeOnMessageListener = function () {
    var _this = this;

    /**
     * Listens for command sent from Background.
     * If requested function found, call it.
     *
     * @type    method
     * @param   objMessage
     *            Message received.
     * @param   objSender
     *            Sender of the message.
     * @param   funcSendResponse
     *            Function used for callback.
     * @return  void
     **/

    chrome.runtime.onMessage.addListener(
      function( objMessage, objSender, funcSendResponse ) {
        pozitoneModule.api.processRequest(
            objMessage
          , objSender
          , funcSendResponse
          , _this
        );

        // Indicate that the response function will be called asynchronously
        return true;
      }
    );
  };

  /**
   * Provide relative notification logo URL/src, get data URL.
   *
   * PoziTone can't access image files from other extensions.
   * Thus, image URLs have to be data URLs.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.convertNotificationLogoUrl = function () {
    var self = this;

    pozitoneModule.api.convertImageSrcToDataUrl(
        chrome.runtime.getURL( self.objStationInfo.strLogoDataUri )
      , function ( strDataUri ) {
          self.objStationInfo.strLogoDataUri = strDataUri;
        }
    );
  };

  /**
   * Send media event information to PoziTone.
   *
   * @type    method
   * @param   strFeedback
   *            Optional. Feedback for main actions (play/stop, mute/unmute).
   * @param   strCommand
   *            Optional. What command made this call.
   * @param   boolSetTrackInfo
   *            Optional. Whether to set track info.
   * @param   boolDisregardSameMessage
   *            Optional. Whether to show the same message again or not
   * @return  void
   **/

  PageWatcher.prototype.sendMediaEvent = function (
      strFeedback
    , strCommand
    , boolSetTrackInfo
    , boolDisregardSameMessage
  ) {
    var self = this
      , $userMenu = document.querySelector( this.strUserMenuSelector )
      ;

    if ( typeof boolSetTrackInfo === 'undefined' || boolSetTrackInfo ) {
      this.objStationInfo.strTrackInfo = document.title;
    }

    this.objStationInfo.strAdditionalInfo =
      ( typeof strFeedback === 'string' && strFeedback !== '' )
        ? strFeedback
        : ''
        ;

    if ( strCommand !== 'muteUnmute' ) {
      this.isMuted( function( boolIsMuted ) {
        self.objPlayerInfo.boolIsMuted = boolIsMuted;
        self.objPlayerInfo.intVolume = boolIsMuted ? 0 : 10;
      } );
    }

    // TODO: Is this really needed?
    if ( typeof boolDisregardSameMessage !== 'boolean' ) {
      boolDisregardSameMessage = this.boolDisregardSameMessage;
    }

    var objData = {
        boolIsUserLoggedIn : document.contains( $userMenu )
      , boolDisregardSameMessage : boolDisregardSameMessage
      , objPlayerInfo : this.objPlayerInfo
      , objStationInfo : this.objStationInfo
      , strCommand : strCommand
    };

    pozitoneModule.api.sendMediaEvent( objData );
  };

  /**
   * Trigger button click if the button exists
   *
   * @type    method
   * @param   $btn
   *            The button to be clicked.
   * @return  void
   **/

  PageWatcher.prototype.triggerButtonClick = function( $btn ) {
    if ( document.contains( $btn ) ) {
      $btn.click();
    }
  };

  /**
   * Simulate "Favorite/Like" player method
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.triggerPlayerAction_favorite = function() {
    this.triggerButtonClick( document.querySelector( this.strFavoriteBtnSelector ) );
  };

  /**
   * Simulate "Next" player method
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.triggerPlayerAction_next = function() {
    this.triggerButtonClick( this.$playNextTrackBtn );
  };

  /**
   * Simulate "Previous" player method
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.triggerPlayerAction_previous = function() {
    this.triggerButtonClick( this.$playPreviousTrackBtn );
  };

  /**
   * Toggle the sound.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.triggerPlayerAction_playStop = function() {
    this.triggerButtonClick( this.$playPauseBtn );
  };

  /**
   * Simulate "Mute" player method
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.triggerPlayerAction_mute = function() {
    this.triggerPlayerAction_muteUnmute();
  };

  /**
   * Simulate "Unmute" player method
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.triggerPlayerAction_unmute = function() {
    this.triggerPlayerAction_muteUnmute();
  };

  /**
   * If volume is not 0, then mute. Otherwise, unmute.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.triggerPlayerAction_muteUnmute = function() {
    var self = this;

    this.isMuted( function( boolIsMuted ) {
      // Switching values to the opposite
      self.objPlayerInfo.boolIsMuted = ! boolIsMuted;
      self.objPlayerInfo.intVolume = boolIsMuted ? 10 : 0;

      self.triggerButtonClick( self.$muteUnmuteBtn );
      self.sendMediaEvent( boolIsMuted ? 'onUnmute' : 'onMute', 'muteUnmute' );
    } );
  };

  /**
   * Show the last shown notification again.
   *
   * @type    method
   * @param   No Parameters Taken
   * @return  void
   **/

  PageWatcher.prototype.triggerPlayerAction_showNotification = function() {
    this.sendMediaEvent( 'onShowNotification' );
  };

  /**
   * If volume is not 0, then mute. Otherwise, unmute.
   *
   * @type    method
   * @param   funcSuccessCallback
   *            Optional. Function to run on success.
   * @param   funcErrorCallback
   *            Optional. Function to run on error.
   * @return  void
   **/

  PageWatcher.prototype.isMuted = function( funcSuccessCallback, funcErrorCallback ) {
    var $muteUnmuteBtnContainer = this.$muteUnmuteBtnContainer;

    if ( document.contains( $muteUnmuteBtnContainer ) ) {
      var boolIsMuted = $muteUnmuteBtnContainer.classList.contains( this.strMuteUnmuteBtnContainerMutedClass );

      if ( typeof funcSuccessCallback === 'function' ) {
        funcSuccessCallback( boolIsMuted );
      }
    }
    else if ( typeof funcErrorCallback === 'function' ) {
      funcErrorCallback();
    }
  };

  if ( typeof pozitoneModule === 'undefined' ) {
    window.pozitoneModule = {};
  }

  // TODO: Fix cause, not effect
  if ( ! pozitoneModule.pageWatcher ) {
    pozitoneModule.pageWatcher = new PageWatcher();
  }
}() );
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies.
 */

var through = require('through2');
var gutil = require('gulp-util');
var path = require('path');
var _ = require('lodash');

/**
 * Assemble renderer plugin used to render templates passed through the stream.
 *
 * @param  {Object} `options` Additional options to use.
 * @param  {Object} `locals` Additional locals to pass to the renderer.
 * @return {Stream} Stream compatible with Assemble pipelines
 */

module.exports = function rendererPlugin(options, locals) {

  var assemble = this;
  var session = assemble.session;
  var opts = _.extend({}, this.options, options);

  // get the custom file type created for this task
  var taskName = session.get('task name');
  var fileType = 'page';
  var buildKey = assemble.option('renameKey') || function (fp) {
    return path.basename(fp, path.extname(fp));
  };

  // create a custom file type based on the task name to keep
  // source files separate.
  if (taskName) {
    fileType = '__task__' + taskName;
    buildKey = function (fp) {
      return path.basename(fp, path.extname(fp));
    };
  }

  var plural = assemble.collection[fileType];
  var renderables = session.get('renderables') || [];
  renderables = renderables.concat([plural]).filter(Boolean);

  /**
   * Actual renderer plugin used in a pipeline.
   *
   * @param  {Object} `record` Vinyl File Object from the current stream.
   * @param  {Object} `enc` `record.contents` encoding.
   * @param  {Function} `cb` Callback to indicate when the transform function is complete.
   */

  return through.obj(function(record, encoding, cb) {
    if (record.isNull()) {
      this.push(record);
      return cb();
    }

    if (record.isStream()) {
      this.emit('error', new gutil.PluginError('assemble-plugin:render', 'Streaming is not supported.'));
      return cb();
    }

    try {
      // find the template associated with the vinyl record
      var stream = this;
      var key = buildKey(record.path);
      var file = renderables.map(function(type) {
        return assemble.views[type][key];
      }).filter(Boolean);

      file = file.length === 0 ? assemble.views.pages[key] : file[0];
      if (!file) {
        stream.push(record);
        return cb();
      }

      // update the template information with any changes that might not have
      // been updated by reference while running through the stream
      file.content = record.contents.toString();

      // render the file template with the given locals
      file.render(locals, function(err, content) {
        if (err) {
          stream.emit('error', new gutil.PluginError('assemble-plugin:render', err));
          cb(err);
          return;
        }

        // update the vinyl record with the rendered contents
        // and push back into the stream.
        record.contents = new Buffer(content);
        stream.push(record);
        cb();
      });

    } catch (err) {
      this.emit('error', new gutil.PluginError('assemble-plugin:render', err));
      return cb();
    }
  });
};
<commit_msg>putting options in the correct place<commit_after>'use strict';

/**
 * Module dependencies.
 */

var through = require('through2');
var gutil = require('gulp-util');
var path = require('path');
var _ = require('lodash');

/**
 * Assemble renderer plugin used to render templates passed through the stream.
 *
 * @param  {Object} `options` Additional options to use.
 * @param  {Object} `locals` Additional locals to pass to the renderer.
 * @return {Stream} Stream compatible with Assemble pipelines
 */

module.exports = function rendererPlugin(options, locals) {

  var assemble = this;
  var session = assemble.session;
  var opts = _.extend({}, this.options, options);
  locals = locals || {};
  locals.options = _.extend({}, locals.options, opts);

  // get the custom file type created for this task
  var taskName = session.get('task name');
  var fileType = 'page';
  var buildKey = assemble.option('renameKey') || function (fp) {
    return path.basename(fp, path.extname(fp));
  };

  // create a custom file type based on the task name to keep
  // source files separate.
  if (taskName) {
    fileType = '__task__' + taskName;
    buildKey = function (fp) {
      return path.basename(fp, path.extname(fp));
    };
  }

  var plural = assemble.collection[fileType];
  var renderables = session.get('renderables') || [];
  renderables = renderables.concat([plural]).filter(Boolean);

  /**
   * Actual renderer plugin used in a pipeline.
   *
   * @param  {Object} `record` Vinyl File Object from the current stream.
   * @param  {Object} `enc` `record.contents` encoding.
   * @param  {Function} `cb` Callback to indicate when the transform function is complete.
   */

  return through.obj(function(record, encoding, cb) {
    if (record.isNull()) {
      this.push(record);
      return cb();
    }

    if (record.isStream()) {
      this.emit('error', new gutil.PluginError('assemble-plugin:render', 'Streaming is not supported.'));
      return cb();
    }

    try {
      // find the template associated with the vinyl record
      var stream = this;
      var key = buildKey(record.path);
      var file = renderables.map(function(type) {
        return assemble.views[type][key];
      }).filter(Boolean);

      file = file.length === 0 ? assemble.views.pages[key] : file[0];
      if (!file) {
        stream.push(record);
        return cb();
      }

      // update the template information with any changes that might not have
      // been updated by reference while running through the stream
      file.content = record.contents.toString();

      // render the file template with the given locals
      file.render(locals, function(err, content) {
        if (err) {
          stream.emit('error', new gutil.PluginError('assemble-plugin:render', err));
          cb(err);
          return;
        }

        // update the vinyl record with the rendered contents
        // and push back into the stream.
        record.contents = new Buffer(content);
        stream.push(record);
        cb();
      });

    } catch (err) {
      this.emit('error', new gutil.PluginError('assemble-plugin:render', err));
      return cb();
    }
  });
};
<|endoftext|>


<commit_before>'use strict'

const NOW = new Date()

const {join, parse} = require('path')

const {readFileSync, readdirSync} = require('fs')

const GitHubAPIs = require('github')

const {
  env: {
    RELEASE_GIST,
    GIST_TOKEN,
    GIST_ID,
    GIT_REPO_TAG
  },
  exit,
  stdout,
  stderr
} = require('process')

if (!RELEASE_GIST) {
  stdout.write('Skipped Gist release\n')
  exit(1)
}

if (!GIST_ID || !GIST_TOKEN) {
  stderr.write('Missing environment variables\n')
  exit(1)
}

const github = new GitHubAPIs()
const files = {}
const ENCODING = {encoding: 'utf8'}
const CONTAINER = join(__dirname, '..', '..', 'out')
const LIST = readdirSync(CONTAINER)

const filename = item => {
  const {name, ext} = parse(item)
  return name + '-' + GIT_REPO_TAG + ext
}

for (const item of LIST) {
  const content = readFileSync(join(CONTAINER, item), ENCODING)
  files[filename(item)] = {content}
}

github.authenticate({
  type: 'token',
  token: GIST_TOKEN
})

new Promise(
  (resolve, reject) => {
    github.gists.edit({
      id: GIST_ID,
      files
    })
      .then(
        response => {
          const rfiles = response.files
          let summary = `# Release ${GIT_REPO_TAG} - ${NOW.toISOString()}\n\n`
          for (const item of LIST) {
            const {raw_url: url, type, language} = rfiles[filename(item)]
            summary += ` * [${item}](${url})\n  - Type: ${type}\n  - Language: ${language}\n\n`
          }
          github.gists.edit({
            id: GIST_ID,
            files: {
              [ filename('summary.md') ]: {
                content: summary
              }
            }
          })
            .then(resolve)
            .catch(reject)
        }
      )
      .catch(reject)
  }
)
  .catch(
    () => {
      stderr.write('Upload to Gist failed.\n')
      exit(2)
    }
  )
<commit_msg>Use co<commit_after>'use strict'

const NOW = new Date()

const {join, parse} = require('path')

const {readFileSync, readdirSync} = require('fs')

const GitHubAPIs = require('github')

const co = require('co')

const {
  env: {
    RELEASE_GIST,
    GIST_TOKEN,
    GIST_ID,
    GIT_REPO_TAG
  },
  exit,
  stdout,
  stderr
} = require('process')

if (!RELEASE_GIST) {
  stdout.write('Skipped Gist release\n')
  exit(1)
}

if (!GIST_ID || !GIST_TOKEN) {
  stderr.write('Missing environment variables\n')
  exit(1)
}

const github = new GitHubAPIs()
const {gists} = github
const files = {}
const ENCODING = {encoding: 'utf8'}
const CONTAINER = join(__dirname, '..', '..', 'out')
const LIST = readdirSync(CONTAINER)

const filename = item => {
  const {name, ext} = parse(item)
  return name + '-' + GIT_REPO_TAG + ext
}

for (const item of LIST) {
  const content = readFileSync(join(CONTAINER, item), ENCODING)
  files[filename(item)] = {content}
}

github.authenticate({
  type: 'token',
  token: GIST_TOKEN
})

co(main)

function * main () {
  const artifactsResponse = yield gists.edit({
    id: GIST_ID,
    files
  })
  const rfiles = artifactsResponse.files
  const summary = LIST.reduce(
    (prev, item) => {
      const {raw_url: url, type, language} = rfiles[filename(item)]
      return prev + ` * [${item}](${url})\n  - Type: ${type}\n  - Language: ${language}\n\n`
    },
    `# Release ${GIT_REPO_TAG} - ${NOW.toISOString()}\n\n`
  )
  yield gists.edit({
    id: GIST_ID,
    files: {
      [ filename('summary.md') ]: {
        content: summary
      }
    }
  })
}
<|endoftext|>


<commit_before>const { broadcastAndEmit } = require('../../utils');
const logger = require('../../logging');

const { getActiveGenfors } = require('../../models/meeting');
const { endGenfors, toggleRegistrationStatus } = require('../../managers/meeting');

const {
  ADMIN_END_MEETING,
  END_MEETING,
  TOGGLE_REGISTRATION_STATE,
  TOGGLED_REGISTRATION_STATE } = require('../../../common/actionTypes/meeting');

async function endGAM(socket) {
  const genfors = await getActiveGenfors();
  logger.info('Ending meeting', { genfors: genfors.title });
  await endGenfors(genfors, socket.request.user);

  broadcastAndEmit(socket, END_MEETING);
}

const toggleRegistration = async (socket, data) => {
  logger.debug('Toggling meeting registration status', data);
  const genfors = await getActiveGenfors();
  const updatedMeeting = await toggleRegistrationStatus(genfors,
    data.registrationOpen);
  broadcastAndEmit(socket, TOGGLED_REGISTRATION_STATE, updatedMeeting);
};

const listener = (socket) => {
  socket.on('action', async (data) => {
    switch (data.type) {
      case TOGGLE_REGISTRATION_STATE: {
        toggleRegistration(socket, data);
        break;
      }
      case ADMIN_END_MEETING: {
        endGAM(socket);
        break;
      }
      default:
        break;
    }
  });
};

module.exports = {
  listener,
  endGAM,
  toggleRegistration,
};
<commit_msg>Show error if failing to update registration state<commit_after>const { broadcastAndEmit, emitError } = require('../../utils');
const logger = require('../../logging');

const { getActiveGenfors } = require('../../models/meeting');
const { endGenfors, toggleRegistrationStatus } = require('../../managers/meeting');

const {
  ADMIN_END_MEETING,
  END_MEETING,
  TOGGLE_REGISTRATION_STATE,
  TOGGLED_REGISTRATION_STATE } = require('../../../common/actionTypes/meeting');

async function endGAM(socket) {
  const genfors = await getActiveGenfors();
  logger.info('Ending meeting', { genfors: genfors.title });
  await endGenfors(genfors, socket.request.user);

  broadcastAndEmit(socket, END_MEETING);
}

const toggleRegistration = async (socket, data) => {
  logger.debug('Toggling meeting registration status', data);
  const genfors = await getActiveGenfors();
  try {
    const updatedMeeting = await toggleRegistrationStatus({ _id: genfors._id },
    data.registrationOpen);
    broadcastAndEmit(socket, TOGGLED_REGISTRATION_STATE, updatedMeeting);
  } catch (err) {
    logger.warn('Toggling registration failed for meeting', { genfors: genfors.title, err });
    emitError(socket, 'Noe gikk galt under oppdatering av registreringsstatus.');
  }
};

const listener = (socket) => {
  socket.on('action', async (data) => {
    switch (data.type) {
      case TOGGLE_REGISTRATION_STATE: {
        toggleRegistration(socket, data);
        break;
      }
      case ADMIN_END_MEETING: {
        endGAM(socket);
        break;
      }
      default:
        break;
    }
  });
};

module.exports = {
  listener,
  endGAM,
  toggleRegistration,
};
<|endoftext|>


<commit_before>goog.provide('glift.flattener.starpoints');

glift.flattener.starpoints = {
  /**
   * @const {!Object<number, !Array<!Array<number>>>}
   * @private
   */
  pts_: {
    9: [[4,4]],
    13: [
          [3,3], [3,9],
             [6,6],
          [9,3], [9,9],
        ],
    19: [
          [3,3],  [3,9],  [3,15],
          [9,3],  [9,9],  [9,15],
          [15,3], [15,9], [15,15],
        ],
  },

  /**
   * Lookup map for pts.
   * @private {!Object<number, !Object<glift.PtStr, boolean>>}
   */
  map_: {},

  /**
   * @param {glift.Point} pt
   * @param {number} size
   * @return {boolean} Whether the point is a starpoint.
   */
  isPt: function(pt, size) {
    var map = glift.flattener.starpoints.map_[size];
    if (!map) {
      var newmap = {};
      var allPts = glift.flattener.starpoints.allPts(size);
      for (var i = 0; i < allPts.length; i++) {
        newmap[allPts[i].toString()] = true;
      }
      glift.flattener.starpoints.map_[size] = newmap;
      map = newmap;
    }
    return !!map[pt.toString()];
  },

  /**
   * @param {number} size
   * @return {!Array<!glif.Point>} All the points that should be considered
   * starpoints.
   */
  allPts: function(size) {
    /** @type {!Array<glift.Point>} */
    var out = [];
    var ptz = glift.flattener.starpoints.pts_[size] || [];
    for (var i = 0; i < ptz.length; i++) {
      var p = ptz[i];
      out.push(new glift.Point(p[0], p[1]));
    }
    return out;
  },
};
<commit_msg>Fix build<commit_after>goog.provide('glift.flattener.starpoints');

glift.flattener.starpoints = {
  /**
   * @const {!Object<number, !Array<!Array<number>>>}
   * @private
   */
  pts_: {
    9: [[4,4]],
    13: [
          [3,3], [3,9],
             [6,6],
          [9,3], [9,9],
        ],
    19: [
          [3,3],  [3,9],  [3,15],
          [9,3],  [9,9],  [9,15],
          [15,3], [15,9], [15,15],
        ],
  },

  /**
   * Lookup map for pts.
   * @private {!Object<number, !Object<glift.PtStr, boolean>>}
   */
  map_: {},

  /**
   * @param {glift.Point} pt
   * @param {number} size
   * @return {boolean} Whether the point is a starpoint.
   */
  isPt: function(pt, size) {
    var map = glift.flattener.starpoints.map_[size];
    if (!map) {
      var newmap = {};
      var allPts = glift.flattener.starpoints.allPts(size);
      for (var i = 0; i < allPts.length; i++) {
        newmap[allPts[i].toString()] = true;
      }
      glift.flattener.starpoints.map_[size] = newmap;
      map = newmap;
    }
    return !!map[pt.toString()];
  },

  /**
   * @param {number} size
   * @return {!Array<!glift.Point>} All the points that should be considered
   * starpoints.
   */
  allPts: function(size) {
    /** @type {!Array<glift.Point>} */
    var out = [];
    var ptz = glift.flattener.starpoints.pts_[size] || [];
    for (var i = 0; i < ptz.length; i++) {
      var p = ptz[i];
      out.push(new glift.Point(p[0], p[1]));
    }
    return out;
  },
};
<|endoftext|>


<commit_before>var levelCouchSync = require('level-couch-sync')
var pad            = require('padded-semver').pad
var semver         = require('semver')

exports.db = function (db, config) {
  var packageDb = db.sublevel('pkg', {valueEncoding: 'json'})
  var versionDb = db.sublevel('ver', {valueEncoding: 'json'})

  //if a date is missing, use this number.
  var yearZero = new Date(2009, 1, 1)

  if(config._[0] === 'sync')
    config.sync = true

  if(!(config && config.sync))
    return db.sublevel('registry-sync')

  var lastPercentage = 0
  var registrySync
  if(config.sync !== false) {

    registrySync = db.sublevel('registry-sync')
    levelCouchSync(config.registry, db, registrySync,
    function (data, emit) {
      var doc = data.doc

      if(doc._deleted) return
      //ignore broken modules
      if(!doc._attachments) return

      //don't allow keys with ~
      if(/~/.test(data.id)) return

      //is a design doc
      if(!doc.name || !doc.versions) return
      if(!doc.maintainers || !doc.maintainers[0] || !doc.maintainers[0].name)
        return

      try {

        //set time to something sensible by default.
        var time = doc.time ? {
            created  : doc.time.created,
            modified : doc.time.modified
          } : {
            created  : yearZero,
            modified : yearZero
          }

        emit(data.id, {
          name        : doc.name,
          description : doc.description,
          readme      : doc.readme,
          keywords    : doc.keywords,
          author      : doc.author,
          licenses    : doc.licenses,
          repository  : doc.repository,
          maintainers : doc.maintainers,
          time        : time
        }, packageDb)

        //versions
        var vers = doc.versions
        for(var version in vers) {
          var ver = vers[version]
          var tgz = doc.name + '-' + version + '.tgz'
          if(doc._attachments[tgz])
            emit(data.id + '!' + pad(version), {
              name            : ver.name,
              version         : ver.version,
              dependencies    : ver.dependencies,
              devDependencies : ver.devDependencies,
              description     : ver.description,
              size            : doc._attachments[tgz].length,
              time            : doc.time ? doc.time[version] : yearZero,
              shasum          : ver.dist.shasum,
              gypfile         : ver.gypfile
            }, versionDb)

        }
      } catch (err) {
        console.error(err.stack)
        throw err
      }
    })

  }

}

exports.cli = function (db) {
  db.cli.push(function (db, config, cb) {
    if(!config.sync) return
    console.log('syncing...')
    var registrySync =
      db.sublevel('registry-sync')

    var whitespace = ''

    for(var i = 0; i < process.stdout.colums; i++)
      whitespace += ' '

    if(config.verbose)
      db.sublevel('pkg').post(function (op) {
        var pkg = JSON.parse(op.value)

        var maintainers = pkg.maintainers.map(function (e) {
          return e.name
        }).join(', ')

        console.log(pkg.name)
        if(pkg.description)
          console.log('  '+pkg.description)
      })
    else {
      registrySync.createProgressBar('npm registry', function (data) {
        var doc = data.doc
        if(!doc.versions) return
        var max = process.stdout.columns
        try {
          var s = doc.name + '@' + semver.maxSatisfying(Object.keys(doc.versions), '*', true) + ': '
          var desc = doc.description || '[no description]'
          var len = s.length + desc.length
          return len > max ? s + desc.substring(0, max - s.length) : s + desc
        } catch (err) {
          console.error(err.message)
        }

      })
    }
    return true
  })
}
<commit_msg>show connection status<commit_after>var levelCouchSync = require('level-couch-sync')
var pad            = require('padded-semver').pad
var semver         = require('semver')

exports.db = function (db, config) {
  var packageDb = db.sublevel('pkg', {valueEncoding: 'json'})
  var versionDb = db.sublevel('ver', {valueEncoding: 'json'})

  //if a date is missing, use this number.
  var yearZero = new Date(2009, 1, 1)

  if(config._[0] === 'sync')
    config.sync = true

  if(!(config && config.sync))
    return db.sublevel('registry-sync')

  var lastPercentage = 0
  var registrySync
  if(config.sync !== false) {

    registrySync = db.sublevel('registry-sync')
    levelCouchSync(config.registry, db, registrySync,
    function (data, emit) {
      var doc = data.doc

      if(doc._deleted) return
      //ignore broken modules
      if(!doc._attachments) return

      //don't allow keys with ~
      if(/~/.test(data.id)) return

      //is a design doc
      if(!doc.name || !doc.versions) return
      if(!doc.maintainers || !doc.maintainers[0] || !doc.maintainers[0].name)
        return

      try {

        //set time to something sensible by default.
        var time = doc.time ? {
            created  : doc.time.created,
            modified : doc.time.modified
          } : {
            created  : yearZero,
            modified : yearZero
          }

        emit(data.id, {
          name        : doc.name,
          description : doc.description,
          readme      : doc.readme,
          keywords    : doc.keywords,
          author      : doc.author,
          licenses    : doc.licenses,
          repository  : doc.repository,
          maintainers : doc.maintainers,
          time        : time
        }, packageDb)

        //versions
        var vers = doc.versions
        for(var version in vers) {
          var ver = vers[version]
          var tgz = doc.name + '-' + version + '.tgz'
          if(doc._attachments[tgz])
            emit(data.id + '!' + pad(version), {
              name            : ver.name,
              version         : ver.version,
              dependencies    : ver.dependencies,
              devDependencies : ver.devDependencies,
              description     : ver.description,
              size            : doc._attachments[tgz].length,
              time            : doc.time ? doc.time[version] : yearZero,
              shasum          : ver.dist.shasum,
              gypfile         : ver.gypfile
            }, versionDb)

        }
      } catch (err) {
        console.error(err.stack)
        throw err
      }
    })

  }

}

exports.cli = function (db) {
  db.cli.push(function (db, config, cb) {
    if(!config.sync) return
    console.log('syncing...')
    var registrySync =
      db.sublevel('registry-sync')

    var whitespace = ''

    for(var i = 0; i < process.stdout.colums; i++)
      whitespace += ' '

    if(config.verbose)
      db.sublevel('pkg').post(function (op) {
        var pkg = JSON.parse(op.value)

        var maintainers = pkg.maintainers.map(function (e) {
          return e.name
        }).join(', ')

        console.log(pkg.name)
        if(pkg.description)
          console.log('  '+pkg.description)
      })
    else if (config.debug) {

     ;['backoff', 'ready',  'data',    'progress',
       'max',     'wait',   'timeout', 'retry',
       'confirm', 'confirm_request']
      .forEach(function (name) {
        registrySync.on(name, function () {
          var args = [].slice.call(arguments)
          args.unshift(name)
          console.log.apply(console, args)
        })
      })
    }
    else {
      registrySync.createProgressBar('npm registry', function (data) {
        var doc = data.doc
        if(!doc.versions) return
        var max = process.stdout.columns
        try {
          var s = doc.name + '@' + semver.maxSatisfying(Object.keys(doc.versions), '*', true) + ': '
          var desc = doc.description || '[no description]'
          var len = s.length + desc.length
          return len > max ? s + desc.substring(0, max - s.length) : s + desc
        } catch (err) {
          console.error(err.message)
        }

      })
    }
    return true
  })
}
<|endoftext|>


<commit_before>'use strict';

(function (angular) {
    angular
        .module('peoplePluginContent')
        .controller('ImportCSVPopupCtrl', ['$scope', '$modalInstance','FormatConverter', function ($scope, $modalInstance,FormatConverter) {
            var ImportCSVPopup=this;
            ImportCSVPopup.ok = function (linkUrl) {
                console.log(ImportCSVPopup.fileData);
                if(ImportCSVPopup.fileData){
                    var json = FormatConverter.CSV2JSON(ImportCSVPopup.fileData);
                }
                $modalInstance.close(linkUrl);
            };
            ImportCSVPopup.cancel = function () {
                $modalInstance.dismiss('Dismiss');
            };
        }])
})(window.angular);
<commit_msg>Import CSV feature implemented with bulk insert on content<commit_after>'use strict';

(function (angular) {
    angular
        .module('peoplePluginContent')
        .controller('ImportCSVPopupCtrl', ['$scope', '$modalInstance','FormatConverter','Buildfire','TAG_NAMES', function ($scope, $modalInstance,FormatConverter,Buildfire,TAG_NAMES) {
            var ImportCSVPopup=this;
            ImportCSVPopup.ok = function (linkUrl) {
                console.log(ImportCSVPopup.fileData);
                if(ImportCSVPopup.fileData){
                    var json = FormatConverter.CSV2JSON(ImportCSVPopup.fileData);
                    console.log('json------------------',json);
                    Buildfire.datastore.bulkInsert(JSON.parse(angular.toJson(json)), TAG_NAMES.PEOPLE, function (err, data) {
                        if (err) {
                            console.error('There was a problem while importing the file----',err);
                        }
                        else {
                            console.log('File has been imported----------------------------',data);
                        }
                    });

                }
                $modalInstance.close(linkUrl);
            };
            ImportCSVPopup.cancel = function () {
                $modalInstance.dismiss('Dismiss');
            };
        }])
})(window.angular);
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// download buttons

/**
 * A special function for IE.  Without this hack there is no prompt to download after they click.  sigh.
 * bug 393263
 *
 * @param string direct link to download URL
 */
function trigger_ie_download(link) {
    // Only open if we got a link and this is IE.
    if (link && navigator.appVersion.indexOf('MSIE') != -1) {
        window.open(link, 'download_window', 'toolbar=0,location=no,directories=0,status=0,scrollbars=0,resizeable=0,width=1,height=1,top=0,left=0');
        window.focus();
    }
}

// attach an event to all the download buttons to trigger the special
// ie functionality if on ie
function init_download_links() {
    $('.download-link').each(function() {
        var $el = $(this);
        $el.click(function() {
            trigger_ie_download($el.data('direct-link'));
        });
    });
    $('.download-list').attr('role', 'presentation');
}

// platform images

function init_platform_imgs() {
    $('.platform-img').each(function() {
        var suffix = '';
        var $img = $(this);
        if (site.platform === 'osx' || site.platform === 'oldmac') {
            suffix = '-mac';
        }
        else if (site.platform === 'linux') {
            suffix = '-linux';
        }

        var orig_src = $img.data('src');
        var i = orig_src.lastIndexOf('.');
        var base = orig_src.substring(0, i);
        var ext = orig_src.substring(i);
        this.src = base + suffix + ext;
        $img.addClass(site.platform);
    });
}

// init

$(document).ready(function() {
    init_download_links();
    init_platform_imgs();
    $(window).on('load', function () {
        $('html').addClass('loaded');
    });
});

//get Master firefox version
function getFirefoxMasterVersion() {
    var version = 0;

    var matches = /Firefox\/([0-9]+).[0-9]+(?:.[0-9]+)?/.exec(
        navigator.userAgent
    );

    if (matches !== null && matches.length > 0) {
        version = parseInt(matches[1], 10);
    }

    return version;
}

function isFirefox() {
    return /\sFirefox/.test(navigator.userAgent);
}

function isFirefoxUpToDate(latest, esr) {

    var $body = $('body');
    var fx_version = getFirefoxMasterVersion();
    var esrFirefoxVersions = esr || $body.data('esr-versions');
    var latestFirefoxVersion;

    if (!latest) {
        latestFirefoxVersion = $body.attr('data-latest-firefox');
        latestFirefoxVersion = parseInt(latestFirefoxVersion.split('.')[0], 10);
    } else {
        latestFirefoxVersion = parseInt(latest.split('.')[0], 10);
    }

    return ($.inArray(fx_version, esrFirefoxVersions) !== -1 ||
            latestFirefoxVersion <= fx_version);
}

function isMobile() {
    return /\sMobile/.test(window.navigator.userAgent);
}

// Create text translation function using #strings element.
// TODO: Move to docs
// In order to use it, you need a block string_data bit inside your template,
// @see https://github.com/mozilla/bedrock/blob/master/apps/firefox/templates/firefox/partners/landing.html#L14
// then, each key name needs to be preceeded by data- as this uses data attributes
// to work. After this, you can access all strings defined inside the
// string_data block in JS using window.trans('keyofstring'); Thank @mkelly
var $strings = $('#strings');
window.trans = function trans(stringId) {
    return $strings.data(stringId);
};

function gaTrack(eventsArray, callback) {
    // submit eventsArray to GA and call callback only after they've
    // been sent, or if sending fails.
    // Example usage:
    //
    // $(function() {
    //      var handler = function(e) {
    //           var _this = this;
    //           e.preventDefault();
    //           $(_this).off('submit', handler);
    //           gaTrack(
    //              [['_trackEvent', 'Newsletter Registration', 'submit', newsletter]],
    //              function() {$(_this).submit();}
    //           );
    //      };
    //      $(thing).on('submit', handler);
    // });

    var timer = null;
    var hasCallback = typeof(callback) === 'function';
    var gaCallback = function() {
        clearTimeout(timer);
        if (hasCallback) {
            callback();
        }
    }
    if (typeof(window._gaq) === 'object') {
        // Failsafe - be sure we do the callback in a half-second
        // even if GA isn't able to send in our trackEvent.
        timer = setTimeout(gaCallback, 500);
        // But ordinarily, we get GA to call us back immediately after
        // it finishes sending our things.
        window._gaq.push(
            // https://developers.google.com/analytics/devguides/collection/analyticsjs/advanced#hitCallback
            // This is called AFTER GA has sent all pending data:
            ['_set', 'hitCallback', gaCallback()]
        )
        // send events to GA
        window._gaq.push.apply(this, eventsArray);
    } else {
        // GA disabled or blocked or something, make sure we still
        // call the caller's callback:
        if (hasCallback) {
            callback();
        }
    }
}
<commit_msg>Bug 896001 Aborted GA Call on Newsletter Submission<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// download buttons

/**
 * A special function for IE.  Without this hack there is no prompt to download after they click.  sigh.
 * bug 393263
 *
 * @param string direct link to download URL
 */
function trigger_ie_download(link) {
    // Only open if we got a link and this is IE.
    if (link && navigator.appVersion.indexOf('MSIE') != -1) {
        window.open(link, 'download_window', 'toolbar=0,location=no,directories=0,status=0,scrollbars=0,resizeable=0,width=1,height=1,top=0,left=0');
        window.focus();
    }
}

// attach an event to all the download buttons to trigger the special
// ie functionality if on ie
function init_download_links() {
    $('.download-link').each(function() {
        var $el = $(this);
        $el.click(function() {
            trigger_ie_download($el.data('direct-link'));
        });
    });
    $('.download-list').attr('role', 'presentation');
}

// platform images

function init_platform_imgs() {
    $('.platform-img').each(function() {
        var suffix = '';
        var $img = $(this);
        if (site.platform === 'osx' || site.platform === 'oldmac') {
            suffix = '-mac';
        }
        else if (site.platform === 'linux') {
            suffix = '-linux';
        }

        var orig_src = $img.data('src');
        var i = orig_src.lastIndexOf('.');
        var base = orig_src.substring(0, i);
        var ext = orig_src.substring(i);
        this.src = base + suffix + ext;
        $img.addClass(site.platform);
    });
}

// init

$(document).ready(function() {
    init_download_links();
    init_platform_imgs();
    $(window).on('load', function () {
        $('html').addClass('loaded');
    });
});

//get Master firefox version
function getFirefoxMasterVersion() {
    var version = 0;

    var matches = /Firefox\/([0-9]+).[0-9]+(?:.[0-9]+)?/.exec(
        navigator.userAgent
    );

    if (matches !== null && matches.length > 0) {
        version = parseInt(matches[1], 10);
    }

    return version;
}

function isFirefox() {
    return /\sFirefox/.test(navigator.userAgent);
}

function isFirefoxUpToDate(latest, esr) {

    var $body = $('body');
    var fx_version = getFirefoxMasterVersion();
    var esrFirefoxVersions = esr || $body.data('esr-versions');
    var latestFirefoxVersion;

    if (!latest) {
        latestFirefoxVersion = $body.attr('data-latest-firefox');
        latestFirefoxVersion = parseInt(latestFirefoxVersion.split('.')[0], 10);
    } else {
        latestFirefoxVersion = parseInt(latest.split('.')[0], 10);
    }

    return ($.inArray(fx_version, esrFirefoxVersions) !== -1 ||
            latestFirefoxVersion <= fx_version);
}

function isMobile() {
    return /\sMobile/.test(window.navigator.userAgent);
}

// Create text translation function using #strings element.
// TODO: Move to docs
// In order to use it, you need a block string_data bit inside your template,
// @see https://github.com/mozilla/bedrock/blob/master/apps/firefox/templates/firefox/partners/landing.html#L14
// then, each key name needs to be preceeded by data- as this uses data attributes
// to work. After this, you can access all strings defined inside the
// string_data block in JS using window.trans('keyofstring'); Thank @mkelly
var $strings = $('#strings');
window.trans = function trans(stringId) {
    return $strings.data(stringId);
};

function gaTrack(eventsArray, callback) {
    // submit eventsArray to GA and call callback only after they've
    // been sent, or if sending fails.
    // Example usage:
    //
    // $(function() {
    //      var handler = function(e) {
    //           var _this = this;
    //           e.preventDefault();
    //           $(_this).off('submit', handler);
    //           gaTrack(
    //              [['_trackEvent', 'Newsletter Registration', 'submit', newsletter]],
    //              function() {$(_this).submit();}
    //           );
    //      };
    //      $(thing).on('submit', handler);
    // });

    var timer = null;
    var hasCallback = typeof(callback) === 'function';
    var gaCallback = function() {
        clearTimeout(timer);
        if (hasCallback) {
            callback();
        }
    };
    if (typeof(window._gaq) === 'object') {
        // Failsafe - be sure we do the callback in a half-second
        // even if GA isn't able to send in our trackEvent.
        timer = setTimeout(gaCallback, 500);
        // But ordinarily, we get GA to call us back immediately after
        // it finishes sending our things.
        window._gaq.push(
            // https://developers.google.com/analytics/devguides/collection/analyticsjs/advanced#hitCallback
            // This is called AFTER GA has sent all pending data:
            ['_set', 'hitCallback', gaCallback()]
        );
        // send events to GA
        window._gaq.push.apply(window._gaq, eventsArray);
    } else {
        // GA disabled or blocked or something, make sure we still
        // call the caller's callback:
        if (hasCallback) {
            callback();
        }
    }
}
<|endoftext|>


<commit_before>/*
Geodesic extension to Leaflet library, by Fragger
https://github.com/Fragger/Leaflet.Geodesic
Version from master branch, dated Apr 26, 2013
Modified by qnstie 2013-07-17 to maintain compatibility with Leaflet.draw
*/
(function () {
  function geodesicPoly(Klass, fill) {
    return Klass.extend({
      initialize: function (latlngs, options) {
        Klass.prototype.initialize.call(this, L.geodesicConvertLines(latlngs, fill), options);
        this._latlngsinit = this._convertLatLngs(latlngs);
      },
      getLatLngs: function () {
        return this._latlngsinit;
      },
      setLatLngs: function (latlngs) {
        this._latlngsinit = this._convertLatLngs(latlngs);
        return this.redraw();
      },
      addLatLng: function (latlng) {
        this._latlngsinit.push(L.latLng(latlng));
        return this.redraw();
      },
      spliceLatLngs: function () { // (Number index, Number howMany)
        var removed = [].splice.apply(this._latlngsinit, arguments);
        this._convertLatLngs(this._latlngsinit);
        this.redraw();
        return removed;
      },
      redraw: function() {
        this._latlngs = this._convertLatLngs(L.geodesicConvertLines(this._latlngsinit, fill));
        return Klass.prototype.redraw.call(this);
      }
    });
  }

  function geodesicConvertLine(startLatlng, endLatlng, convertedPoints) {
    var i,
      R = 6378137, // earth radius in meters (doesn't have to be exact)
      maxlength = 5000, // meters before splitting
      d2r = L.LatLng.DEG_TO_RAD,
      r2d = L.LatLng.RAD_TO_DEG,
      lat1, lat2, lng1, lng2, dLng, d, segments,
      f, A, B, x, y, z, fLat, fLng;

    dLng = (endLatlng.lng - startLatlng.lng) * d2r;
    lat1 = startLatlng.lat * d2r;
    lat2 = endLatlng.lat * d2r;
    lng1 = startLatlng.lng * d2r;
    lng2 = endLatlng.lng * d2r;

    // http://en.wikipedia.org/wiki/Great-circle_distance
    d = Math.atan2(Math.sqrt( Math.pow(Math.cos(lat2) * Math.sin(dLng), 2) + Math.pow(Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLng), 2) ), Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos(dLng));

    segments = Math.ceil(d * R / maxlength);
    // loop starts at 1 - we don't add the very first point
    // loop ends before 'segments' is reached - we don't add the very last point here but outside the loop
    // (this was to fix a bug - https://github.com/jonatkins/ingress-intel-total-conversion/issues/471
    //  rounding errors? maths bug? not sure - but it solves the issue! and is a slight optimisation)
    for (i = 1; i < segments; i++) {
      // http://williams.best.vwh.net/avform.htm#Intermediate
      // modified to handle longitude above +-180 degrees
      f = i / segments;
      A = Math.sin((1-f)*d) / Math.sin(d);
      B = Math.sin(f*d) / Math.sin(d);
      x = A * Math.cos(lat1) * Math.cos(0) + B * Math.cos(lat2) * Math.cos(dLng);
      y = A * Math.cos(lat1) * Math.sin(0) + B * Math.cos(lat2) * Math.sin(dLng);
      z = A * Math.sin(lat1)               + B * Math.sin(lat2);
      fLat = r2d * Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)));
      fLng = r2d * (Math.atan2(y, x)+lng1);

      convertedPoints.push(L.latLng([fLat, fLng]));
    }
    // push the final point unmodified
    convertedPoints.push(L.latLng(endLatlng));
  }

  L.geodesicConvertLines = function (latlngs, fill) {
    var i, j, len, geodesiclatlngs = [];
    for (i = 0, len = latlngs.length; i < len; i++) {
      if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') {
        return;
      }
      latlngs[i] = L.latLng(latlngs[i]);
    }

    if(!fill) {
      geodesiclatlngs.push(latlngs[0]);
    }
    for (i = 0, len = latlngs.length - 1; i < len; i++) {
      geodesicConvertLine(latlngs[i], latlngs[i+1], geodesiclatlngs);
    }
    if(fill) {
      geodesicConvertLine(latlngs[len], latlngs[0], geodesiclatlngs);
    }
    return geodesiclatlngs;
  }

  L.GeodesicPolyline = geodesicPoly(L.Polyline, 0);
  L.GeodesicPolygon = geodesicPoly(L.Polygon, 1);

  //L.GeodesicMultiPolyline = createMulti(L.GeodesicPolyline);
  //L.GeodesicMultiPolygon = createMulti(L.GeodesicPolygon);

  /*L.GeodesicMultiPolyline = L.MultiPolyline.extend({
    initialize: function (latlngs, options) {
      L.MultiPolyline.prototype.initialize.call(this, L.geodesicConvertLines(latlngs), options);
    }
  });*/

  /*L.GeodesicMultiPolygon = L.MultiPolygon.extend({
    initialize: function (latlngs, options) {
      L.MultiPolygon.prototype.initialize.call(this, L.geodesicConvertLines(latlngs), options);
    }
  });*/

  L.GeodesicCircle = L.Polygon.extend({
    initialize: function (latlng, radius, options) {
      this._latlng = L.latLng(latlng);
      this._mRadius = radius;

      points = this._calcPoints();

      L.Polygon.prototype.initialize.call(this, points, options);
    },

    options: {
      fill: true
    },

    setLatLng: function (latlng) {
      this._latlng = L.latLng(latlng);
      points = this._calcPoints();
      this.setLatLngs(points);
    },

    setRadius: function (radius) {
      this._mRadius = radius;
      points = this._calcPoints();
      this.setLatLngs(points);

    },

    getLatLng: function () {
      return this._latlng;
    },

    getRadius: function() {
      return this._mRadius;
    },

    _calcPoints: function() {
      var R = 6378137; //earth radius in meters (approx - taken from leaflet source code)
      var d2r = L.LatLng.DEG_TO_RAD;
      var r2d = L.LatLng.RAD_TO_DEG;
//console.log("geodesicCircle: radius = "+this._mRadius+"m, centre "+this._latlng.lat+","+this._latlng.lng);

      // circle radius as an angle from the centre of the earth
      var radRadius = this._mRadius / R;

//console.log(" (radius in radians "+radRadius);

      // pre-calculate various values used for every point on the circle
      var centreLat = this._latlng.lat * d2r;
      var centreLng = this._latlng.lng * d2r;

      var cosCentreLat = Math.cos(centreLat);
      var sinCentreLat = Math.sin(centreLat);

      var cosRadRadius = Math.cos(radRadius);
      var sinRadRadius = Math.sin(radRadius);

      var calcLatLngAtAngle = function(angle) {
        var lat = Math.asin(sinCentreLat*cosRadRadius + cosCentreLat*sinRadRadius*Math.cos(angle));
        var lng = centreLng + Math.atan2(Math.sin(angle)*sinRadRadius*cosCentreLat, cosRadRadius-sinCentreLat*Math.sin(lat));

        return L.latLng(lat * r2d,lng * r2d);
      }

      var segments = Math.max(32,Math.floor(this._mRadius/1000));
//console.log(" (drawing circle as "+segments+" lines)");
      var points = [];
      for (var i=0; i<segments; i++) {
        var angle = Math.PI*2/segments*i;

        var point = calcLatLngAtAngle(angle)
        points.push ( point );
      }

      return points;
    },

  });

  L.geodesicPolyline = function (latlngs, options) {
    return new L.GeodesicPolyline(latlngs, options);
  };

  L.geodesicPolygon = function (latlngs, options) {
    return new L.GeodesicPolygon(latlngs, options);
  };

  /*
  L.geodesicMultiPolyline = function (latlngs, options) {
    return new L.GeodesicMultiPolyline(latlngs, options);
  };

  L.geodesicMultiPolygon = function (latlngs, options) {
    return new L.GeodesicMultiPolygon(latlngs, options);
  };

  */

  L.geodesicCircle = function (latlng, radius, options) {
    return new L.GeodesicCircle(latlng, radius, options);
  }

}());
<commit_msg>geodesic lines: improve the handling for lines that extend beyond the antimeridian. no odd straight line segments leaflet doesn't have particularly great handling for this, but this is a reasonable measure for now<commit_after>/*
Geodesic extension to Leaflet library, by Fragger
https://github.com/Fragger/Leaflet.Geodesic
Version from master branch, dated Apr 26, 2013
Modified by qnstie 2013-07-17 to maintain compatibility with Leaflet.draw
*/
(function () {
  function geodesicPoly(Klass, fill) {
    return Klass.extend({
      initialize: function (latlngs, options) {
        Klass.prototype.initialize.call(this, L.geodesicConvertLines(latlngs, fill), options);
        this._latlngsinit = this._convertLatLngs(latlngs);
      },
      getLatLngs: function () {
        return this._latlngsinit;
      },
      setLatLngs: function (latlngs) {
        this._latlngsinit = this._convertLatLngs(latlngs);
        return this.redraw();
      },
      addLatLng: function (latlng) {
        this._latlngsinit.push(L.latLng(latlng));
        return this.redraw();
      },
      spliceLatLngs: function () { // (Number index, Number howMany)
        var removed = [].splice.apply(this._latlngsinit, arguments);
        this._convertLatLngs(this._latlngsinit);
        this.redraw();
        return removed;
      },
      redraw: function() {
        this._latlngs = this._convertLatLngs(L.geodesicConvertLines(this._latlngsinit, fill));
        return Klass.prototype.redraw.call(this);
      }
    });
  }

  function geodesicConvertLine(startLatlng, endLatlng, convertedPoints) {
    var i,
      R = 6378137, // earth radius in meters (doesn't have to be exact)
      maxlength = 5000, // meters before splitting
      d2r = L.LatLng.DEG_TO_RAD,
      r2d = L.LatLng.RAD_TO_DEG,
      lat1, lat2, lng1, lng2, dLng, d, segments,
      f, A, B, x, y, z, fLat, fLng;

    dLng = (endLatlng.lng - startLatlng.lng) * d2r;
    lat1 = startLatlng.lat * d2r;
    lat2 = endLatlng.lat * d2r;
    lng1 = startLatlng.lng * d2r;
    lng2 = endLatlng.lng * d2r;

    // http://en.wikipedia.org/wiki/Great-circle_distance
    d = Math.atan2(Math.sqrt( Math.pow(Math.cos(lat2) * Math.sin(dLng), 2) + Math.pow(Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLng), 2) ), Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos(dLng));

    segments = Math.ceil(d * R / maxlength);
    // loop starts at 1 - we don't add the very first point
    // loop ends before 'segments' is reached - we don't add the very last point here but outside the loop
    // (this was to fix a bug - https://github.com/jonatkins/ingress-intel-total-conversion/issues/471
    //  rounding errors? maths bug? not sure - but it solves the issue! and is a slight optimisation)
    for (i = 1; i < segments; i++) {
      // http://williams.best.vwh.net/avform.htm#Intermediate
      // modified to handle longitude above +-180 degrees
      f = i / segments;
      A = Math.sin((1-f)*d) / Math.sin(d);
      B = Math.sin(f*d) / Math.sin(d);
      x = A * Math.cos(lat1) * Math.cos(0) + B * Math.cos(lat2) * Math.cos(dLng);
      y = A * Math.cos(lat1) * Math.sin(0) + B * Math.cos(lat2) * Math.sin(dLng);
      z = A * Math.sin(lat1)               + B * Math.sin(lat2);
      fLat = r2d * Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)));
      fLng = r2d * (Math.atan2(y, x)+lng1);

      convertedPoints.push(L.latLng([fLat, fLng]));
    }
    // push the final point unmodified
    convertedPoints.push(L.latLng(endLatlng));
  }

  L.geodesicConvertLines = function (latlngs, fill) {
    if (latlngs.length == 0) {
      return [];
    }

    for (var i = 0, len = latlngs.length; i < len; i++) {
      if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') {
        return;
      }
      latlngs[i] = L.latLng(latlngs[i]);
    }

    // geodrsic calculations have issues when crossing the anti-meridian. so offset the points
    // so this isn't an issue, then add back the offset afterwards
    // a center longitude would be ideal - but the start point logitude will be 'good enougn'
    var lngOffset = latlngs[0].lng;

    // points are wrapped after being offset relative to the first point coordinate, so they're
    // within +-180 degrees
    latlngs = latlngs.map(function(a){ return L.latLng(a.lat, a.lng-lngOffset).wrap(); });

    var geodesiclatlngs = [];

    if(!fill) {
      geodesiclatlngs.push(latlngs[0]);
    }
    for (i = 0, len = latlngs.length - 1; i < len; i++) {
      geodesicConvertLine(latlngs[i], latlngs[i+1], geodesiclatlngs);
    }
    if(fill) {
      geodesicConvertLine(latlngs[len], latlngs[0], geodesiclatlngs);
    }

    // now add back the offset subtracted above. no wrapping here - the drawing code handles
    // things better when there's no sudden jumps in coordinates. yes, lines will extend
    // beyond +-180 degrees - but they won't be 'broken'
    geodesiclatlngs = geodesiclatlngs.map(function(a){ return L.latLng(a.lat, a.lng+lngOffset); });

    return geodesiclatlngs;
  }

  L.GeodesicPolyline = geodesicPoly(L.Polyline, 0);
  L.GeodesicPolygon = geodesicPoly(L.Polygon, 1);

  //L.GeodesicMultiPolyline = createMulti(L.GeodesicPolyline);
  //L.GeodesicMultiPolygon = createMulti(L.GeodesicPolygon);

  /*L.GeodesicMultiPolyline = L.MultiPolyline.extend({
    initialize: function (latlngs, options) {
      L.MultiPolyline.prototype.initialize.call(this, L.geodesicConvertLines(latlngs), options);
    }
  });*/

  /*L.GeodesicMultiPolygon = L.MultiPolygon.extend({
    initialize: function (latlngs, options) {
      L.MultiPolygon.prototype.initialize.call(this, L.geodesicConvertLines(latlngs), options);
    }
  });*/

  L.GeodesicCircle = L.Polygon.extend({
    initialize: function (latlng, radius, options) {
      this._latlng = L.latLng(latlng);
      this._mRadius = radius;

      points = this._calcPoints();

      L.Polygon.prototype.initialize.call(this, points, options);
    },

    options: {
      fill: true
    },

    setLatLng: function (latlng) {
      this._latlng = L.latLng(latlng);
      points = this._calcPoints();
      this.setLatLngs(points);
    },

    setRadius: function (radius) {
      this._mRadius = radius;
      points = this._calcPoints();
      this.setLatLngs(points);

    },

    getLatLng: function () {
      return this._latlng;
    },

    getRadius: function() {
      return this._mRadius;
    },

    _calcPoints: function() {
      var R = 6378137; //earth radius in meters (approx - taken from leaflet source code)
      var d2r = L.LatLng.DEG_TO_RAD;
      var r2d = L.LatLng.RAD_TO_DEG;
//console.log("geodesicCircle: radius = "+this._mRadius+"m, centre "+this._latlng.lat+","+this._latlng.lng);

      // circle radius as an angle from the centre of the earth
      var radRadius = this._mRadius / R;

//console.log(" (radius in radians "+radRadius);

      // pre-calculate various values used for every point on the circle
      var centreLat = this._latlng.lat * d2r;
      var centreLng = this._latlng.lng * d2r;

      var cosCentreLat = Math.cos(centreLat);
      var sinCentreLat = Math.sin(centreLat);

      var cosRadRadius = Math.cos(radRadius);
      var sinRadRadius = Math.sin(radRadius);

      var calcLatLngAtAngle = function(angle) {
        var lat = Math.asin(sinCentreLat*cosRadRadius + cosCentreLat*sinRadRadius*Math.cos(angle));
        var lng = centreLng + Math.atan2(Math.sin(angle)*sinRadRadius*cosCentreLat, cosRadRadius-sinCentreLat*Math.sin(lat));

        return L.latLng(lat * r2d,lng * r2d);
      }

      var segments = Math.max(32,Math.floor(this._mRadius/1000));
//console.log(" (drawing circle as "+segments+" lines)");
      var points = [];
      for (var i=0; i<segments; i++) {
        var angle = Math.PI*2/segments*i;

        var point = calcLatLngAtAngle(angle)
        points.push ( point );
      }

      return points;
    },

  });

  L.geodesicPolyline = function (latlngs, options) {
    return new L.GeodesicPolyline(latlngs, options);
  };

  L.geodesicPolygon = function (latlngs, options) {
    return new L.GeodesicPolygon(latlngs, options);
  };

  /*
  L.geodesicMultiPolyline = function (latlngs, options) {
    return new L.GeodesicMultiPolyline(latlngs, options);
  };

  L.geodesicMultiPolygon = function (latlngs, options) {
    return new L.GeodesicMultiPolygon(latlngs, options);
  };

  */

  L.geodesicCircle = function (latlng, radius, options) {
    return new L.GeodesicCircle(latlng, radius, options);
  }

}());
<|endoftext|>