<commit_before>'use strict';

angular.module('copayApp.controllers').controller('tabSendController', function($scope, $rootScope, $log, $timeout, $ionicScrollDelegate, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService, platformInfo, bwcError, gettextCatalog, scannerService, configService, bitcoinCashJsService, $ionicNavBarDelegate, clipboardService) {
  var clipboardHasAddress = false;
  var clipboardHasContent = false;
  var originalList;
  $scope.displayBalanceAsFiat = true;
  $scope.walletSelectorTitleForce = true;

  $scope.walletHide = function() {
    console.log("wallet HIDE");
    alert('test');
  };

  $scope.addContact = function() {
    $state.go('tabs.settings').then(function() {
      $state.go('tabs.addressbook').then(function() {
        $state.go('tabs.addressbook.add');
      });
    });
  };

  $scope.pasteClipboard = function() {
    if ($scope.clipboardHasAddress || $scope.clipboardHasContent) {
      clipboardService.readFromClipboard(function(text) {
        $scope.formData.search = text;
        $scope.findContact($scope.formData.search);
      });
    }
  };

  $scope.$on("$ionicView.enter", function(event, data) {
    clipboardService.readFromClipboard(function(text) {
      if (text.length > 200) {
        text = text.substring(0, 200);
      }

      $scope.clipboardHasAddress = false;
      $scope.clipboardHasContent = false;
      if ((text.indexOf('bitcoincash:') === 0 || text[0] === 'C' || text[0] === 'H' || text[0] === 'p' || text[0] === 'q') && text.replace('bitcoincash:', '').length === 42) { // CashAddr
        $scope.clipboardHasAddress = true;
      } else if ((text[0] === "1" || text[0] === "3" || text.substring(0, 3) === "bc1") && text.length >= 26 && text.length <= 35) { // Legacy Addresses
        $scope.clipboardHasAddress = true;
      } else if (text.length > 1) {
        $scope.clipboardHasContent = true;
      }
    });

    $ionicNavBarDelegate.showBar(true);
    if (!$scope.hasWallets) {
      $scope.checkingBalance = false;
      return;
    }
    updateHasFunds();
    updateContactsList(function() {
      updateList();
    });
  });

  var wallets;
  var walletsBch;
  var walletsBtc;
  var walletToWalletFrom = false;

  $scope.onWalletSelect = function(wallet) {
    if (!$scope.walletToWalletFrom) {
      $scope.walletToWalletFrom = wallet;
      if (wallet.coin === 'bch') {
        $scope.showWalletsBch = true;
      } else if (wallet.coin === 'btc') {
        $scope.showWalletsBtc = true;
      }
      $scope.walletSelectorTitleTo = gettextCatalog.getString('Send to');
    } else {
      walletService.getAddress(wallet, true, function(err, addr) {
        return $state.transitionTo('tabs.send.amount', {
          displayAddress: $scope.walletToWalletFrom.coin === 'bch' ? bitcoinCashJsService.translateAddresses(addr).cashaddr : addr,
          recipientType: 'wallet',
          fromWalletId: $scope.walletToWalletFrom.walletId,
          toAddress: addr,
          coin: $scope.walletToWalletFrom.coin
        });
      });

    }
  };

  $scope.showWalletSelector = function() {
    $scope.walletToWalletFrom = false;
    $scope.walletSelectorTitleFrom = gettextCatalog.getString('Send from');
    $scope.showWallets = true;
  };

  $scope.findContact = function(search) {

    if (incomingData.redir(search)) {
      return;
    }

    if (!search || search.length < 1) {
      $scope.list = originalList;
      $timeout(function() {
        $scope.$apply();
      });
      return;
    }

    var result = lodash.filter(originalList, function(item) {
      var val = item.name;
      return lodash.startsWith(val.toLowerCase(), search.toLowerCase());
    });

    $scope.list = result;
  };

  var hasWallets = function() {
    $scope.wallets = profileService.getWallets({
      onlyComplete: true
    });
    $scope.walletsBch = profileService.getWallets({
      onlyComplete: true,
      coin: 'bch'
    });
    $scope.walletsBtc = profileService.getWallets({
      onlyComplete: true,
      coin: 'btc'
    });
    $scope.hasWallets = lodash.isEmpty($scope.wallets) ? false : true;
  };

  var updateHasFunds = function() {

    if ($rootScope.everHasFunds) {
      $scope.hasFunds = true;
      return;
    }

    $scope.hasFunds = false;
    var index = 0;
    lodash.each($scope.wallets, function(w) {
      walletService.getStatus(w, {}, function(err, status) {

        ++index;
        if (err && !status) {
          $log.error(err);
          // error updating the wallet. Probably a network error, do not show
          // the 'buy bitcoins' message.

          $scope.hasFunds = true;
        } else if (status.availableBalanceSat > 0) {
          $scope.hasFunds = true;
          $rootScope.everHasFunds = true;
        }

        if (index == $scope.wallets.length) {
          $scope.checkingBalance = false;
          $timeout(function() {
            $scope.$apply();
          });
        }
      });
    });
  };

  var updateContactsList = function(cb) {
    var config = configService.getSync();
    var defaults = configService.getDefaults();
    addressbookService.list(function(err, ab) {
      if (err) $log.error(err);

      $scope.hasContacts = lodash.isEmpty(ab) ? false : true;
      if (!$scope.hasContacts) return cb();

      var completeContacts = [];
      lodash.each(ab, function(v, k) {
        completeContacts.push({
          name: lodash.isObject(v) ? v.name : v,
          address: k,
          email: lodash.isObject(v) ? v.email : null,
          recipientType: 'contact',
          coin: v.coin,
          displayCoin:  (v.coin == 'bch'
              ? (config.bitcoinCashAlias || defaults.bitcoinCashAlias)
              : (config.bitcoinAlias || defaults.bitcoinAlias)).toUpperCase(),
          getAddress: function(cb) {
            return cb(null, k);
          },
        });
      });
      originalList = completeContacts;
      return cb();
    });
  };

  var updateList = function() {
    $scope.list = lodash.clone(originalList);
    $timeout(function() {
      $ionicScrollDelegate.resize();
      $scope.$apply();
    }, 10);
  };

  $scope.searchInFocus = function() {
    $scope.searchFocus = true;
  };

  $scope.searchBlurred = function() {
    if ($scope.formData.search == null || $scope.formData.search.length == 0) {
      $scope.searchFocus = false;
    }
  };

  $scope.goToAmount = function(item) {
    $timeout(function() {
      item.getAddress(function(err, addr) {
        if (err || !addr) {
          //Error is already formated
          return popupService.showAlert(err);
        }

        if (item.recipientType && item.recipientType == 'contact') {
          if (addr.indexOf('bch') == 0 || addr.indexOf('btc') == 0) {
            addr = addr.substring(3);
          }
        }

        $log.debug('Got toAddress:' + addr + ' | ' + item.name);
        return $state.transitionTo('tabs.send.amount', {
          recipientType: item.recipientType,
          displayAddress: item.coin == 'bch' ? bitcoinCashJsService.translateAddresses(addr).cashaddr : addr,
          toAddress: addr,
          toName: item.name,
          toEmail: item.email,
          toColor: item.color,
          coin: item.coin
        });
      });
    });
  };

  // This could probably be enhanced refactoring the routes abstract states
  $scope.createWallet = function() {
    $state.go('tabs.home').then(function() {
      $state.go('tabs.add.create-personal');
    });
  };

  $scope.buyBitcoin = function() {
    $state.go('tabs.home').then(function() {
      $state.go('tabs.buyandsell');
    });
  };

  $scope.$on("$ionicView.beforeEnter", function(event, data) {
    $scope.showWalletsBch = $scope.showWalletsBtc = $scope.showWallets = false;

    $scope.checkingBalance = true;
    $scope.formData = {
      search: null
    };
    originalList = [];
    hasWallets();

    configService.whenAvailable(function(_config) {
      $scope.displayBalanceAsFiat = _config.wallet.settings.priceDisplay === 'fiat';
    });

  });
});
<commit_msg>Shows "Empty case" send screen after sending all funds from wallets.<commit_after>'use strict';

angular.module('copayApp.controllers').controller('tabSendController', function($scope, $rootScope, $log, $timeout, $ionicScrollDelegate, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService, platformInfo, bwcError, gettextCatalog, scannerService, configService, bitcoinCashJsService, $ionicNavBarDelegate, clipboardService) {
  var clipboardHasAddress = false;
  var clipboardHasContent = false;
  var originalList;
  $scope.displayBalanceAsFiat = true;
  $scope.walletSelectorTitleForce = true;

  $scope.walletHide = function() {
    console.log("wallet HIDE");
    alert('test');
  };

  $scope.addContact = function() {
    $state.go('tabs.settings').then(function() {
      $state.go('tabs.addressbook').then(function() {
        $state.go('tabs.addressbook.add');
      });
    });
  };

  $scope.pasteClipboard = function() {
    if ($scope.clipboardHasAddress || $scope.clipboardHasContent) {
      clipboardService.readFromClipboard(function(text) {
        $scope.formData.search = text;
        $scope.findContact($scope.formData.search);
      });
    }
  };

  $scope.$on("$ionicView.enter", function(event, data) {
    clipboardService.readFromClipboard(function(text) {
      if (text.length > 200) {
        text = text.substring(0, 200);
      }

      $scope.clipboardHasAddress = false;
      $scope.clipboardHasContent = false;
      if ((text.indexOf('bitcoincash:') === 0 || text[0] === 'C' || text[0] === 'H' || text[0] === 'p' || text[0] === 'q') && text.replace('bitcoincash:', '').length === 42) { // CashAddr
        $scope.clipboardHasAddress = true;
      } else if ((text[0] === "1" || text[0] === "3" || text.substring(0, 3) === "bc1") && text.length >= 26 && text.length <= 35) { // Legacy Addresses
        $scope.clipboardHasAddress = true;
      } else if (text.length > 1) {
        $scope.clipboardHasContent = true;
      }
    });

    $ionicNavBarDelegate.showBar(true);
    if (!$scope.hasWallets) {
      $scope.checkingBalance = false;
      return;
    }
    updateHasFunds();
    updateContactsList(function() {
      updateList();
    });
  });

  var wallets;
  var walletsBch;
  var walletsBtc;
  var walletToWalletFrom = false;

  $scope.onWalletSelect = function(wallet) {
    if (!$scope.walletToWalletFrom) {
      $scope.walletToWalletFrom = wallet;
      if (wallet.coin === 'bch') {
        $scope.showWalletsBch = true;
      } else if (wallet.coin === 'btc') {
        $scope.showWalletsBtc = true;
      }
      $scope.walletSelectorTitleTo = gettextCatalog.getString('Send to');
    } else {
      walletService.getAddress(wallet, true, function(err, addr) {
        return $state.transitionTo('tabs.send.amount', {
          displayAddress: $scope.walletToWalletFrom.coin === 'bch' ? bitcoinCashJsService.translateAddresses(addr).cashaddr : addr,
          recipientType: 'wallet',
          fromWalletId: $scope.walletToWalletFrom.walletId,
          toAddress: addr,
          coin: $scope.walletToWalletFrom.coin
        });
      });

    }
  };

  $scope.showWalletSelector = function() {
    $scope.walletToWalletFrom = false;
    $scope.walletSelectorTitleFrom = gettextCatalog.getString('Send from');
    $scope.showWallets = true;
  };

  $scope.findContact = function(search) {

    if (incomingData.redir(search)) {
      return;
    }

    if (!search || search.length < 1) {
      $scope.list = originalList;
      $timeout(function() {
        $scope.$apply();
      });
      return;
    }

    var result = lodash.filter(originalList, function(item) {
      var val = item.name;
      return lodash.startsWith(val.toLowerCase(), search.toLowerCase());
    });

    $scope.list = result;
  };

  var hasWallets = function() {
    $scope.wallets = profileService.getWallets({
      onlyComplete: true
    });
    $scope.walletsBch = profileService.getWallets({
      onlyComplete: true,
      coin: 'bch'
    });
    $scope.walletsBtc = profileService.getWallets({
      onlyComplete: true,
      coin: 'btc'
    });
    $scope.hasWallets = lodash.isEmpty($scope.wallets) ? false : true;
  };

  var updateHasFunds = function() {

    $scope.hasFunds = false;
    var index = 0;
    lodash.each($scope.wallets, function(w) {
      walletService.getStatus(w, {}, function(err, status) {

        ++index;
        if (err && !status) {
          $log.error(err);
          // error updating the wallet. Probably a network error, do not show
          // the 'buy bitcoins' message.

          $scope.hasFunds = true;
        } else if (status.availableBalanceSat > 0) {
          $scope.hasFunds = true;
        }

        if (index === $scope.wallets.length) {
          $scope.checkingBalance = false;
          $timeout(function() {
            $scope.$apply();
          });
        }
      });
    });
  };

  var updateContactsList = function(cb) {
    var config = configService.getSync();
    var defaults = configService.getDefaults();
    addressbookService.list(function(err, ab) {
      if (err) $log.error(err);

      $scope.hasContacts = lodash.isEmpty(ab) ? false : true;
      if (!$scope.hasContacts) return cb();

      var completeContacts = [];
      lodash.each(ab, function(v, k) {
        completeContacts.push({
          name: lodash.isObject(v) ? v.name : v,
          address: k,
          email: lodash.isObject(v) ? v.email : null,
          recipientType: 'contact',
          coin: v.coin,
          displayCoin:  (v.coin == 'bch'
              ? (config.bitcoinCashAlias || defaults.bitcoinCashAlias)
              : (config.bitcoinAlias || defaults.bitcoinAlias)).toUpperCase(),
          getAddress: function(cb) {
            return cb(null, k);
          },
        });
      });
      originalList = completeContacts;
      return cb();
    });
  };

  var updateList = function() {
    $scope.list = lodash.clone(originalList);
    $timeout(function() {
      $ionicScrollDelegate.resize();
      $scope.$apply();
    }, 10);
  };

  $scope.searchInFocus = function() {
    $scope.searchFocus = true;
  };

  $scope.searchBlurred = function() {
    if ($scope.formData.search == null || $scope.formData.search.length == 0) {
      $scope.searchFocus = false;
    }
  };

  $scope.goToAmount = function(item) {
    $timeout(function() {
      item.getAddress(function(err, addr) {
        if (err || !addr) {
          //Error is already formated
          return popupService.showAlert(err);
        }

        if (item.recipientType && item.recipientType == 'contact') {
          if (addr.indexOf('bch') == 0 || addr.indexOf('btc') == 0) {
            addr = addr.substring(3);
          }
        }

        $log.debug('Got toAddress:' + addr + ' | ' + item.name);
        return $state.transitionTo('tabs.send.amount', {
          recipientType: item.recipientType,
          displayAddress: item.coin == 'bch' ? bitcoinCashJsService.translateAddresses(addr).cashaddr : addr,
          toAddress: addr,
          toName: item.name,
          toEmail: item.email,
          toColor: item.color,
          coin: item.coin
        });
      });
    });
  };

  // This could probably be enhanced refactoring the routes abstract states
  $scope.createWallet = function() {
    $state.go('tabs.home').then(function() {
      $state.go('tabs.add.create-personal');
    });
  };

  $scope.buyBitcoin = function() {
    $state.go('tabs.home').then(function() {
      $state.go('tabs.buyandsell');
    });
  };

  $scope.$on("$ionicView.beforeEnter", function(event, data) {
    $scope.showWalletsBch = $scope.showWalletsBtc = $scope.showWallets = false;

    $scope.checkingBalance = true;
    $scope.formData = {
      search: null
    };
    originalList = [];
    hasWallets();

    configService.whenAvailable(function(_config) {
      $scope.displayBalanceAsFiat = _config.wallet.settings.priceDisplay === 'fiat';
    });

  });
});
<|endoftext|>


<commit_before>/*
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */
/* eslint-env node */
'use strict';

import * as utils from '../utils';
export {shimGetUserMedia} from './getusermedia';
export {shimGetDisplayMedia} from './getdisplaymedia';

export function shimOnTrack(window) {
  if (typeof window === 'object' && window.RTCTrackEvent &&
      ('receiver' in window.RTCTrackEvent.prototype) &&
      !('transceiver' in window.RTCTrackEvent.prototype)) {
    Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', {
      get() {
        return {receiver: this.receiver};
      }
    });
  }
}

export function shimPeerConnection(window) {
  const browserDetails = utils.detectBrowser(window);

  if (typeof window !== 'object' ||
      !(window.RTCPeerConnection || window.mozRTCPeerConnection)) {
    return; // probably media.peerconnection.enabled=false in about:config
  }
  if (!window.RTCPeerConnection && window.mozRTCPeerConnection) {
    // very basic support for old versions.
    window.RTCPeerConnection = window.mozRTCPeerConnection;
  }

  if (browserDetails.version < 53) {
    // shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
        .forEach(function(method) {
          const nativeMethod = window.RTCPeerConnection.prototype[method];
          const methodObj = {[method]() {
            arguments[0] = new ((method === 'addIceCandidate') ?
                window.RTCIceCandidate :
                window.RTCSessionDescription)(arguments[0]);
            return nativeMethod.apply(this, arguments);
          }};
          window.RTCPeerConnection.prototype[method] = methodObj[method];
        });
  }

  // support for addIceCandidate(null or undefined)
  // as well as ignoring {sdpMid, candidate: ""}
  if (browserDetails.version < 68) {
    const nativeAddIceCandidate =
        window.RTCPeerConnection.prototype.addIceCandidate;
    window.RTCPeerConnection.prototype.addIceCandidate =
    function addIceCandidate() {
      if (!arguments[0]) {
        if (arguments[1]) {
          arguments[1].apply(null);
        }
        return Promise.resolve();
      }
      // Firefox 68+ emits and processes {candidate: "", ...}, ignore
      // in older versions.
      if (arguments[0] && arguments[0].candidate === '') {
        return Promise.resolve();
      }
      return nativeAddIceCandidate.apply(this, arguments);
    };
  }

  const modernStatsTypes = {
    inboundrtp: 'inbound-rtp',
    outboundrtp: 'outbound-rtp',
    candidatepair: 'candidate-pair',
    localcandidate: 'local-candidate',
    remotecandidate: 'remote-candidate'
  };

  const nativeGetStats = window.RTCPeerConnection.prototype.getStats;
  window.RTCPeerConnection.prototype.getStats = function getStats() {
    const [selector, onSucc, onErr] = arguments;
    return nativeGetStats.apply(this, [selector || null])
      .then(stats => {
        if (browserDetails.version < 53 && !onSucc) {
          // Shim only promise getStats with spec-hyphens in type names
          // Leave callback version alone; misc old uses of forEach before Map
          try {
            stats.forEach(stat => {
              stat.type = modernStatsTypes[stat.type] || stat.type;
            });
          } catch (e) {
            if (e.name !== 'TypeError') {
              throw e;
            }
            // Avoid TypeError: "type" is read-only, in old versions. 34-43ish
            stats.forEach((stat, i) => {
              stats.set(i, Object.assign({}, stat, {
                type: modernStatsTypes[stat.type] || stat.type
              }));
            });
          }
        }
        return stats;
      })
      .then(onSucc, onErr);
  };
}

export function shimSenderGetStats(window) {
  if (!(typeof window === 'object' && window.RTCPeerConnection &&
      window.RTCRtpSender)) {
    return;
  }
  if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) {
    return;
  }
  const origGetSenders = window.RTCPeerConnection.prototype.getSenders;
  if (origGetSenders) {
    window.RTCPeerConnection.prototype.getSenders = function getSenders() {
      const senders = origGetSenders.apply(this, []);
      senders.forEach(sender => sender._pc = this);
      return senders;
    };
  }

  const origAddTrack = window.RTCPeerConnection.prototype.addTrack;
  if (origAddTrack) {
    window.RTCPeerConnection.prototype.addTrack = function addTrack() {
      const sender = origAddTrack.apply(this, arguments);
      sender._pc = this;
      return sender;
    };
  }
  window.RTCRtpSender.prototype.getStats = function getStats() {
    return this.track ? this._pc.getStats(this.track) :
        Promise.resolve(new Map());
  };
}

export function shimReceiverGetStats(window) {
  if (!(typeof window === 'object' && window.RTCPeerConnection &&
      window.RTCRtpSender)) {
    return;
  }
  if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) {
    return;
  }
  const origGetReceivers = window.RTCPeerConnection.prototype.getReceivers;
  if (origGetReceivers) {
    window.RTCPeerConnection.prototype.getReceivers = function getReceivers() {
      const receivers = origGetReceivers.apply(this, []);
      receivers.forEach(receiver => receiver._pc = this);
      return receivers;
    };
  }
  utils.wrapPeerConnectionEvent(window, 'track', e => {
    e.receiver._pc = e.srcElement;
    return e;
  });
  window.RTCRtpReceiver.prototype.getStats = function getStats() {
    return this._pc.getStats(this.track);
  };
}

export function shimRemoveStream(window) {
  if (!window.RTCPeerConnection ||
      'removeStream' in window.RTCPeerConnection.prototype) {
    return;
  }
  window.RTCPeerConnection.prototype.removeStream =
    function removeStream(stream) {
      utils.deprecated('removeStream', 'removeTrack');
      this.getSenders().forEach(sender => {
        if (sender.track && stream.getTracks().includes(sender.track)) {
          this.removeTrack(sender);
        }
      });
    };
}

export function shimRTCDataChannel(window) {
  // rename DataChannel to RTCDataChannel (native fix in FF60):
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1173851
  if (window.DataChannel && !window.RTCDataChannel) {
    window.RTCDataChannel = window.DataChannel;
  }
}

export function shimAddTransceiver(window) {
  // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
  // Firefox ignores the init sendEncodings options passed to addTransceiver
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
  if (!(typeof window === 'object' && window.RTCPeerConnection)) {
    return;
  }
  const origAddTransceiver = window.RTCPeerConnection.prototype.addTransceiver;
  if (origAddTransceiver) {
    window.RTCPeerConnection.prototype.addTransceiver =
      function addTransceiver() {
        this.setParametersPromises = [];
        const initParameters = arguments[1];
        const shouldPerformCheck = initParameters &&
                                  'sendEncodings' in initParameters;
        if (shouldPerformCheck) {
          // If sendEncodings params are provided, validate grammar
          initParameters.sendEncodings.forEach((encodingParam) => {
            if ('rid' in encodingParam) {
              const ridRegex = /^[a-z0-9]{0,16}$/i;
              if (!ridRegex.test(encodingParam.rid)) {
                throw new TypeError('Invalid RID value provided.');
              }
            }
            if ('scaleResolutionDownBy' in encodingParam) {
              if (!(parseFloat(encodingParam.scaleResolutionDownBy) >= 1.0)) {
                throw new RangeError('scale_resolution_down_by must be >= 1.0');
              }
            }
            if ('maxFramerate' in encodingParam) {
              if (!(parseFloat(encodingParam.maxFramerate) >= 0)) {
                throw new RangeError('max_framerate must be >= 0.0');
              }
            }
          });
        }
        const transceiver = origAddTransceiver.apply(this, arguments);
        if (shouldPerformCheck) {
          // Check if the init options were applied. If not we do this in an
          // asynchronous way and save the promise reference in a global object.
          // This is an ugly hack, but at the same time is way more robust than
          // checking the sender parameters before and after the createOffer
          // Also note that after the createoffer we are not 100% sure that
          // the params were asynchronously applied so we might miss the
          // opportunity to recreate offer.
          const {sender} = transceiver;
          const params = sender.getParameters();
          if (!('encodings' in params)) {
            params.encodings = initParameters.sendEncodings;
            sender.sendEncodings = initParameters.sendEncodings;
            this.setParametersPromises.push(sender.setParameters(params)
              .then(() => {
                delete sender.sendEncodings;
              }).catch(() => {
                delete sender.sendEncodings;
              })
            );
          }
        }
        return transceiver;
      };
  }
}

export function shimGetParameters(window) {
  if (!(typeof window === 'object' && window.RTCRtpSender)) {
    return;
  }
  const origGetParameters = window.RTCRtpSender.prototype.getParameters;
  if (origGetParameters) {
    window.RTCRtpSender.prototype.getParameters =
      function getParameters() {
        var params = origGetParameters.apply(this, arguments);
        if (!('sendEncodings' in this)) {
          return params;
        }
        return Object.assign({}, {encodings: this.sendEncodings}, params);
      };
  }
}

export function shimCreateOffer(window) {
  // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
  // Firefox ignores the init sendEncodings options passed to addTransceiver
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
  if (!(typeof window === 'object' && window.RTCPeerConnection)) {
    return;
  }
  const origCreateOffer = window.RTCPeerConnection.prototype.createOffer;
  window.RTCPeerConnection.prototype.createOffer = function createOffer() {
    if (this.setParametersPromises && this.setParametersPromises.length) {
      return Promise.all(this.setParametersPromises)
      .then(() => {
        return origCreateOffer.apply(this, arguments);
      })
      .finally(() => {
        this.setParametersPromises = [];
      });
    }
    return origCreateOffer.apply(this, arguments);
  };
}

export function shimCreateAnswer(window) {
  // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
  // Firefox ignores the init sendEncodings options passed to addTransceiver
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
  if (!(typeof window === 'object' && window.RTCPeerConnection)) {
    return;
  }
  const origCreateAnswer = window.RTCPeerConnection.prototype.createAnswer;
  window.RTCPeerConnection.prototype.createAnswer = function createAnswer() {
    if (this.setParametersPromises && this.setParametersPromises.length) {
      return Promise.all(this.setParametersPromises)
      .then(() => {
        return origCreateAnswer.apply(this, arguments);
      })
      .finally(() => {
        this.setParametersPromises = [];
      });
    }
    return origCreateAnswer.apply(this, arguments);
  };
}
<commit_msg>Add default encodings[0] to getParameters() when missing in Firefox.<commit_after>/*
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */
/* eslint-env node */
'use strict';

import * as utils from '../utils';
export {shimGetUserMedia} from './getusermedia';
export {shimGetDisplayMedia} from './getdisplaymedia';

export function shimOnTrack(window) {
  if (typeof window === 'object' && window.RTCTrackEvent &&
      ('receiver' in window.RTCTrackEvent.prototype) &&
      !('transceiver' in window.RTCTrackEvent.prototype)) {
    Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', {
      get() {
        return {receiver: this.receiver};
      }
    });
  }
}

export function shimPeerConnection(window) {
  const browserDetails = utils.detectBrowser(window);

  if (typeof window !== 'object' ||
      !(window.RTCPeerConnection || window.mozRTCPeerConnection)) {
    return; // probably media.peerconnection.enabled=false in about:config
  }
  if (!window.RTCPeerConnection && window.mozRTCPeerConnection) {
    // very basic support for old versions.
    window.RTCPeerConnection = window.mozRTCPeerConnection;
  }

  if (browserDetails.version < 53) {
    // shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
        .forEach(function(method) {
          const nativeMethod = window.RTCPeerConnection.prototype[method];
          const methodObj = {[method]() {
            arguments[0] = new ((method === 'addIceCandidate') ?
                window.RTCIceCandidate :
                window.RTCSessionDescription)(arguments[0]);
            return nativeMethod.apply(this, arguments);
          }};
          window.RTCPeerConnection.prototype[method] = methodObj[method];
        });
  }

  // support for addIceCandidate(null or undefined)
  // as well as ignoring {sdpMid, candidate: ""}
  if (browserDetails.version < 68) {
    const nativeAddIceCandidate =
        window.RTCPeerConnection.prototype.addIceCandidate;
    window.RTCPeerConnection.prototype.addIceCandidate =
    function addIceCandidate() {
      if (!arguments[0]) {
        if (arguments[1]) {
          arguments[1].apply(null);
        }
        return Promise.resolve();
      }
      // Firefox 68+ emits and processes {candidate: "", ...}, ignore
      // in older versions.
      if (arguments[0] && arguments[0].candidate === '') {
        return Promise.resolve();
      }
      return nativeAddIceCandidate.apply(this, arguments);
    };
  }

  const modernStatsTypes = {
    inboundrtp: 'inbound-rtp',
    outboundrtp: 'outbound-rtp',
    candidatepair: 'candidate-pair',
    localcandidate: 'local-candidate',
    remotecandidate: 'remote-candidate'
  };

  const nativeGetStats = window.RTCPeerConnection.prototype.getStats;
  window.RTCPeerConnection.prototype.getStats = function getStats() {
    const [selector, onSucc, onErr] = arguments;
    return nativeGetStats.apply(this, [selector || null])
      .then(stats => {
        if (browserDetails.version < 53 && !onSucc) {
          // Shim only promise getStats with spec-hyphens in type names
          // Leave callback version alone; misc old uses of forEach before Map
          try {
            stats.forEach(stat => {
              stat.type = modernStatsTypes[stat.type] || stat.type;
            });
          } catch (e) {
            if (e.name !== 'TypeError') {
              throw e;
            }
            // Avoid TypeError: "type" is read-only, in old versions. 34-43ish
            stats.forEach((stat, i) => {
              stats.set(i, Object.assign({}, stat, {
                type: modernStatsTypes[stat.type] || stat.type
              }));
            });
          }
        }
        return stats;
      })
      .then(onSucc, onErr);
  };
}

export function shimSenderGetStats(window) {
  if (!(typeof window === 'object' && window.RTCPeerConnection &&
      window.RTCRtpSender)) {
    return;
  }
  if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) {
    return;
  }
  const origGetSenders = window.RTCPeerConnection.prototype.getSenders;
  if (origGetSenders) {
    window.RTCPeerConnection.prototype.getSenders = function getSenders() {
      const senders = origGetSenders.apply(this, []);
      senders.forEach(sender => sender._pc = this);
      return senders;
    };
  }

  const origAddTrack = window.RTCPeerConnection.prototype.addTrack;
  if (origAddTrack) {
    window.RTCPeerConnection.prototype.addTrack = function addTrack() {
      const sender = origAddTrack.apply(this, arguments);
      sender._pc = this;
      return sender;
    };
  }
  window.RTCRtpSender.prototype.getStats = function getStats() {
    return this.track ? this._pc.getStats(this.track) :
        Promise.resolve(new Map());
  };
}

export function shimReceiverGetStats(window) {
  if (!(typeof window === 'object' && window.RTCPeerConnection &&
      window.RTCRtpSender)) {
    return;
  }
  if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) {
    return;
  }
  const origGetReceivers = window.RTCPeerConnection.prototype.getReceivers;
  if (origGetReceivers) {
    window.RTCPeerConnection.prototype.getReceivers = function getReceivers() {
      const receivers = origGetReceivers.apply(this, []);
      receivers.forEach(receiver => receiver._pc = this);
      return receivers;
    };
  }
  utils.wrapPeerConnectionEvent(window, 'track', e => {
    e.receiver._pc = e.srcElement;
    return e;
  });
  window.RTCRtpReceiver.prototype.getStats = function getStats() {
    return this._pc.getStats(this.track);
  };
}

export function shimRemoveStream(window) {
  if (!window.RTCPeerConnection ||
      'removeStream' in window.RTCPeerConnection.prototype) {
    return;
  }
  window.RTCPeerConnection.prototype.removeStream =
    function removeStream(stream) {
      utils.deprecated('removeStream', 'removeTrack');
      this.getSenders().forEach(sender => {
        if (sender.track && stream.getTracks().includes(sender.track)) {
          this.removeTrack(sender);
        }
      });
    };
}

export function shimRTCDataChannel(window) {
  // rename DataChannel to RTCDataChannel (native fix in FF60):
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1173851
  if (window.DataChannel && !window.RTCDataChannel) {
    window.RTCDataChannel = window.DataChannel;
  }
}

export function shimAddTransceiver(window) {
  // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
  // Firefox ignores the init sendEncodings options passed to addTransceiver
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
  if (!(typeof window === 'object' && window.RTCPeerConnection)) {
    return;
  }
  const origAddTransceiver = window.RTCPeerConnection.prototype.addTransceiver;
  if (origAddTransceiver) {
    window.RTCPeerConnection.prototype.addTransceiver =
      function addTransceiver() {
        this.setParametersPromises = [];
        const initParameters = arguments[1];
        const shouldPerformCheck = initParameters &&
                                  'sendEncodings' in initParameters;
        if (shouldPerformCheck) {
          // If sendEncodings params are provided, validate grammar
          initParameters.sendEncodings.forEach((encodingParam) => {
            if ('rid' in encodingParam) {
              const ridRegex = /^[a-z0-9]{0,16}$/i;
              if (!ridRegex.test(encodingParam.rid)) {
                throw new TypeError('Invalid RID value provided.');
              }
            }
            if ('scaleResolutionDownBy' in encodingParam) {
              if (!(parseFloat(encodingParam.scaleResolutionDownBy) >= 1.0)) {
                throw new RangeError('scale_resolution_down_by must be >= 1.0');
              }
            }
            if ('maxFramerate' in encodingParam) {
              if (!(parseFloat(encodingParam.maxFramerate) >= 0)) {
                throw new RangeError('max_framerate must be >= 0.0');
              }
            }
          });
        }
        const transceiver = origAddTransceiver.apply(this, arguments);
        if (shouldPerformCheck) {
          // Check if the init options were applied. If not we do this in an
          // asynchronous way and save the promise reference in a global object.
          // This is an ugly hack, but at the same time is way more robust than
          // checking the sender parameters before and after the createOffer
          // Also note that after the createoffer we are not 100% sure that
          // the params were asynchronously applied so we might miss the
          // opportunity to recreate offer.
          const {sender} = transceiver;
          const params = sender.getParameters();
          if (!('encodings' in params)) {
            params.encodings = initParameters.sendEncodings;
            sender.sendEncodings = initParameters.sendEncodings;
            this.setParametersPromises.push(sender.setParameters(params)
              .then(() => {
                delete sender.sendEncodings;
              }).catch(() => {
                delete sender.sendEncodings;
              })
            );
          }
        }
        return transceiver;
      };
  }
}

export function shimGetParameters(window) {
  if (!(typeof window === 'object' && window.RTCRtpSender)) {
    return;
  }
  const origGetParameters = window.RTCRtpSender.prototype.getParameters;
  if (origGetParameters) {
    window.RTCRtpSender.prototype.getParameters =
      function getParameters() {
        var params = origGetParameters.apply(this, arguments);
        if (!('encodings' in params)) {
          params.encodings = [].concat(this.sendEncodings || [{}]);
        }
        return params;
      };
  }
}

export function shimCreateOffer(window) {
  // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
  // Firefox ignores the init sendEncodings options passed to addTransceiver
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
  if (!(typeof window === 'object' && window.RTCPeerConnection)) {
    return;
  }
  const origCreateOffer = window.RTCPeerConnection.prototype.createOffer;
  window.RTCPeerConnection.prototype.createOffer = function createOffer() {
    if (this.setParametersPromises && this.setParametersPromises.length) {
      return Promise.all(this.setParametersPromises)
      .then(() => {
        return origCreateOffer.apply(this, arguments);
      })
      .finally(() => {
        this.setParametersPromises = [];
      });
    }
    return origCreateOffer.apply(this, arguments);
  };
}

export function shimCreateAnswer(window) {
  // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
  // Firefox ignores the init sendEncodings options passed to addTransceiver
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
  if (!(typeof window === 'object' && window.RTCPeerConnection)) {
    return;
  }
  const origCreateAnswer = window.RTCPeerConnection.prototype.createAnswer;
  window.RTCPeerConnection.prototype.createAnswer = function createAnswer() {
    if (this.setParametersPromises && this.setParametersPromises.length) {
      return Promise.all(this.setParametersPromises)
      .then(() => {
        return origCreateAnswer.apply(this, arguments);
      })
      .finally(() => {
        this.setParametersPromises = [];
      });
    }
    return origCreateAnswer.apply(this, arguments);
  };
}
<|endoftext|>


<commit_before>/*======================================================
************   Searchbar   ************
======================================================*/
var Searchbar = function (container, params) {
    var defaults = {
        input: null,
        clearButton: null,
        cancelButton: null,
        searchList: null,
        searchIn: '.item-title',
        searchBy: '',
        found: null,
        notFound: null,
        overlay: null,
        ignore: '.searchbar-ignore',
        customSearch: false,
        removeDiacritics: false,
        hideDividers: true,
        hideGroups: true,
        /* Callbacks
        onSearch
        onEnable
        onDisable
        onClear
        */

    };
    params = params || {};
    for (var def in defaults) {
        if (typeof params[def] === 'undefined' || params[def] === null && defaults.hasOwnProperty(def)) {
            params[def] = defaults[def];
        }
    }

    // Instance
    var s = this;

    // Material
    s.material = app.params.material;

    // Params
    s.params = params;

    // Container
    container = $(container);
    s.container = container;

    // Active
    s.active = false;

    // Input
    s.input = s.params.input ? $(s.params.input) : s.container.find('input[type="search"]');
    s.clearButton = s.params.clearButton ? $(s.params.clearButton) : s.container.find('.searchbar-clear');
    s.cancelButton = s.params.cancelButton ? $(s.params.cancelButton) : s.container.find('.searchbar-cancel');

    // Search List
    s.searchList = $(s.params.searchList);

    // Is Virtual List
    s.isVirtualList = s.searchList.hasClass('virtual-list');

    // Is In Page
    s.pageContainer = s.container.parents('.page').eq(0);

    // Overlay
    if (!s.params.overlay) {
        s.overlay = s.pageContainer.length > 0 ? s.pageContainer.find('.searchbar-overlay') : $('.searchbar-overlay');
    }
    else {
        s.overlay = $(s.params.overlay);
    }
    // Found and not found
    if (!s.params.found) {
        s.found = s.pageContainer.length > 0 ? s.pageContainer.find('.searchbar-found') : $('.searchbar-found');
    }
    else {
        s.found = $(s.params.found);
    }
    if (!s.params.notFound) {
        s.notFound = s.pageContainer.length > 0 ? s.pageContainer.find('.searchbar-not-found') : $('.searchbar-not-found');
    }
    else {
        s.notFound = $(s.params.notFound);
    }

    // Set Cancel button
    var cancelMarginProp = app.rtl ? 'margin-left' : 'margin-right';
    var cancelButtonHasMargin = false;
    s.setCancelButtonMargin = function () {
        s.cancelButton.transition(0).show();
        s.cancelButton.css(cancelMarginProp, -s.cancelButton[0].offsetWidth + 'px');
        var clientLeft = s.cancelButton[0].clientLeft;
        s.cancelButton.transition('');
        cancelButtonHasMargin = true;
    };

    // Trigger
    s.triggerEvent = function (eventName, callbackName, eventData) {
        s.container.trigger(eventName, eventData);
        if (s.searchList.length > 0) s.searchList.trigger(eventName, eventData);
        if (callbackName && s.params[callbackName]) s.params[callbackName](s, eventData);
    };

    // Enable/disalbe
    s.enable = function (e) {
        function _enable() {
            if ((s.searchList.length || s.params.customSearch) && !s.container.hasClass('searchbar-active') && !s.query) s.overlay.addClass('searchbar-overlay-active');
            s.container.addClass('searchbar-active');
            if (s.cancelButton.length > 0 && !s.material) {
                if (!cancelButtonHasMargin) {
                    s.setCancelButtonMargin();
                }
                s.cancelButton.css(cancelMarginProp, '0px');
            }
            s.triggerEvent('enableSearch searchbar:enable', 'onEnable');
            s.active = true;
        }
        if (app.device.ios && !app.params.material && e && e.type === 'focus') {
            setTimeout(function () {
                _enable();
            }, 400);
        }
        else {
            _enable();
        }
    };

    s.disable = function () {
        s.input.val('').trigger('change');
        s.container.removeClass('searchbar-active searchbar-not-empty');
        if (s.cancelButton.length > 0 && !s.material) s.cancelButton.css(cancelMarginProp, -s.cancelButton[0].offsetWidth + 'px');

        if (s.searchList.length || s.params.customSearch) s.overlay.removeClass('searchbar-overlay-active');

        s.active = false;
        function _disable() {
            s.input.blur();
        }
        if (app.device.ios) {
            setTimeout(function () {
                _disable();
            }, 400);
        }
        else {
            _disable();
        }
        s.triggerEvent('disableSearch searchbar:disable', 'onDisable');
    };

    // Clear
    s.clear = function (e) {
        if (!s.query && e && $(e.target).hasClass('searchbar-clear')) {
            s.disable();
            return;
        }
        var previousQuery = s.value;
        s.input.val('').trigger('change').focus();
        s.triggerEvent('clearSearch searchbar:clear', 'onClear', {previousQuery: previousQuery});
    };

    // Search
    s.handleInput = function () {
        setTimeout(function () {
            var value = s.input.val().trim();
            if ((s.searchList.length > 0 || s.params.customSearch) && (s.params.searchIn || s.isVirtualList)) s.search(value, true);
        }, 0);
    };

    var previousQuery = '';
    var virtualList;
    s.search = function (query, internal) {
        if (query.trim() === previousQuery) return;
        previousQuery = query.trim();

        if (!internal) {
            if (!s.active) {
                s.enable();
            }
            s.input.val(query);
        }
        s.query = s.value = query;
        // Add active/inactive classes on overlay
        if (query.length === 0) {
            s.container.removeClass('searchbar-not-empty');
            if (s.searchList.length && s.container.hasClass('searchbar-active')) s.overlay.addClass('searchbar-overlay-active');
        }
        else {
            s.container.addClass('searchbar-not-empty');
            if (s.searchList.length && s.container.hasClass('searchbar-active')) s.overlay.removeClass('searchbar-overlay-active');
        }

        if (s.params.customSearch) {
            s.triggerEvent('search searchbar:search', 'onSearch', {query: query});
            return;
        }

        var foundItems = [], _vlQuery;
        if (s.isVirtualList) {
            virtualList = s.searchList[0].f7VirtualList;
            if (query.trim() === '') {
                virtualList.resetFilter();
                s.notFound.hide();
                s.found.show();
                return;
            }
            _vlQuery = s.params.removeDiacritics ? $.removeDiacritics(query) : query;
            if (virtualList.params.searchAll) {
                foundItems = virtualList.params.searchAll(_vlQuery, virtualList.items) || [];
            }
            else if (virtualList.params.searchByItem) {
                for (var i = 0; i < virtualList.items.length; i++) {
                    if(virtualList.params.searchByItem(_vlQuery, i, virtualList.params.items[i])) {
                        foundItems.push(i);
                    }
                }
            }
        }
        else {
            var values;
            if (s.params.removeDiacritics) values = $.removeDiacritics(query.trim().toLowerCase()).split(' ');
            else {
                values = query.trim().toLowerCase().split(' ');
            }
            s.searchList.find('li').removeClass('hidden-by-searchbar').each(function (index, el) {
                el = $(el);
                var compareWithText = [];
                el.find(s.params.searchIn).each(function () {
                    var itemText = $(this).text().trim().toLowerCase();
                    if (s.params.removeDiacritics) itemText = $.removeDiacritics(itemText);
                    compareWithText.push(itemText);
                });
                compareWithText = compareWithText.join(' ');
                var wordsMatch = 0;
                for (var i = 0; i < values.length; i++) {
                    if (compareWithText.indexOf(values[i]) >= 0) wordsMatch++;
                }
                if (wordsMatch !== values.length && !(s.params.ignore && el.is(s.params.ignore))) {
                    el.addClass('hidden-by-searchbar');
                }
                else {
                    foundItems.push(el[0]);
                }
            });

            if (s.params.hideDividers) {
                s.searchList.find('.item-divider, .list-group-title').each(function () {
                    var title = $(this);
                    var nextElements = title.nextAll('li');
                    var hide = true;
                    for (var i = 0; i < nextElements.length; i++) {
                        var nextEl = $(nextElements[i]);
                        if (nextEl.hasClass('list-group-title') || nextEl.hasClass('item-divider')) break;
                        if (!nextEl.hasClass('hidden-by-searchbar')) {
                            hide = false;
                        }
                    }
                    var ignore = s.params.ignore && title.is(s.params.ignore);
                    if (hide && !ignore) title.addClass('hidden-by-searchbar');
                    else title.removeClass('hidden-by-searchbar');
                });
            }
            if (s.params.hideGroups) {
                s.searchList.find('.list-group').each(function () {
                    var group = $(this);
                    var ignore = s.params.ignore && group.is(s.params.ignore);
                    var notHidden = group.find('li:not(.hidden-by-searchbar)');
                    if (notHidden.length === 0 && !ignore) {
                        group.addClass('hidden-by-searchbar');
                    }
                    else {
                        group.removeClass('hidden-by-searchbar');
                    }
                });
            }
        }
        s.triggerEvent('search searchbar:search', 'onSearch', {query: query, foundItems: foundItems});
        if (foundItems.length === 0) {
            s.notFound.show();
            s.found.hide();
        }
        else {
            s.notFound.hide();
            s.found.show();
        }
        if (s.isVirtualList) {
            virtualList.filterItems(foundItems);
        }
    };

    // Events
    function preventSubmit(e) {
        e.preventDefault();
    }

    s.attachEvents = function (destroy) {
        var method = destroy ? 'off' : 'on';
        s.container[method]('submit', preventSubmit);
        if (!s.material) s.cancelButton[method]('click', s.disable);
        s.overlay[method]('click', s.disable);
        s.input[method]('focus', s.enable);
        s.input[method]('change keydown keypress keyup compositionend', s.handleInput);
        s.clearButton[method]('click', s.clear);

    };
    s.detachEvents = function() {
        s.attachEvents(true);
    };

    // Init Destroy
    s.init = function () {
        s.attachEvents();
    };
    s.destroy = function () {
        if (!s) return;
        s.detachEvents();
        s = null;
    };

    // Init
    s.init();

    s.container[0].f7Searchbar = s;
    return s;

};
app.searchbar = function (container, params) {
    return new Searchbar(container, params);
};
app.initSearchbar = function (container) {
    container = $(container);
    var searchbar = container.hasClass('searchbar') ? container : container.find('.searchbar');
    if (searchbar.length === 0) return;
    if (!searchbar.hasClass('searchbar-init')) return;

    var sb = app.searchbar(searchbar, searchbar.dataset());

    function onBeforeRemove() {
        if (sb) sb.destroy();
    }
    if (container.hasClass('page')) {
        container.once('pageBeforeRemove', onBeforeRemove);
    }
    else if (container.hasClass('navbar-inner')) {
        container.once('navbarBeforeRemove', onBeforeRemove);
    }
};
<commit_msg>FIX: if previousQuery doesn't exist,but searchterm<commit_after>/*======================================================
************   Searchbar   ************
======================================================*/
var Searchbar = function (container, params) {
    var defaults = {
        input: null,
        clearButton: null,
        cancelButton: null,
        searchList: null,
        searchIn: '.item-title',
        searchBy: '',
        found: null,
        notFound: null,
        overlay: null,
        ignore: '.searchbar-ignore',
        customSearch: false,
        removeDiacritics: false,
        hideDividers: true,
        hideGroups: true,
        /* Callbacks
        onSearch
        onEnable
        onDisable
        onClear
        */

    };
    params = params || {};
    for (var def in defaults) {
        if (typeof params[def] === 'undefined' || params[def] === null && defaults.hasOwnProperty(def)) {
            params[def] = defaults[def];
        }
    }

    // Instance
    var s = this;

    // Material
    s.material = app.params.material;

    // Params
    s.params = params;

    // Container
    container = $(container);
    s.container = container;

    // Active
    s.active = false;

    // Input
    s.input = s.params.input ? $(s.params.input) : s.container.find('input[type="search"]');
    s.clearButton = s.params.clearButton ? $(s.params.clearButton) : s.container.find('.searchbar-clear');
    s.cancelButton = s.params.cancelButton ? $(s.params.cancelButton) : s.container.find('.searchbar-cancel');

    // Search List
    s.searchList = $(s.params.searchList);

    // Is Virtual List
    s.isVirtualList = s.searchList.hasClass('virtual-list');

    // Is In Page
    s.pageContainer = s.container.parents('.page').eq(0);

    // Overlay
    if (!s.params.overlay) {
        s.overlay = s.pageContainer.length > 0 ? s.pageContainer.find('.searchbar-overlay') : $('.searchbar-overlay');
    }
    else {
        s.overlay = $(s.params.overlay);
    }
    // Found and not found
    if (!s.params.found) {
        s.found = s.pageContainer.length > 0 ? s.pageContainer.find('.searchbar-found') : $('.searchbar-found');
    }
    else {
        s.found = $(s.params.found);
    }
    if (!s.params.notFound) {
        s.notFound = s.pageContainer.length > 0 ? s.pageContainer.find('.searchbar-not-found') : $('.searchbar-not-found');
    }
    else {
        s.notFound = $(s.params.notFound);
    }

    // Set Cancel button
    var cancelMarginProp = app.rtl ? 'margin-left' : 'margin-right';
    var cancelButtonHasMargin = false;
    s.setCancelButtonMargin = function () {
        s.cancelButton.transition(0).show();
        s.cancelButton.css(cancelMarginProp, -s.cancelButton[0].offsetWidth + 'px');
        var clientLeft = s.cancelButton[0].clientLeft;
        s.cancelButton.transition('');
        cancelButtonHasMargin = true;
    };

    // Trigger
    s.triggerEvent = function (eventName, callbackName, eventData) {
        s.container.trigger(eventName, eventData);
        if (s.searchList.length > 0) s.searchList.trigger(eventName, eventData);
        if (callbackName && s.params[callbackName]) s.params[callbackName](s, eventData);
    };

    // Enable/disalbe
    s.enable = function (e) {
        function _enable() {
            if ((s.searchList.length || s.params.customSearch) && !s.container.hasClass('searchbar-active') && !s.query) s.overlay.addClass('searchbar-overlay-active');
            s.container.addClass('searchbar-active');
            if (s.cancelButton.length > 0 && !s.material) {
                if (!cancelButtonHasMargin) {
                    s.setCancelButtonMargin();
                }
                s.cancelButton.css(cancelMarginProp, '0px');
            }
            s.triggerEvent('enableSearch searchbar:enable', 'onEnable');
            s.active = true;
        }
        if (app.device.ios && !app.params.material && e && e.type === 'focus') {
            setTimeout(function () {
                _enable();
            }, 400);
        }
        else {
            _enable();
        }
    };

    s.disable = function () {
        s.input.val('').trigger('change');
        s.container.removeClass('searchbar-active searchbar-not-empty');
        if (s.cancelButton.length > 0 && !s.material) s.cancelButton.css(cancelMarginProp, -s.cancelButton[0].offsetWidth + 'px');

        if (s.searchList.length || s.params.customSearch) s.overlay.removeClass('searchbar-overlay-active');

        s.active = false;
        function _disable() {
            s.input.blur();
        }
        if (app.device.ios) {
            setTimeout(function () {
                _disable();
            }, 400);
        }
        else {
            _disable();
        }
        s.triggerEvent('disableSearch searchbar:disable', 'onDisable');
    };

    // Clear
    s.clear = function (e) {
        if (!s.query && e && $(e.target).hasClass('searchbar-clear')) {
            s.disable();
            return;
        }
        var previousQuery = s.value;
        s.input.val('').trigger('change').focus();
        s.triggerEvent('clearSearch searchbar:clear', 'onClear', {previousQuery: previousQuery});
    };

    // Search
    s.handleInput = function () {
        setTimeout(function () {
            var value = s.input.val().trim();
            if ((s.searchList.length > 0 || s.params.customSearch) && (s.params.searchIn || s.isVirtualList)) s.search(value, true);
        }, 0);
    };

    var previousQuery = '';
    var virtualList;
    s.search = function (query, internal) {
        if (previousQuery && query.trim() === previousQuery) return;
        previousQuery = query.trim();

        if (!internal) {
            if (!s.active) {
                s.enable();
            }
            s.input.val(query);
        }
        s.query = s.value = query;
        // Add active/inactive classes on overlay
        if (query.length === 0) {
            s.container.removeClass('searchbar-not-empty');
            if (s.searchList.length && s.container.hasClass('searchbar-active')) s.overlay.addClass('searchbar-overlay-active');
        }
        else {
            s.container.addClass('searchbar-not-empty');
            if (s.searchList.length && s.container.hasClass('searchbar-active')) s.overlay.removeClass('searchbar-overlay-active');
        }

        if (s.params.customSearch) {
            s.triggerEvent('search searchbar:search', 'onSearch', {query: query});
            return;
        }

        var foundItems = [], _vlQuery;
        if (s.isVirtualList) {
            virtualList = s.searchList[0].f7VirtualList;
            if (query.trim() === '') {
                virtualList.resetFilter();
                s.notFound.hide();
                s.found.show();
                return;
            }
            _vlQuery = s.params.removeDiacritics ? $.removeDiacritics(query) : query;
            if (virtualList.params.searchAll) {
                foundItems = virtualList.params.searchAll(_vlQuery, virtualList.items) || [];
            }
            else if (virtualList.params.searchByItem) {
                for (var i = 0; i < virtualList.items.length; i++) {
                    if(virtualList.params.searchByItem(_vlQuery, i, virtualList.params.items[i])) {
                        foundItems.push(i);
                    }
                }
            }
        }
        else {
            var values;
            if (s.params.removeDiacritics) values = $.removeDiacritics(query.trim().toLowerCase()).split(' ');
            else {
                values = query.trim().toLowerCase().split(' ');
            }
            s.searchList.find('li').removeClass('hidden-by-searchbar').each(function (index, el) {
                el = $(el);
                var compareWithText = [];
                el.find(s.params.searchIn).each(function () {
                    var itemText = $(this).text().trim().toLowerCase();
                    if (s.params.removeDiacritics) itemText = $.removeDiacritics(itemText);
                    compareWithText.push(itemText);
                });
                compareWithText = compareWithText.join(' ');
                var wordsMatch = 0;
                for (var i = 0; i < values.length; i++) {
                    if (compareWithText.indexOf(values[i]) >= 0) wordsMatch++;
                }
                if (wordsMatch !== values.length && !(s.params.ignore && el.is(s.params.ignore))) {
                    el.addClass('hidden-by-searchbar');
                }
                else {
                    foundItems.push(el[0]);
                }
            });

            if (s.params.hideDividers) {
                s.searchList.find('.item-divider, .list-group-title').each(function () {
                    var title = $(this);
                    var nextElements = title.nextAll('li');
                    var hide = true;
                    for (var i = 0; i < nextElements.length; i++) {
                        var nextEl = $(nextElements[i]);
                        if (nextEl.hasClass('list-group-title') || nextEl.hasClass('item-divider')) break;
                        if (!nextEl.hasClass('hidden-by-searchbar')) {
                            hide = false;
                        }
                    }
                    var ignore = s.params.ignore && title.is(s.params.ignore);
                    if (hide && !ignore) title.addClass('hidden-by-searchbar');
                    else title.removeClass('hidden-by-searchbar');
                });
            }
            if (s.params.hideGroups) {
                s.searchList.find('.list-group').each(function () {
                    var group = $(this);
                    var ignore = s.params.ignore && group.is(s.params.ignore);
                    var notHidden = group.find('li:not(.hidden-by-searchbar)');
                    if (notHidden.length === 0 && !ignore) {
                        group.addClass('hidden-by-searchbar');
                    }
                    else {
                        group.removeClass('hidden-by-searchbar');
                    }
                });
            }
        }
        s.triggerEvent('search searchbar:search', 'onSearch', {query: query, foundItems: foundItems});
        if (foundItems.length === 0) {
            s.notFound.show();
            s.found.hide();
        }
        else {
            s.notFound.hide();
            s.found.show();
        }
        if (s.isVirtualList) {
            virtualList.filterItems(foundItems);
        }
    };

    // Events
    function preventSubmit(e) {
        e.preventDefault();
    }

    s.attachEvents = function (destroy) {
        var method = destroy ? 'off' : 'on';
        s.container[method]('submit', preventSubmit);
        if (!s.material) s.cancelButton[method]('click', s.disable);
        s.overlay[method]('click', s.disable);
        s.input[method]('focus', s.enable);
        s.input[method]('change keydown keypress keyup compositionend', s.handleInput);
        s.clearButton[method]('click', s.clear);

    };
    s.detachEvents = function() {
        s.attachEvents(true);
    };

    // Init Destroy
    s.init = function () {
        s.attachEvents();
    };
    s.destroy = function () {
        if (!s) return;
        s.detachEvents();
        s = null;
    };

    // Init
    s.init();

    s.container[0].f7Searchbar = s;
    return s;

};
app.searchbar = function (container, params) {
    return new Searchbar(container, params);
};
app.initSearchbar = function (container) {
    container = $(container);
    var searchbar = container.hasClass('searchbar') ? container : container.find('.searchbar');
    if (searchbar.length === 0) return;
    if (!searchbar.hasClass('searchbar-init')) return;

    var sb = app.searchbar(searchbar, searchbar.dataset());

    function onBeforeRemove() {
        if (sb) sb.destroy();
    }
    if (container.hasClass('page')) {
        container.once('pageBeforeRemove', onBeforeRemove);
    }
    else if (container.hasClass('navbar-inner')) {
        container.once('navbarBeforeRemove', onBeforeRemove);
    }
};
<|endoftext|>


<commit_before>var ipc = window.require('ipc');
var _ = require('underscore');

var Reflux = require('reflux');
var Actions = require('../actions/actions');
var apiRequests = require('../utils/api-requests');
var SettingsStore = require('../stores/settings');

var NotificationsStore = Reflux.createStore({
  listenables: Actions,

  init: function () {
    this._notifications = [];
    this._previousNotifications = [];
  },

  updateTrayIcon: function (notifications) {
    if (notifications.length > 0) {
      ipc.sendChannel('update-icon', 'TrayActive');
    } else {
      ipc.sendChannel('update-icon', 'TrayIdle');
    }
  },

  isNewNotification: function (response) {
    var self = this;

    // Check if notification is already in the store.
    var isNew = false;
    _.map(response, function (obj) {
      if (!_.contains(self._previousNotifications, obj.id)) {
        isNew = true;
      }
    });

    // Play Sound.
    if (isNew) {
      var audio = new Audio('sounds/digi.wav');
      audio.play();
    }

    // Now Reset the previousNotifications array.
    self._previousNotifications = [];
    _.map(response, function (obj) {
      self._previousNotifications.push(obj.id);
    });
  },

  onGetNotifications: function () {
    var self = this;
    var participating = SettingsStore.getSettings().participating;
    var playSound = SettingsStore.getSettings().playSound;

    apiRequests
      .getAuth('https://api.github.com/notifications?participating=' +
        (participating ? 'true' : 'false'))
      .end(function (err, response) {
        if (response && response.ok) {
          // Success - Do Something.
          Actions.getNotifications.completed(response.body);
          self.updateTrayIcon(response.body);

          if (playSound) {
            self.isNewNotification(response.body);
          }
        } else {
          // Error - Show messages.
          Actions.getNotifications.failed(err);
        }
      });
  },

  onGetNotificationsCompleted: function (notifications) {
    var groupedNotifications = _.groupBy(notifications, function (object) {
      return object.repository.full_name;
    });

    var array = [];
    _.map(groupedNotifications, function (obj) {
      array.push(obj);
    });

    this._notifications = array;
    this.trigger(this._notifications);
  },

  onGetNotificationsFailed: function () {
    this._notifications = [];
    this.trigger(this._notifications);
  }

});

module.exports = NotificationsStore;
<commit_msg>Prepare native notifications<commit_after>var ipc = window.require('ipc');
var _ = require('underscore');

var Reflux = require('reflux');
var Actions = require('../actions/actions');
var apiRequests = require('../utils/api-requests');
var SettingsStore = require('../stores/settings');

var NotificationsStore = Reflux.createStore({
  listenables: Actions,

  init: function () {
    this._notifications = [];
    this._previousNotifications = [];
  },

  updateTrayIcon: function (notifications) {
    if (notifications.length > 0) {
      ipc.sendChannel('update-icon', 'TrayActive');
    } else {
      ipc.sendChannel('update-icon', 'TrayIdle');
    }
  },

  isNewNotification: function (response) {
    var self = this;
    var playSound = SettingsStore.getSettings().playSound;

    if (!playSound) { return; }

    // Check if notification is already in the store.
    var isNew = false;
    _.map(response, function (obj) {
      if (!_.contains(self._previousNotifications, obj.id)) {
        isNew = true;
      }
    });

    // Play Sound.
    if (isNew) {
      if (playSound) {
        var audio = new Audio('sounds/digi.wav');
        audio.play();
      }
    }

    // Now Reset the previousNotifications array.
    self._previousNotifications = [];
    _.map(response, function (obj) {
      self._previousNotifications.push(obj.id);
    });
  },

  onGetNotifications: function () {
    var self = this;
    var participating = SettingsStore.getSettings().participating;

    apiRequests
      .getAuth('https://api.github.com/notifications?participating=' +
        (participating ? 'true' : 'false'))
      .end(function (err, response) {
        if (response && response.ok) {
          // Success - Do Something.
          Actions.getNotifications.completed(response.body);
          self.updateTrayIcon(response.body);
          self.isNewNotification(response.body);
        } else {
          // Error - Show messages.
          Actions.getNotifications.failed(err);
        }
      });
  },

  onGetNotificationsCompleted: function (notifications) {
    var groupedNotifications = _.groupBy(notifications, function (object) {
      return object.repository.full_name;
    });

    var array = [];
    _.map(groupedNotifications, function (obj) {
      array.push(obj);
    });

    this._notifications = array;
    this.trigger(this._notifications);
  },

  onGetNotificationsFailed: function () {
    this._notifications = [];
    this.trigger(this._notifications);
  }

});

module.exports = NotificationsStore;
<|endoftext|>


<commit_before>// @flow
import createReducer from '../common/utils/create-reducer';
import TodoRecord from './models/todo-record';
import TodoManagerRecord from './models/todo-manager-record';
import { ACTION_TYPES } from './actions';
import type { SetVisibilityFilterAction, ToggleTodoAction, AddTodoAction } from './types';

const initialState: TodoManagerRecord = new TodoManagerRecord();

const addTodo = (state: TodoManagerRecord, action: AddTodoAction): TodoManagerRecord => (
  state.set('todos', state.get('todos').push(new TodoRecord({
    id: action.id,
    text: action.text,
    completed: false
  })))
);

const toggleTodo = (state: TodoManagerRecord, action: ToggleTodoAction): TodoManagerRecord => (
  state.set('todos', state.get('todos').map((todo) => {
    if (todo.get('id') !== action.id) {
      return todo;
    }

    return todo.set('completed', !todo.get('completed'));
  }))
);

const setVisibilityFilter = (
  state: TodoManagerRecord,
  action: SetVisibilityFilterAction
): TodoManagerRecord => (
  state.set('visibilityFilter', action.filter)
);

export default createReducer(initialState, {
  [ACTION_TYPES.ADD_TODO]: addTodo,
  [ACTION_TYPES.TOGGLE_TODO]: toggleTodo,
  [ACTION_TYPES.SET_VISIBILITY_FILTER]: setVisibilityFilter
});
<commit_msg>Refactored<commit_after>// @flow
import createReducer from '../common/utils/create-reducer';
import TodoRecord from './models/todo-record';
import TodoManagerRecord from './models/todo-manager-record';
import { ACTION_TYPES } from './actions';
import type { SetVisibilityFilterAction, ToggleTodoAction, AddTodoAction } from './types';

const addTodo = (state: TodoManagerRecord, action: AddTodoAction): TodoManagerRecord => (
  state.set('todos', state.get('todos').push(new TodoRecord({
    id: action.id,
    text: action.text,
    completed: false
  })))
);

const toggleTodo = (state: TodoManagerRecord, action: ToggleTodoAction): TodoManagerRecord => (
  state.set('todos', state.get('todos').map(todo => (
    todo.get('id') === action.id ?
      todo.set('completed', !todo.get('completed')) :
      todo
  )))
);

const setVisibilityFilter = (
  state: TodoManagerRecord,
  action: SetVisibilityFilterAction
): TodoManagerRecord => (
  state.set('visibilityFilter', action.filter)
);

export default createReducer(new TodoManagerRecord(), {
  [ACTION_TYPES.ADD_TODO]: addTodo,
  [ACTION_TYPES.TOGGLE_TODO]: toggleTodo,
  [ACTION_TYPES.SET_VISIBILITY_FILTER]: setVisibilityFilter
});
<|endoftext|>


<commit_before>import {
	CAMERA_ORTHOGRAPHIC, CAMERA_PERSPECTIVE, CAMERA_ANAGLYPH,
	LEG_CAVE, FEATURE_SELECTED_BOX, FEATURE_SURVEY, FEATURE_TERRAIN
} from '../core/constants';

import { AnaglyphEffect } from './AnaglyphEffect';

import {
	OrthographicCamera, PerspectiveCamera, MathUtils,
	MeshBasicMaterial, BackSide, Vector2, Euler, Quaternion,
} from '../Three';

const __rotation = new Euler();
const __q = new Quaternion();

function CameraManager ( ctx, renderer, scene ) {

	const container = ctx.container;

	const width = container.clientWidth;
	const height = container.clientHeight;

	const orthographicCamera = new OrthographicCamera( -width / 2, width / 2, height / 2, -height / 2, 1, 4000 );
	const perspectiveCamera = new PerspectiveCamera( ctx.cfg.themeValue( 'fieldOfView' ), width / height, 1, 16000 );

	const mouse = new Vector2();

	const self = this;

	scene.add( perspectiveCamera );
	scene.add( orthographicCamera );

	initCamera( perspectiveCamera );
	initCamera( orthographicCamera );

	this.activeCamera = perspectiveCamera;
	this.mode = CAMERA_PERSPECTIVE;

	const backMaterial = new MeshBasicMaterial( { side: BackSide, colorWrite: false } );
	const backMask = 1 << FEATURE_SURVEY | 1 << FEATURE_TERRAIN;

	let savedMask;
	let eyeSeparation = 0.5;
	let lastFrame = 0;

	ctx.viewer.addEventListener( 'resized', onResize );

	function onResize ( e ) {

		const width = e.width;
		const height = e.height;

		// adjust cameras to new aspect ratio etc.

		orthographicCamera.zoom *= width / ( orthographicCamera.right - orthographicCamera.left );

		orthographicCamera.left   = -width / 2;
		orthographicCamera.right  =  width / 2;
		orthographicCamera.top    =  height / 2;
		orthographicCamera.bottom = -height / 2;

		orthographicCamera.updateProjectionMatrix();

		perspectiveCamera.aspect = width / height;

		perspectiveCamera.updateProjectionMatrix();

		if ( self.activeEffect !== null ) {

			self.activeEffect.setSize( width, height );

		}

	}

	const basicRenderer = function () {

		renderer.render( scene, self.activeCamera );
		renderer.getContext().flush();

		lastFrame = renderer.info.render.frame;

	};

	const maskedRenderer = function () {

		// render depth buffer from underside of terrain
		const camera = self.activeCamera;

		if ( self.testCameraLayer( FEATURE_TERRAIN ) ) {

			camera.layers.mask = backMask;
			scene.overrideMaterial = backMaterial;

			renderer.render( scene, camera );

			scene.overrideMaterial = null;
			camera.layers.mask = savedMask;

		}

		renderer.render( scene, camera );
		renderer.getContext().flush();

		lastFrame = renderer.info.render.frame;

	};

	this.maskedTerrain = true;
	this.activeRenderer = maskedRenderer;

	this.activeEffect = null;

	function initCamera ( camera ) {

		camera.zoom = 1;

		camera.layers.set( 0 );

		camera.layers.enable( LEG_CAVE );
		camera.layers.enable( FEATURE_SELECTED_BOX );

	}

	this.resetCameras = function () {

		initCamera( perspectiveCamera );
		initCamera( orthographicCamera );

	};

	this.setCameraLayer = function ( layerTag, enable ) {

		if ( enable ) {

			perspectiveCamera.layers.enable( layerTag );
			orthographicCamera.layers.enable( layerTag );

		} else {

			perspectiveCamera.layers.disable( layerTag );
			orthographicCamera.layers.disable( layerTag );

		}

		savedMask = this.activeCamera.layers.mask;

		if ( this.activeEffect !== null ) {

			this.activeEffect.setLayers( savedMask );

		}

	};

	this.testCameraLayer = function ( layerTag ) {

		return ( ( savedMask & 1 << layerTag ) > 0 );

	};

	this.setCamera = function ( mode, target ) {

		if ( this.mode === mode ) return;

		let offsetLength;
		let activeCamera = this.activeCamera;

		const offset = activeCamera.position.clone().sub( target );

		if ( this.activeEffect !== null ) {

			this.activeEffect.dispose();

		}

		let activeEffect = null;

		switch ( mode ) {

		case CAMERA_ANAGLYPH:

			activeEffect = new AnaglyphEffect( renderer, width, height );

			if ( activeCamera.isPerspective ) break;

		case CAMERA_PERSPECTIVE: // eslint-disable-line no-fallthrough

			offsetLength = 4 * height * Math.tan( MathUtils.DEG2RAD * perspectiveCamera.fov / 2 ) / orthographicCamera.zoom / 2;

			offset.setLength( offsetLength );

			activeCamera = perspectiveCamera;

			break;

		case CAMERA_ORTHOGRAPHIC:

			offsetLength = offset.length();

			orthographicCamera.zoom = 2 * height * Math.tan( MathUtils.DEG2RAD * perspectiveCamera.fov / 2 ) / offsetLength;

			activeCamera = orthographicCamera;

			break;

		default:

			console.warn( 'unknown camera mode', mode );
			return;

		}

		if ( activeEffect !== null ) {

			activeEffect.setLayers( activeCamera.layers.mask );

			this.activeRenderer = function () {

				activeEffect.render( scene, self.activeCamera );

			};

		} else {

			if ( this.maskedTerrain ) {

				this.activeRenderer = maskedRenderer;

			} else {

				this.activeRenderer = basicRenderer;

			}

		}

		// update new camera with position to give same apparent zoom and view

		activeCamera.position.copy( offset.add( target ) );

		activeCamera.updateProjectionMatrix();
		activeCamera.lookAt( target );

		this.activeCamera = activeCamera;
		this.activeEffect = activeEffect;
		this.mode = mode;

	};

	this.getLastFrame = function () {

		return lastFrame;

	};

	this.getMouse = function ( x, y ) {

		const boundingRect = container.getBoundingClientRect();

		mouse.set(
			( ( x - boundingRect.left ) / container.clientWidth ) * 2 - 1,
			- ( ( y - boundingRect.top ) / container.clientHeight ) * 2 + 1
		);

		return mouse;

	};

	this.getRotation = function () {

		return __rotation.setFromQuaternion( this.activeCamera.getWorldQuaternion( __q ) );

	};

	Object.defineProperties( this, {

		'eyeSeparation': {
			get: function () { return eyeSeparation; },
			set: function ( x ) {

				// x varies from 0 to 1
				// base separation = 0.064
				eyeSeparation = x;

				if ( this.activeEffect !== null ) {

					this.activeEffect.setEyeSeparation( 0.064 + ( x - 0.5 ) * 0.06 );

				}

			}

		},

		'focalLength': {
			get: function () { return perspectiveCamera.getFocalLength(); },
			set: function ( x ) {
				perspectiveCamera.setFocalLength( x );
			}
		}

	} );

}

export { CameraManager };<commit_msg>code cleanup/remove exposed property<commit_after>import {
	CAMERA_ORTHOGRAPHIC, CAMERA_PERSPECTIVE, CAMERA_ANAGLYPH,
	LEG_CAVE, FEATURE_SELECTED_BOX, FEATURE_SURVEY, FEATURE_TERRAIN
} from '../core/constants';

import { AnaglyphEffect } from './AnaglyphEffect';

import {
	OrthographicCamera, PerspectiveCamera, MathUtils,
	MeshBasicMaterial, BackSide, Vector2, Euler, Quaternion,
} from '../Three';

const __rotation = new Euler();
const __q = new Quaternion();

function CameraManager ( ctx, renderer, scene ) {

	const container = ctx.container;

	const width = container.clientWidth;
	const height = container.clientHeight;

	const orthographicCamera = new OrthographicCamera( -width / 2, width / 2, height / 2, -height / 2, 1, 4000 );
	const perspectiveCamera = new PerspectiveCamera( ctx.cfg.themeValue( 'fieldOfView' ), width / height, 1, 16000 );

	const mouse = new Vector2();

	const self = this;

	scene.add( perspectiveCamera );
	scene.add( orthographicCamera );

	initCamera( perspectiveCamera );
	initCamera( orthographicCamera );

	this.activeCamera = perspectiveCamera;
	this.mode = CAMERA_PERSPECTIVE;

	const backMaterial = new MeshBasicMaterial( { side: BackSide, colorWrite: false } );
	const backMask = 1 << FEATURE_SURVEY | 1 << FEATURE_TERRAIN;

	let savedMask;
	let eyeSeparation = 0.5;
	let lastFrame = 0;
	let activeEffect = null;

	ctx.viewer.addEventListener( 'resized', onResize );

	function onResize ( e ) {

		const width = e.width;
		const height = e.height;

		// adjust cameras to new aspect ratio etc.

		orthographicCamera.zoom *= width / ( orthographicCamera.right - orthographicCamera.left );

		orthographicCamera.left   = -width / 2;
		orthographicCamera.right  =  width / 2;
		orthographicCamera.top    =  height / 2;
		orthographicCamera.bottom = -height / 2;

		orthographicCamera.updateProjectionMatrix();

		perspectiveCamera.aspect = width / height;

		perspectiveCamera.updateProjectionMatrix();

		if ( activeEffect !== null ) {

			activeEffect.setSize( width, height );

		}

	}

	const basicRenderer = function () {

		renderer.render( scene, self.activeCamera );
		renderer.getContext().flush();

		lastFrame = renderer.info.render.frame;

	};

	const maskedRenderer = function () {

		// render depth buffer from underside of terrain
		const camera = self.activeCamera;

		if ( self.testCameraLayer( FEATURE_TERRAIN ) ) {

			camera.layers.mask = backMask;
			scene.overrideMaterial = backMaterial;

			renderer.render( scene, camera );

			scene.overrideMaterial = null;
			camera.layers.mask = savedMask;

		}

		renderer.render( scene, camera );
		renderer.getContext().flush();

		lastFrame = renderer.info.render.frame;

	};

	this.maskedTerrain = true;
	this.activeRenderer = maskedRenderer;

	function initCamera ( camera ) {

		camera.zoom = 1;

		camera.layers.set( 0 );

		camera.layers.enable( LEG_CAVE );
		camera.layers.enable( FEATURE_SELECTED_BOX );

	}

	this.resetCameras = function () {

		initCamera( perspectiveCamera );
		initCamera( orthographicCamera );

	};

	this.setCameraLayer = function ( layerTag, enable ) {

		if ( enable ) {

			perspectiveCamera.layers.enable( layerTag );
			orthographicCamera.layers.enable( layerTag );

		} else {

			perspectiveCamera.layers.disable( layerTag );
			orthographicCamera.layers.disable( layerTag );

		}

		savedMask = this.activeCamera.layers.mask;

		if ( activeEffect !== null ) {

			activeEffect.setLayers( savedMask );

		}

	};

	this.testCameraLayer = function ( layerTag ) {

		return ( ( savedMask & 1 << layerTag ) > 0 );

	};

	this.setCamera = function ( mode, target ) {

		if ( this.mode === mode ) return;

		let offsetLength;
		let activeCamera = this.activeCamera;

		const offset = activeCamera.position.clone().sub( target );

		if ( activeEffect !== null ) {

			activeEffect.dispose();
			activeEffect = null;

		}

		switch ( mode ) {

		case CAMERA_ANAGLYPH:

			activeEffect = new AnaglyphEffect( renderer, width, height );

			if ( activeCamera.isPerspective ) break;

		case CAMERA_PERSPECTIVE: // eslint-disable-line no-fallthrough

			offsetLength = 4 * height * Math.tan( MathUtils.DEG2RAD * perspectiveCamera.fov / 2 ) / orthographicCamera.zoom / 2;

			offset.setLength( offsetLength );

			activeCamera = perspectiveCamera;

			break;

		case CAMERA_ORTHOGRAPHIC:

			offsetLength = offset.length();

			orthographicCamera.zoom = 2 * height * Math.tan( MathUtils.DEG2RAD * perspectiveCamera.fov / 2 ) / offsetLength;

			activeCamera = orthographicCamera;

			break;

		default:

			console.warn( 'unknown camera mode', mode );
			return;

		}

		if ( activeEffect !== null ) {

			activeEffect.setLayers( activeCamera.layers.mask );

			this.activeRenderer = function () {

				activeEffect.render( scene, activeCamera );

			};

		} else {

			if ( this.maskedTerrain ) {

				this.activeRenderer = maskedRenderer;

			} else {

				this.activeRenderer = basicRenderer;

			}

		}

		// update new camera with position to give same apparent zoom and view

		activeCamera.position.copy( offset.add( target ) );

		activeCamera.updateProjectionMatrix();
		activeCamera.lookAt( target );

		this.activeCamera = activeCamera;
		this.mode = mode;

	};

	this.getLastFrame = function () {

		return lastFrame;

	};

	this.getMouse = function ( x, y ) {

		const boundingRect = container.getBoundingClientRect();

		mouse.set(
			( ( x - boundingRect.left ) / container.clientWidth ) * 2 - 1,
			- ( ( y - boundingRect.top ) / container.clientHeight ) * 2 + 1
		);

		return mouse;

	};

	this.getRotation = function () {

		return __rotation.setFromQuaternion( this.activeCamera.getWorldQuaternion( __q ) );

	};

	Object.defineProperties( this, {

		'eyeSeparation': {
			get: function () { return eyeSeparation; },
			set: function ( x ) {

				// x varies from 0 to 1
				// base separation = 0.064
				eyeSeparation = x;

				if ( activeEffect !== null ) {

					activeEffect.setEyeSeparation( 0.064 + ( x - 0.5 ) * 0.06 );

				}

			}

		},

		'focalLength': {
			get: perspectiveCamera.getFocalLength,
			set: perspectiveCamera.setFocalLength
		}

	} );

}

export { CameraManager };<|endoftext|>


<commit_before>import fs from "fs-extra";
import path from "path";
import getWebpackAdditions from "./getWebpackAdditions";

function getBasePath () {
  return path.join(process.cwd(), "src", "config", ".entries");
}

/**
 * This method is a information preparer/gatherer. This information is used by
 * `buildWebpackEntries` and the server request handler. It sets up the default
 * entry point that will be used by most apps, as well as combining any
 * additional entry points specified in the webpack additions.
 */
export function getWebpackEntries () {
  const output = {};
  const cwd = process.cwd();
  const basePath = getBasePath();

  // setup default
  const entryPoints = {
    "/": {
      name: "main",
      routes: path.join(cwd, "src", "config", "routes"),
      reducers: path.join(cwd, "src", "reducers")
    },
    ...getWebpackAdditions().entryPoints
  };

  Object.keys(entryPoints).forEach((key) => {
    const entry = entryPoints[key];
    const fileName = entry.name.replace(/\W/, "-");
    output[key] = {
      ...entry,
      fileName: fileName,
      filePath: `${path.join(basePath, fileName)}-[chunkhash].js`,
      routes: entry.routes || path.join(cwd, "src", "config", "routes", fileName),
      reducers: entry.reducers || path.join(cwd, "src", "reducers", fileName),
      index: entry.index || path.join(cwd, "Index")
    };
  });

  return output;
}

/**
 * This method will wipe out the `config/.entries` hidden folder and rebuild it
 * it based on the defined entry points. It creates entry point files for each
 * of the entry points and returns the hash that webpack uses when bundling
 * code.
 *
 * Each entry point will be an array including the shared client entry point
 * file which includes global dependencies like the babel polyfill. It then
 * includes the generated entry point. Finally, if we are in development mode
 * it will start the array off with the webpack hot middleware client.
 */
export default function buildWebpackEntries (isProduction) {
  const output = {};
  const basePath = getBasePath();

  // Clean slate
  fs.removeSync(basePath);
  fs.ensureDirSync(basePath);

  const entries = getWebpackEntries();
  for (const key in entries) {
    const entry = entries[key];
    const { filePath, fileName } = entry;
    fs.outputFileSync(filePath, getEntryPointContent(entry));
    output[fileName] = [path.join(__dirname, "..", "entrypoints", "client.js"), filePath];

    // Include hot middleware in development mode only
    if (!isProduction) {
      output[fileName].unshift("webpack-hot-middleware/client");
    }
  }

  return output;
}

function getEntryPointContent (entry) {
  const cwd = process.cwd();
  const { routes, index, reducers } = entry;
  const reduxMiddlewarePath = path.join(cwd, "src", "config", "redux-middleware");
  const config = path.join(cwd, "src", "config", "application");
  const mainEntry = path.join(cwd, "src", "config", ".entry");
  const output = `import getRoutes from "${routes}";

// Make sure that webpack considers new dependencies introduced in the Index
// file
import "${index}";
import config from "${config}";
import Entry from "${mainEntry}";
import { createStore } from "gluestick-shared";
import middleware from "${reduxMiddlewarePath}";

let store;
export function getStore (httpClient) {
  store = store || createStore(httpClient, () => require("${reducers}"), middleware, (cb) => module.hot && module.hot.accept("${reducers}", cb), !!module.hot);
  return store;
}

if (typeof window === "object") {
  Entry.start(getRoutes, getStore);
}
`;

  return output;
}

<commit_msg>stop reusing store<commit_after>import fs from "fs-extra";
import path from "path";
import getWebpackAdditions from "./getWebpackAdditions";

function getBasePath () {
  return path.join(process.cwd(), "src", "config", ".entries");
}

/**
 * This method is a information preparer/gatherer. This information is used by
 * `buildWebpackEntries` and the server request handler. It sets up the default
 * entry point that will be used by most apps, as well as combining any
 * additional entry points specified in the webpack additions.
 */
export function getWebpackEntries () {
  const output = {};
  const cwd = process.cwd();
  const basePath = getBasePath();

  // setup default
  const entryPoints = {
    "/": {
      name: "main",
      routes: path.join(cwd, "src", "config", "routes"),
      reducers: path.join(cwd, "src", "reducers")
    },
    ...getWebpackAdditions().entryPoints
  };

  Object.keys(entryPoints).forEach((key) => {
    const entry = entryPoints[key];
    const fileName = entry.name.replace(/\W/, "-");
    output[key] = {
      ...entry,
      fileName: fileName,
      filePath: `${path.join(basePath, fileName)}-[chunkhash].js`,
      routes: entry.routes || path.join(cwd, "src", "config", "routes", fileName),
      reducers: entry.reducers || path.join(cwd, "src", "reducers", fileName),
      index: entry.index || path.join(cwd, "Index")
    };
  });

  return output;
}

/**
 * This method will wipe out the `config/.entries` hidden folder and rebuild it
 * it based on the defined entry points. It creates entry point files for each
 * of the entry points and returns the hash that webpack uses when bundling
 * code.
 *
 * Each entry point will be an array including the shared client entry point
 * file which includes global dependencies like the babel polyfill. It then
 * includes the generated entry point. Finally, if we are in development mode
 * it will start the array off with the webpack hot middleware client.
 */
export default function buildWebpackEntries (isProduction) {
  const output = {};
  const basePath = getBasePath();

  // Clean slate
  fs.removeSync(basePath);
  fs.ensureDirSync(basePath);

  const entries = getWebpackEntries();
  for (const key in entries) {
    const entry = entries[key];
    const { filePath, fileName } = entry;
    fs.outputFileSync(filePath, getEntryPointContent(entry));
    output[fileName] = [path.join(__dirname, "..", "entrypoints", "client.js"), filePath];

    // Include hot middleware in development mode only
    if (!isProduction) {
      output[fileName].unshift("webpack-hot-middleware/client");
    }
  }

  return output;
}

function getEntryPointContent (entry) {
  const cwd = process.cwd();
  const { routes, index, reducers } = entry;
  const reduxMiddlewarePath = path.join(cwd, "src", "config", "redux-middleware");
  const config = path.join(cwd, "src", "config", "application");
  const mainEntry = path.join(cwd, "src", "config", ".entry");
  const output = `import getRoutes from "${routes}";

// Make sure that webpack considers new dependencies introduced in the Index
// file
import "${index}";
import config from "${config}";
import Entry from "${mainEntry}";
import { createStore } from "gluestick-shared";
import middleware from "${reduxMiddlewarePath}";

export function getStore (httpClient) {
  return createStore(httpClient, () => require("${reducers}"), middleware, (cb) => module.hot && module.hot.accept("${reducers}", cb), !!module.hot);
}

if (typeof window === "object") {
  Entry.start(getRoutes, getStore);
}
`;

  return output;
}

<|endoftext|>


<commit_before>const kDefaultWait = 2000;
// Wait for a condition and call a supplied callback if condition is met within
// alloted time. If condition is not met, cause a hard failure,
// stopping the test.
function waitFor(callback, test, timeout) {
  if (test()) {
    callback();
    return;
  }

  timeout = timeout || Date.now();
  if (Date.now() - timeout > kDefaultWait)
    throw 'waitFor timeout';
  setTimeout(waitFor, 50, callback, test, timeout);
}

// XXX: Currently we're waiting for the lockscreen to be auto-locked
// then we're unlocking it and waiting for the custom event to declare
// the tests ready to run.
// There should be a better way...
if (typeof readyAndUnlocked === 'undefined') {
  readyAndUnlocked = false;

  waitFor(function() {
    var contentWindow = shell.home.contentWindow.wrappedJSObject;
    contentWindow.addEventListener('unlocked', function waitUnlocked() {
      contentWindow.removeEventListener('unlocked', waitUnlocked);
      readyAndUnlocked = true;
    });

    contentWindow.addEventListener('locked', function waitLocked() {
      contentWindow.removeEventListener('locked', waitLocked);
      contentWindow.Gaia.lockScreen.unlock(-1, true);
    });
  }, function() {
    let contentWindow = shell.home.contentWindow.wrappedJSObject;
    return ('Gaia' in contentWindow) && ('lockScreen' in contentWindow.Gaia);
  });
}

function appTest(url, callback) {
  waitForExplicitFinish();

  waitFor(function() {
    var contentWindow = shell.home.contentWindow.wrappedJSObject;
    var AppManager = contentWindow.Gaia.AppManager;
    AppManager.launch(url);

    contentWindow.addEventListener('appopen', function launchWait() {
      contentWindow.removeEventListener('appopen', launchWait);

      callback(AppManager.foregroundWindow);

      // cleaning up
      waitFor(function() {
        AppManager.close();
        contentWindow.addEventListener('appclose', function closeWait() {
          contentWindow.removeEventListener('appclose', closeWait);
          AppManager.kill(url);

          finish();
        });
      }, function() {
        return SimpleTest.__appTestFinished;
      }, (Date.now() + 5000));
    });
  }, function() {
    return readyAndUnlocked;
  });
}

<commit_msg>Opening an issue for this... issue<commit_after>const kDefaultWait = 2000;
// Wait for a condition and call a supplied callback if condition is met within
// alloted time. If condition is not met, cause a hard failure,
// stopping the test.
function waitFor(callback, test, timeout) {
  if (test()) {
    callback();
    return;
  }

  timeout = timeout || Date.now();
  if (Date.now() - timeout > kDefaultWait)
    throw 'waitFor timeout';
  setTimeout(waitFor, 50, callback, test, timeout);
}

// Currently we're waiting for the lockscreen to be auto-locked
// then we're unlocking it and waiting for the custom event to declare
// the tests ready to run.
// see https://github.com/andreasgal/gaia/issues/333
if (typeof readyAndUnlocked === 'undefined') {
  readyAndUnlocked = false;

  waitFor(function() {
    var contentWindow = shell.home.contentWindow.wrappedJSObject;
    contentWindow.addEventListener('unlocked', function waitUnlocked() {
      contentWindow.removeEventListener('unlocked', waitUnlocked);
      readyAndUnlocked = true;
    });

    contentWindow.addEventListener('locked', function waitLocked() {
      contentWindow.removeEventListener('locked', waitLocked);
      contentWindow.Gaia.lockScreen.unlock(-1, true);
    });
  }, function() {
    let contentWindow = shell.home.contentWindow.wrappedJSObject;
    return ('Gaia' in contentWindow) && ('lockScreen' in contentWindow.Gaia);
  });
}

function appTest(url, callback) {
  waitForExplicitFinish();

  waitFor(function() {
    var contentWindow = shell.home.contentWindow.wrappedJSObject;
    var AppManager = contentWindow.Gaia.AppManager;
    AppManager.launch(url);

    contentWindow.addEventListener('appopen', function launchWait() {
      contentWindow.removeEventListener('appopen', launchWait);

      callback(AppManager.foregroundWindow);

      // cleaning up
      waitFor(function() {
        AppManager.close();
        contentWindow.addEventListener('appclose', function closeWait() {
          contentWindow.removeEventListener('appclose', closeWait);
          AppManager.kill(url);

          finish();
        });
      }, function() {
        return SimpleTest.__appTestFinished;
      }, (Date.now() + 5000));
    });
  }, function() {
    return readyAndUnlocked;
  });
}

<|endoftext|>