<commit_before>/**
 * Copyright (C) 2011 Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

define('jquery.instructure_misc_plugins', ['i18n'], function(I18n){

  I18n = I18n.scoped('instructure');

  // this function is to prevent you from doing all kinds of expesive operations on a
  // jquery object that doesn't actually have any elements in it
  // it is similar and inspired by http://www.slideshare.net/paul.irish/perfcompression (slide #42)
  // to use it do something like:
  // $("a .bunch #of .nodes").ifExists(function(orignalQuery){
  //   //  'this' points to the original jquery object (in this case, $("a .bunch #of .nodes") );
  //   // orignalQuery is the same as 'this';
  //   this.slideUp().dialog().show();
  // });
  $.fn.ifExists = function(func){
    this.length && func.call(this, this);
    return this;
  };

  // Returns the width of the browser's scroll bars.
  $.fn.scrollbarWidth = function() {
      var $div = $('

').appendTo(this),
          $innerDiv = $div.find('div');
      // Append our div, do our calculation and then remove it
      var w1 = $innerDiv.innerWidth();
      $div.css('overflow-y', 'scroll');
      var w2 = $innerDiv.innerWidth();
      $div.remove();
      return (w1 - w2);
  };

  // Simple animation for dimming an element's opacity
  $.fn.dim = function(speed) {
    return this.animate({opacity: 0.4}, speed);
  };

  $.fn.undim = function(speed) {
    return this.animate({opacity: 1.0}, speed);
  };

  // Helper for deleting objects from the DOM and db.
  //  url: URL to pass DELETE message.  If none provided,
  //    behaves as if the request were a success.  Useful for testing.
  //  message: Confirmation message
  //  cancelled: Function to handle cancel.
  //  confirmed: Functiont to handle confirm, before submit.
  //  success: What to do on success.  If none provided, fades
  //    out the element and removes it from the DOM.
  //  error: Error.
  $.fn.confirmDelete = function(options) {
    var options = $.extend({}, $.fn.confirmDelete.defaults, options);
    var $object = this;
    var result = true;
    options.noMessage = options.noMessage || options.no_message;
    if(options.message && !options.noMessage) {
      if(!$.skipConfirmations) {
        result = confirm(options.message);
      }
    }
    if(!result) {
      if(options.cancelled && $.isFunction(options.cancelled)) {
        options.cancelled.call($object);
      }
      return;
    }
    if(!options.confirmed) {
      options.confirmed = function() {
        $object.dim();
      };
    }
    options.confirmed.call($object);
    if(options.url) {
      if(!options.success) {
        options.success = function(data) {
          $object.fadeOut('slow', function() {
            $object.remove();
          });
        };
      }
      var data = {};
      if(options.token) {
        data.authenticity_token = options.token;
      }
      if(!data.authenticity_token) {
        data.authenticity_token = $("#ajax_authenticity_token").text();
      }
      $.ajaxJSON(options.url, "DELETE", data, function(data) {
        options.success.call($object, data);
      }, function(data, request, status, error) {
        if(options.error && $.isFunction(options.error)) {
          options.error.call($object, data, request, status, error);
        } else {
          $.ajaxJSON.unhandledXHRs.push(request);
        }
      });
    } else {
      if(!options.success) {
        options.success = function() {
          $object.fadeOut('slow', function() {
            $object.remove();
          });
        };
      }
      options.success.call($object);
    }
  };
  $.fn.confirmDelete.defaults = {
    message: I18n.t('confirms.default_delete_thing', "Are you sure you want to delete this?")
  };

  // Watches the given element's location.href for any changes
  // to the fragment ("#...") and calls the provided function
  // when there are any.
  // $(document).fragmentChange(function(event, hash) { alert(hash); });
  $.fn.fragmentChange = function(fn) {
    if(fn && fn !== true) {
      var query = (window.location.search || "").replace(/^\?/, "").split("&");
      var idx;
      // The URL can hard-code a hash regardless of what's
      // actually shown in the hash by specifying a query
      // parameter, hash=some_hash
      var query_hash = null;
      for(idx in query) {
        var item = query[idx];
        if(item && item.indexOf("hash=") === 0) {
          query_hash = "#" + item.substring(5);
        }
      }
      this.bind('document_fragment_change', fn);
      var $doc = this;
      var found = false;
      // Can only be used on the root document,
      // will not work on an iframe, for example.
      for(idx in $._checkFragments.fragmentList) {
        var obj = $._checkFragments.fragmentList[idx];
        if(obj.doc[0] == $doc[0]) {
          found = true;
        }
      }
      if(!found) {
        $._checkFragments.fragmentList.push({
          doc: $doc,
          fragment: ""
        });
      }
      $(window).bind('hashchange', $._checkFragments);
      setTimeout(function() {
        if(query_hash && query_hash.length > 0) {
          $doc.triggerHandler('document_fragment_change', query_hash);
        } else if($doc && $doc[0] && $doc[0].location && $doc[0].location.hash.length > 0) {
          $doc.triggerHandler('document_fragment_change', $doc[0].location.hash);
        }
      }, 500);
    } else {
      this.triggerHandler('document_fragment_change', this[0].location.hash);
    }
    return this;
  };
  $._checkFragments = function() {
    var list = $._checkFragments.fragmentList;
    for(var idx in list) {
      var obj = list[idx];
      var $doc = obj.doc;
      if($doc[0].location.hash != obj.fragment) {
        $doc.triggerHandler('document_fragment_change', $doc[0].location.hash);
        obj.fragment = $doc[0].location.hash;
        $._checkFragments.fragmentList[idx] = obj;
      }
    }
  };
  $._checkFragments.fragmentList = [];
  // Triggers a click only if the anchor tag isn't disabled.
  $.fn.clickLink = function() {
    var $obj = this.eq(0);
    if(!$obj.hasClass('disabled_link')) {
      $obj.click();
    }
  };

  // jQuery supposedly has this built-in, but I haven't
  // had much success with it.
  $.fn.showIf = function(bool) {
    if ($.isFunction(bool)) {
      return this.each(function(index) {
        $(this).showIf(bool.call(this));
      });
    }
    if (bool) {
      this.show();
    } else {
      this.hide();
    }
    return this;
  };

  $.fn.disableIf = function(bool) {
    if ($.isFunction(bool)) { bool = bool.call(this); }
    this.prop('disabled', !!bool);
    return this;
  };

  $.fn.indicate = function(options) {
    options = options || {};
    var $indicator;
    if(options == "remove") {
      $indicator = this.data('indicator');
      if($indicator) {
        $indicator.remove();
      }
      return;
    }
    $(".indicator_box").remove();
    var offset = this.offset();
    if(options && options.offset) {
      offset = options.offset;
    }
    var width = this.width();
    var height = this.height();
    var zIndex = (options.container || this).zIndex();
    $indicator = $(document.createElement('div'));
    $indicator.css({
      width: width + 6,
      height: height + 6,
      top: offset.top - 3,
      left: offset.left - 3,
      zIndex: zIndex + 1,
      position: 'absolute',
      display: 'block',
      "-moz-border-radius": 5,
      opacity: 0.8,
      border: "2px solid #870",
      backgroundColor: "#fd0"
    });
    $indicator.addClass('indicator_box');
    $indicator.mouseover(function() {
      $(this).stop().fadeOut('fast', function() {
        $(this).remove();
      });
    });
    if(this.data('indicator')) {
      this.indicate('remove');
    }
    this.data('indicator', $indicator);
    $("body").append($indicator);
    if(options && options.singleFlash) {
      $indicator.hide().fadeIn().animate({opacity: 0.8}, 500).fadeOut('slow', function() {
        $(this).remove();
      });
    } else {
      $indicator.hide().fadeIn().animate({opacity: 0.8}, 500).fadeOut('slow').fadeIn('slow').animate({opacity: 0.8}, 2500).fadeOut('slow', function() {
        $(this).remove();
      });
    }
    if(options && options.scroll) {
      $("html,body").scrollToVisible($indicator);
    }
  };

  $.fn.hasScrollbar = function(){
    return this.length && (this[0].clientHeight < this[0].scrollHeight);
  };

  $.fn.log = function (msg) {
    console.log("%s: %o", msg, this);
    return this;
  };

  $.fn.chevronCrumbs = function(options) {
    return this.each(function() {
      $(this).show()
        .addClass("chevron-crumbs")
        .children().not("#hide-scratch")
          .addClass('chevron-crumb')
          .append('')
          .filter(".active").prev().addClass("before-active");
    });
  };

  // this is used if you want to fill the browser window with something inside #content but you want to also leave the footer and header on the page.
  $.fn.fillWindowWithMe = function(options){
    var opts               = $.extend({minHeight: 400}, options),
        $this              = $(this),
        $wrapper_container = $('#wrapper-container'),
        $main              = $('#main'),
        $not_right_side    = $('#not_right_side'),
        $window            = $(window),
        $toResize          = $(this).add(opts.alsoResize);

    function fillWindowWithThisElement(){
      $toResize.height(0);
      var spaceLeftForThis = $window.height()
                             - ($wrapper_container.offset().top + $wrapper_container.height())
                             + ($main.height() - $not_right_side.height()),
          newHeight = Math.max(400, spaceLeftForThis);

      $toResize.height(newHeight);
      if ($.isFunction(opts.onResize)) {
        opts.onResize.call($this, newHeight);
      }
    }
    fillWindowWithThisElement();
    $window
      .unbind('resize.fillWindowWithMe')
      .bind('resize.fillWindowWithMe', fillWindowWithThisElement);
    return this;
  };

  $.fn.autoGrowInput = function(o) {

    o = $.extend({
        maxWidth: 1000,
        minWidth: 0,
        comfortZone: 70
    }, o);

    this.filter('input:text').each(function(){

      var minWidth = o.minWidth || $(this).width(),
        val = '',
        input = $(this),
        testSubject = $('<tester/>').css({
          position: 'absolute',
          top: -9999,
          left: -9999,
          width: 'auto',
          fontSize: input.css('fontSize'),
          fontFamily: input.css('fontFamily'),
          fontWeight: input.css('fontWeight'),
          letterSpacing: input.css('letterSpacing'),
          whiteSpace: 'nowrap'
        }),
        check = function() {

          setTimeout(function() {
            if (val === (val = input.val())) {return;}

            // Enter new content into testSubject
            var escaped = val.replace(/&/g, '&').replace(/\s/g,' ').replace(/</g, '<').replace(/>/g, '>');
            testSubject.html(escaped);

            // Calculate new width + whether to change
            var testerWidth = testSubject.width(),
              newWidth = (testerWidth + o.comfortZone) >= minWidth ? testerWidth + o.comfortZone : minWidth,
              currentWidth = input.width(),
              isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
                                   || (newWidth > minWidth && newWidth < o.maxWidth);

            // Animate width
            if (isValidWidthChange) {
              input.width(newWidth);
            }
          });

        };

      testSubject.insertAfter(input);

      $(this).bind('keyup keydown blur update change', check);

    });

    return this;

  };

  $.fn.fixDialogButtons = function() {
    return this.each(function() {
      var $dialog = $(this);
      $dialog.find('.button-container:last .button, button[type=submit]').ifExists(function($buttons){
        $dialog.find('.button-container:last, button[type=submit]').hide();
        var buttons = $buttons.map(function() {
          var $button = $(this);
          return {
            text: $button.text(),
            'data-text-while-loading': $button.data('textWhileLoading'),
            click: function() { $button.click() }
          };
        }).get();
        if (buttons.length) $dialog.dialog('option', 'buttons', buttons);
      });
    });
  }

  return $;
});
<commit_msg>make misc_plugins work until we have AMD fully<commit_after>/**
 * Copyright (C) 2011 Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

I18n.scoped('instructure', function(I18n){

  // this function is to prevent you from doing all kinds of expesive operations on a
  // jquery object that doesn't actually have any elements in it
  // it is similar and inspired by http://www.slideshare.net/paul.irish/perfcompression (slide #42)
  // to use it do something like:
  // $("a .bunch #of .nodes").ifExists(function(orignalQuery){
  //   //  'this' points to the original jquery object (in this case, $("a .bunch #of .nodes") );
  //   // orignalQuery is the same as 'this';
  //   this.slideUp().dialog().show();
  // });
  $.fn.ifExists = function(func){
    this.length && func.call(this, this);
    return this;
  };

  // Returns the width of the browser's scroll bars.
  $.fn.scrollbarWidth = function() {
      var $div = $('

').appendTo(this),
          $innerDiv = $div.find('div');
      // Append our div, do our calculation and then remove it
      var w1 = $innerDiv.innerWidth();
      $div.css('overflow-y', 'scroll');
      var w2 = $innerDiv.innerWidth();
      $div.remove();
      return (w1 - w2);
  };

  // Simple animation for dimming an element's opacity
  $.fn.dim = function(speed) {
    return this.animate({opacity: 0.4}, speed);
  };

  $.fn.undim = function(speed) {
    return this.animate({opacity: 1.0}, speed);
  };

  // Helper for deleting objects from the DOM and db.
  //  url: URL to pass DELETE message.  If none provided,
  //    behaves as if the request were a success.  Useful for testing.
  //  message: Confirmation message
  //  cancelled: Function to handle cancel.
  //  confirmed: Functiont to handle confirm, before submit.
  //  success: What to do on success.  If none provided, fades
  //    out the element and removes it from the DOM.
  //  error: Error.
  $.fn.confirmDelete = function(options) {
    var options = $.extend({}, $.fn.confirmDelete.defaults, options);
    var $object = this;
    var result = true;
    options.noMessage = options.noMessage || options.no_message;
    if(options.message && !options.noMessage) {
      if(!$.skipConfirmations) {
        result = confirm(options.message);
      }
    }
    if(!result) {
      if(options.cancelled && $.isFunction(options.cancelled)) {
        options.cancelled.call($object);
      }
      return;
    }
    if(!options.confirmed) {
      options.confirmed = function() {
        $object.dim();
      };
    }
    options.confirmed.call($object);
    if(options.url) {
      if(!options.success) {
        options.success = function(data) {
          $object.fadeOut('slow', function() {
            $object.remove();
          });
        };
      }
      var data = {};
      if(options.token) {
        data.authenticity_token = options.token;
      }
      if(!data.authenticity_token) {
        data.authenticity_token = $("#ajax_authenticity_token").text();
      }
      $.ajaxJSON(options.url, "DELETE", data, function(data) {
        options.success.call($object, data);
      }, function(data, request, status, error) {
        if(options.error && $.isFunction(options.error)) {
          options.error.call($object, data, request, status, error);
        } else {
          $.ajaxJSON.unhandledXHRs.push(request);
        }
      });
    } else {
      if(!options.success) {
        options.success = function() {
          $object.fadeOut('slow', function() {
            $object.remove();
          });
        };
      }
      options.success.call($object);
    }
  };
  $.fn.confirmDelete.defaults = {
    message: I18n.t('confirms.default_delete_thing', "Are you sure you want to delete this?")
  };

  // Watches the given element's location.href for any changes
  // to the fragment ("#...") and calls the provided function
  // when there are any.
  // $(document).fragmentChange(function(event, hash) { alert(hash); });
  $.fn.fragmentChange = function(fn) {
    if(fn && fn !== true) {
      var query = (window.location.search || "").replace(/^\?/, "").split("&");
      var idx;
      // The URL can hard-code a hash regardless of what's
      // actually shown in the hash by specifying a query
      // parameter, hash=some_hash
      var query_hash = null;
      for(idx in query) {
        var item = query[idx];
        if(item && item.indexOf("hash=") === 0) {
          query_hash = "#" + item.substring(5);
        }
      }
      this.bind('document_fragment_change', fn);
      var $doc = this;
      var found = false;
      // Can only be used on the root document,
      // will not work on an iframe, for example.
      for(idx in $._checkFragments.fragmentList) {
        var obj = $._checkFragments.fragmentList[idx];
        if(obj.doc[0] == $doc[0]) {
          found = true;
        }
      }
      if(!found) {
        $._checkFragments.fragmentList.push({
          doc: $doc,
          fragment: ""
        });
      }
      $(window).bind('hashchange', $._checkFragments);
      setTimeout(function() {
        if(query_hash && query_hash.length > 0) {
          $doc.triggerHandler('document_fragment_change', query_hash);
        } else if($doc && $doc[0] && $doc[0].location && $doc[0].location.hash.length > 0) {
          $doc.triggerHandler('document_fragment_change', $doc[0].location.hash);
        }
      }, 500);
    } else {
      this.triggerHandler('document_fragment_change', this[0].location.hash);
    }
    return this;
  };
  $._checkFragments = function() {
    var list = $._checkFragments.fragmentList;
    for(var idx in list) {
      var obj = list[idx];
      var $doc = obj.doc;
      if($doc[0].location.hash != obj.fragment) {
        $doc.triggerHandler('document_fragment_change', $doc[0].location.hash);
        obj.fragment = $doc[0].location.hash;
        $._checkFragments.fragmentList[idx] = obj;
      }
    }
  };
  $._checkFragments.fragmentList = [];
  // Triggers a click only if the anchor tag isn't disabled.
  $.fn.clickLink = function() {
    var $obj = this.eq(0);
    if(!$obj.hasClass('disabled_link')) {
      $obj.click();
    }
  };

  // jQuery supposedly has this built-in, but I haven't
  // had much success with it.
  $.fn.showIf = function(bool) {
    if ($.isFunction(bool)) {
      return this.each(function(index) {
        $(this).showIf(bool.call(this));
      });
    }
    if (bool) {
      this.show();
    } else {
      this.hide();
    }
    return this;
  };

  $.fn.disableIf = function(bool) {
    if ($.isFunction(bool)) { bool = bool.call(this); }
    this.prop('disabled', !!bool);
    return this;
  };

  $.fn.indicate = function(options) {
    options = options || {};
    var $indicator;
    if(options == "remove") {
      $indicator = this.data('indicator');
      if($indicator) {
        $indicator.remove();
      }
      return;
    }
    $(".indicator_box").remove();
    var offset = this.offset();
    if(options && options.offset) {
      offset = options.offset;
    }
    var width = this.width();
    var height = this.height();
    var zIndex = (options.container || this).zIndex();
    $indicator = $(document.createElement('div'));
    $indicator.css({
      width: width + 6,
      height: height + 6,
      top: offset.top - 3,
      left: offset.left - 3,
      zIndex: zIndex + 1,
      position: 'absolute',
      display: 'block',
      "-moz-border-radius": 5,
      opacity: 0.8,
      border: "2px solid #870",
      backgroundColor: "#fd0"
    });
    $indicator.addClass('indicator_box');
    $indicator.mouseover(function() {
      $(this).stop().fadeOut('fast', function() {
        $(this).remove();
      });
    });
    if(this.data('indicator')) {
      this.indicate('remove');
    }
    this.data('indicator', $indicator);
    $("body").append($indicator);
    if(options && options.singleFlash) {
      $indicator.hide().fadeIn().animate({opacity: 0.8}, 500).fadeOut('slow', function() {
        $(this).remove();
      });
    } else {
      $indicator.hide().fadeIn().animate({opacity: 0.8}, 500).fadeOut('slow').fadeIn('slow').animate({opacity: 0.8}, 2500).fadeOut('slow', function() {
        $(this).remove();
      });
    }
    if(options && options.scroll) {
      $("html,body").scrollToVisible($indicator);
    }
  };

  $.fn.hasScrollbar = function(){
    return this.length && (this[0].clientHeight < this[0].scrollHeight);
  };

  $.fn.log = function (msg) {
    console.log("%s: %o", msg, this);
    return this;
  };

  $.fn.chevronCrumbs = function(options) {
    return this.each(function() {
      $(this).show()
        .addClass("chevron-crumbs")
        .children().not("#hide-scratch")
          .addClass('chevron-crumb')
          .append('')
          .filter(".active").prev().addClass("before-active");
    });
  };

  // this is used if you want to fill the browser window with something inside #content but you want to also leave the footer and header on the page.
  $.fn.fillWindowWithMe = function(options){
    var opts               = $.extend({minHeight: 400}, options),
        $this              = $(this),
        $wrapper_container = $('#wrapper-container'),
        $main              = $('#main'),
        $not_right_side    = $('#not_right_side'),
        $window            = $(window),
        $toResize          = $(this).add(opts.alsoResize);

    function fillWindowWithThisElement(){
      $toResize.height(0);
      var spaceLeftForThis = $window.height()
                             - ($wrapper_container.offset().top + $wrapper_container.height())
                             + ($main.height() - $not_right_side.height()),
          newHeight = Math.max(400, spaceLeftForThis);

      $toResize.height(newHeight);
      if ($.isFunction(opts.onResize)) {
        opts.onResize.call($this, newHeight);
      }
    }
    fillWindowWithThisElement();
    $window
      .unbind('resize.fillWindowWithMe')
      .bind('resize.fillWindowWithMe', fillWindowWithThisElement);
    return this;
  };

  $.fn.autoGrowInput = function(o) {

    o = $.extend({
        maxWidth: 1000,
        minWidth: 0,
        comfortZone: 70
    }, o);

    this.filter('input:text').each(function(){

      var minWidth = o.minWidth || $(this).width(),
        val = '',
        input = $(this),
        testSubject = $('<tester/>').css({
          position: 'absolute',
          top: -9999,
          left: -9999,
          width: 'auto',
          fontSize: input.css('fontSize'),
          fontFamily: input.css('fontFamily'),
          fontWeight: input.css('fontWeight'),
          letterSpacing: input.css('letterSpacing'),
          whiteSpace: 'nowrap'
        }),
        check = function() {

          setTimeout(function() {
            if (val === (val = input.val())) {return;}

            // Enter new content into testSubject
            var escaped = val.replace(/&/g, '&').replace(/\s/g,' ').replace(/</g, '<').replace(/>/g, '>');
            testSubject.html(escaped);

            // Calculate new width + whether to change
            var testerWidth = testSubject.width(),
              newWidth = (testerWidth + o.comfortZone) >= minWidth ? testerWidth + o.comfortZone : minWidth,
              currentWidth = input.width(),
              isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
                                   || (newWidth > minWidth && newWidth < o.maxWidth);

            // Animate width
            if (isValidWidthChange) {
              input.width(newWidth);
            }
          });

        };

      testSubject.insertAfter(input);

      $(this).bind('keyup keydown blur update change', check);

    });

    return this;

  };

  $.fn.fixDialogButtons = function() {
    return this.each(function() {
      var $dialog = $(this);
      $dialog.find('.button-container:last .button, button[type=submit]').ifExists(function($buttons){
        $dialog.find('.button-container:last, button[type=submit]').hide();
        var buttons = $buttons.map(function() {
          var $button = $(this);
          return {
            text: $button.text(),
            'data-text-while-loading': $button.data('textWhileLoading'),
            click: function() { $button.click() }
          };
        }).get();
        if (buttons.length) $dialog.dialog('option', 'buttons', buttons);
      });
    });
  }

  define('jquery.instructure_misc_plugins', ['i18n'], function(){ return $ });
});

<|endoftext|>


<commit_before>
(function(exports) {
  'use strict';
  var debug = Config.debug;
  var controllerWindow;

  var ControllerCommunications = {
    init: function cc_init() {
      debug && console.log('ControllerCommunications INIT');
      // We are going to communicate with Controller through post messages
      window.addEventListener(
        'message',
        function(event) {
          // We need to ensure we are receiving from the same origin
          if (event.origin !== window.location.origin) {
            return;
          }
          // As OpenTok is using window.postMessage in the same
          // window object, we need to shield our code against this type of
          // issues
          try {
            var messageFromController = JSON.parse(event.data);
            if (messageFromController.id !== 'controller') {
              debug && console.log('CallScreen: PostMessage not from Controller');
              return;
            }

            var answer;
            controllerWindow = event.source;
            switch(messageFromController.message) {
              case 'ping':
                answer = {
                  id: 'call_screen',
                  message: 'pong'
                };
                break;
              case 'call':
                var files = [
                  '../js/config.js',
                  '../js/utils.js',
                  '../libs/tokbox/' + window.OTProperties.version + '/js/TB.js',
                  '../libs/opentok.js',
                  '../js/helpers/audio_competing_helper.js',
                  '../js/helpers/call_progress_helper.js',
                  '../js/helpers/tone_player_helper.js',
                  'js/countdown.js',
                  'js/ringer.js',
                  'js/call_manager.js',
                  '../js/branding.js',
                  'js/call_screen_ui.js'
                ];

                if (Config.performanceLog.enabled) {
                  files.push('../js/helpers/performance_log_helper.js');
                }

                LazyLoader.load(
                  files,
                  function onCallScreen() {
                    CallManager.init(messageFromController.params);
                  }
                );
                break;
              case 'abort':
                LazyLoader.load(
                  [
                    '../js/helpers/tone_player_helper.js',
                    '../js/branding.js',
                    'js/call_screen_ui.js'
                  ],
                  function onCallScreen() {
                    CallScreenUI.abortCall(messageFromController.error);
                  }
                );
                break;
            }
            ControllerCommunications.send(answer);
          } catch(e) {}
        }.bind(this)
      );
    },
    send: function cc_send(params) {
      controllerWindow.postMessage(JSON.stringify(params), '*');
    }
  };

  exports.ControllerCommunications = ControllerCommunications;
}(this));

ControllerCommunications.init();
<commit_msg>Bug 1109035 - Fix one lazy loading which is breaking the conversation logic r=jaoo<commit_after>
(function(exports) {
  'use strict';
  var debug = Config.debug;
  var controllerWindow;

  var ControllerCommunications = {
    init: function cc_init() {
      debug && console.log('ControllerCommunications INIT');
      // We are going to communicate with Controller through post messages
      window.addEventListener(
        'message',
        function(event) {
          // We need to ensure we are receiving from the same origin
          if (event.origin !== window.location.origin) {
            return;
          }
          // As OpenTok is using window.postMessage in the same
          // window object, we need to shield our code against this type of
          // issues
          try {
            var messageFromController = JSON.parse(event.data);
            if (messageFromController.id !== 'controller') {
              debug && console.log('CallScreen: PostMessage not from Controller');
              return;
            }

            var answer;
            controllerWindow = event.source;
            switch(messageFromController.message) {
              case 'ping':
                answer = {
                  id: 'call_screen',
                  message: 'pong'
                };
                break;
              case 'call':
                var files = [
                  '../js/config.js',
                  '../js/utils.js',
                  '../libs/tokbox/' + window.OTProperties.version + '/js/TB.js',
                  '../libs/opentok.js',
                  '../js/helpers/audio_competing_helper.js',
                  '../js/helpers/call_progress_helper.js',
                  '../js/helpers/tone_player_helper.js',
                  '../js/helpers/countdown.js',
                  'js/ringer.js',
                  'js/call_manager.js',
                  '../js/branding.js',
                  'js/call_screen_ui.js'
                ];

                if (Config.performanceLog.enabled) {
                  files.push('../js/helpers/performance_log_helper.js');
                }

                LazyLoader.load(
                  files,
                  function onCallScreen() {
                    CallManager.init(messageFromController.params);
                  }
                );
                break;
              case 'abort':
                LazyLoader.load(
                  [
                    '../js/helpers/tone_player_helper.js',
                    '../js/branding.js',
                    'js/call_screen_ui.js'
                  ],
                  function onCallScreen() {
                    CallScreenUI.abortCall(messageFromController.error);
                  }
                );
                break;
            }
            ControllerCommunications.send(answer);
          } catch(e) {}
        }.bind(this)
      );
    },
    send: function cc_send(params) {
      controllerWindow.postMessage(JSON.stringify(params), '*');
    }
  };

  exports.ControllerCommunications = ControllerCommunications;
}(this));

ControllerCommunications.init();
<|endoftext|>


<commit_before>/**
 * @ngdoc directive
 * @name webapp.directive:wa-admin-places
 * @restrict E
 * @description Admin places manager component.
 */
angular.module('webapp').component('waAdminPlaces', {
    controller: 'AdminPlacesCtrl',
    bindings: {
        $router: '<'
    },
    $routeConfig: [
      {path: '/', name:'AdminPlacesDashboard', component: 'waAdminPlacesDashboard'},
      {path: '/:data', name:'AdminPlacesDashboardData', component: 'waAdminPlacesDashboard'},
      {path: '/create', name:'AdminPlacesCreate', component: 'waAdminPlacesCreate'},
      {path: '/edit/:id', name:'AdminPlacesEdit', component: 'waAdminPlacesEdit'}
    ],
    templateUrl: ['$element', function($element) {
        angular.element($element).addClass('layout-column')
        return 'adminPlaces.html'
    }],
    $canActivate: ['AuthService', '$rootRouter', function(AuthService, $rootRouter) {
        return AuthService.requireAdminAuth()
        .then(function(){
          return true
        })
        .catch(function(error){
          $rootRouter.navigate(['Login'])
          return false
        })
    }]
})
<commit_msg>fix routing<commit_after>/**
 * @ngdoc directive
 * @name webapp.directive:wa-admin-places
 * @restrict E
 * @description Admin places manager component.
 */
angular.module('webapp').component('waAdminPlaces', {
    controller: 'AdminPlacesCtrl',
    bindings: {
        $router: '<'
    },
    $routeConfig: [
      {path: '/', name:'AdminPlacesDashboard', component: 'waAdminPlacesDashboard', useAsDefault: true},
      {path: '/:data', name:'AdminPlacesDashboardData', component: 'waAdminPlacesDashboard'},
      {path: '/create', name:'AdminPlacesCreate', component: 'waAdminPlacesCreate'},
      {path: '/edit/:id', name:'AdminPlacesEdit', component: 'waAdminPlacesEdit'}
    ],
    templateUrl: ['$element', function($element) {
        angular.element($element).addClass('layout-column')
        return 'adminPlaces.html'
    }],
    $canActivate: ['AuthService', '$rootRouter', function(AuthService, $rootRouter) {
        return AuthService.requireAdminAuth()
        .then(function(){
          return true
        })
        .catch(function(error){
          $rootRouter.navigate(['Login'])
          return false
        })
    }]
})
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import Relay from 'react-relay';
import DocumentTitle from 'react-document-title';

import PipelineScheduleDeleteMutation from '../../../../mutations/PipelineScheduleDelete';

import PageHeader from '../../../shared/PageHeader';
import Panel from '../../../shared/Panel';
import Emojify from '../../../shared/Emojify';

import permissions from '../../../../lib/permissions';
import { getRelativeDateString } from '../../../../lib/date';

import Build from './build';

class Show extends React.Component {
  static propTypes = {
    pipelineSchedule: PropTypes.shape({
      uuid: PropTypes.string.isRequired,
      cronline: PropTypes.string.isRequired,
      label: PropTypes.string,
      commit: PropTypes.string,
      branch: PropTypes.string,
      message: PropTypes.string,
      env: PropTypes.arrayOf(PropTypes.string),
      nextBuildAt: PropTypes.string,
      pipeline: PropTypes.shape({
        slug: PropTypes.string.isRequired,
        organization: PropTypes.shape({
          slug: PropTypes.string.isRequired
        }).isRequired
      }).isRequired,
      builds: PropTypes.shape({
        edges: PropTypes.arrayOf(
          PropTypes.shape({
            node: PropTypes.shape({
              id: PropTypes.string.isRequired
            }).isRequired
          }).isRequired
        )
      }).isRequired,
      createdBy: PropTypes.shape({
        name: PropTypes.string.isRequired
      }).isRequired,
      permissions: PropTypes.shape({
        pipelineScheduleUpdate: PropTypes.shape({
          allowed: PropTypes.bool.isRequired
        }).isRequired,
        pipelineScheduleDelete: PropTypes.shape({
          allowed: PropTypes.bool.isRequired
        }).isRequired
      }).isRequired
    }).isRequired
  };

  static contextTypes = {
    router: PropTypes.object.isRequired
  };

  state = {
    deleting: false
  }

  render() {
    return (
      <DocumentTitle title={this.props.pipelineSchedule.cronline}>

          <PageHeader>
            <PageHeader.Title><Emojify text={this.props.pipelineSchedule.label || "No description"} /></PageHeader.Title>
            <PageHeader.Description>{this.props.pipelineSchedule.cronline}</PageHeader.Description>
            <PageHeader.Menu>{this.renderMenu()}</PageHeader.Menu>
          </PageHeader>

          <Panel className="mb4">
            <Panel.Section>

Commit

{this.props.pipelineSchedule.commit}

Branch

{this.props.pipelineSchedule.branch}

Message

{this.props.pipelineSchedule.message || "Scheduled build"}

              {this.renderEnv()}

Creator

{this.props.pipelineSchedule.createdBy.name}

            </Panel.Section>
          </Panel>

          <Panel>
            <Panel.Header>Recent Builds</Panel.Header>
            <Panel.Row>

                <Emojify text={`Next build scheduled for ${getRelativeDateString(this.props.pipelineSchedule.nextBuildAt)}`} />

            </Panel.Row>
            {this.props.pipelineSchedule.builds.edges.map((edge) => <Build key={edge.node.id} build={edge.node} />)}
          </Panel>

      </DocumentTitle>
    );
  }

  renderEnv() {
    if (this.props.pipelineSchedule.env.length !== 0) {
      return (

Environment Variables

{this.props.pipelineSchedule.env.join("\n")}

      );
    } else {
      return null;
    }
  }

  renderMenu() {
    return permissions(this.props.pipelineSchedule.permissions).collect(
      {
        allowed: "pipelineScheduleUpdate",
        render: (idx) => {
          const pipeline = this.props.pipelineSchedule.pipeline;
          const organization = this.props.pipelineSchedule.pipeline.organization;

          return (
            <PageHeader.Button key={idx} link={`/${organization.slug}/${pipeline.slug}/settings/schedules/${this.props.pipelineSchedule.uuid}/edit`}>Edit</PageHeader.Button>
          );
        }
      },
      {
        allowed: "pipelineScheduleDelete",
        render: (idx) => (
          <PageHeader.Button key={idx} loading={this.state.deleting ? "Deleting…" : false} onClick={this.handleScheduleDeleteClick}>Delete</PageHeader.Button>
        )
      }
    );
  }

  handleScheduleDeleteClick = () => {
    if (confirm("Delete this schedule?")) {
      this.setState({ deleting: true });

      const mutation = new PipelineScheduleDeleteMutation({
        pipelineSchedule: this.props.pipelineSchedule
      });

      Relay.Store.commitUpdate(mutation, {
        onSuccess: this.handleDeleteMutationSuccess,
        onFailure: this.handleDeleteMutationFailure
      });
    }
  }

  handleDeleteMutationSuccess = () => {
    const pipeline = this.props.pipelineSchedule.pipeline;
    const organization = this.props.pipelineSchedule.pipeline.organization;

    this.context.router.push(`/${organization.slug}/${pipeline.slug}/settings/schedules`);
  }

  handleDeleteMutationFailure = (transaction) => {
    this.setState({ deleting: false });

    alert(transaction.getError());
  }
}

export default Relay.createContainer(Show, {
  fragments: {
    pipelineSchedule: () => Relay.QL`
      fragment on PipelineSchedule {
        ${PipelineScheduleDeleteMutation.getFragment('pipelineSchedule')}
        uuid
        cronline
        label
        nextBuildAt
        commit
        branch
        message
        env
        createdBy {
          name
        }
        pipeline {
          slug
          organization {
            slug
          }
        }
        permissions {
          pipelineScheduleUpdate {
            allowed
          }
          pipelineScheduleDelete {
            allowed
          }
        }
        builds(first: 5) {
          edges {
            node {
              id
              ${Build.getFragment('build')}
            }
          }
        }
      }
    `
  }
});
<commit_msg>"Created By" instead of "Creator"<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import Relay from 'react-relay';
import DocumentTitle from 'react-document-title';

import PipelineScheduleDeleteMutation from '../../../../mutations/PipelineScheduleDelete';

import PageHeader from '../../../shared/PageHeader';
import Panel from '../../../shared/Panel';
import Emojify from '../../../shared/Emojify';

import permissions from '../../../../lib/permissions';
import { getRelativeDateString } from '../../../../lib/date';

import Build from './build';

class Show extends React.Component {
  static propTypes = {
    pipelineSchedule: PropTypes.shape({
      uuid: PropTypes.string.isRequired,
      cronline: PropTypes.string.isRequired,
      label: PropTypes.string,
      commit: PropTypes.string,
      branch: PropTypes.string,
      message: PropTypes.string,
      env: PropTypes.arrayOf(PropTypes.string),
      nextBuildAt: PropTypes.string,
      pipeline: PropTypes.shape({
        slug: PropTypes.string.isRequired,
        organization: PropTypes.shape({
          slug: PropTypes.string.isRequired
        }).isRequired
      }).isRequired,
      builds: PropTypes.shape({
        edges: PropTypes.arrayOf(
          PropTypes.shape({
            node: PropTypes.shape({
              id: PropTypes.string.isRequired
            }).isRequired
          }).isRequired
        )
      }).isRequired,
      createdBy: PropTypes.shape({
        name: PropTypes.string.isRequired
      }).isRequired,
      permissions: PropTypes.shape({
        pipelineScheduleUpdate: PropTypes.shape({
          allowed: PropTypes.bool.isRequired
        }).isRequired,
        pipelineScheduleDelete: PropTypes.shape({
          allowed: PropTypes.bool.isRequired
        }).isRequired
      }).isRequired
    }).isRequired
  };

  static contextTypes = {
    router: PropTypes.object.isRequired
  };

  state = {
    deleting: false
  }

  render() {
    return (
      <DocumentTitle title={this.props.pipelineSchedule.cronline}>

          <PageHeader>
            <PageHeader.Title><Emojify text={this.props.pipelineSchedule.label || "No description"} /></PageHeader.Title>
            <PageHeader.Description>{this.props.pipelineSchedule.cronline}</PageHeader.Description>
            <PageHeader.Menu>{this.renderMenu()}</PageHeader.Menu>
          </PageHeader>

          <Panel className="mb4">
            <Panel.Section>

Commit

{this.props.pipelineSchedule.commit}

Branch

{this.props.pipelineSchedule.branch}

Message

{this.props.pipelineSchedule.message || "Scheduled build"}

              {this.renderEnv()}

Created By

{this.props.pipelineSchedule.createdBy.name}

            </Panel.Section>
          </Panel>

          <Panel>
            <Panel.Header>Recent Builds</Panel.Header>
            <Panel.Row>

                <Emojify text={`Next build scheduled for ${getRelativeDateString(this.props.pipelineSchedule.nextBuildAt)}`} />

            </Panel.Row>
            {this.props.pipelineSchedule.builds.edges.map((edge) => <Build key={edge.node.id} build={edge.node} />)}
          </Panel>

      </DocumentTitle>
    );
  }

  renderEnv() {
    if (this.props.pipelineSchedule.env.length !== 0) {
      return (

Environment Variables

{this.props.pipelineSchedule.env.join("\n")}

      );
    } else {
      return null;
    }
  }

  renderMenu() {
    return permissions(this.props.pipelineSchedule.permissions).collect(
      {
        allowed: "pipelineScheduleUpdate",
        render: (idx) => {
          const pipeline = this.props.pipelineSchedule.pipeline;
          const organization = this.props.pipelineSchedule.pipeline.organization;

          return (
            <PageHeader.Button key={idx} link={`/${organization.slug}/${pipeline.slug}/settings/schedules/${this.props.pipelineSchedule.uuid}/edit`}>Edit</PageHeader.Button>
          );
        }
      },
      {
        allowed: "pipelineScheduleDelete",
        render: (idx) => (
          <PageHeader.Button key={idx} loading={this.state.deleting ? "Deleting…" : false} onClick={this.handleScheduleDeleteClick}>Delete</PageHeader.Button>
        )
      }
    );
  }

  handleScheduleDeleteClick = () => {
    if (confirm("Delete this schedule?")) {
      this.setState({ deleting: true });

      const mutation = new PipelineScheduleDeleteMutation({
        pipelineSchedule: this.props.pipelineSchedule
      });

      Relay.Store.commitUpdate(mutation, {
        onSuccess: this.handleDeleteMutationSuccess,
        onFailure: this.handleDeleteMutationFailure
      });
    }
  }

  handleDeleteMutationSuccess = () => {
    const pipeline = this.props.pipelineSchedule.pipeline;
    const organization = this.props.pipelineSchedule.pipeline.organization;

    this.context.router.push(`/${organization.slug}/${pipeline.slug}/settings/schedules`);
  }

  handleDeleteMutationFailure = (transaction) => {
    this.setState({ deleting: false });

    alert(transaction.getError());
  }
}

export default Relay.createContainer(Show, {
  fragments: {
    pipelineSchedule: () => Relay.QL`
      fragment on PipelineSchedule {
        ${PipelineScheduleDeleteMutation.getFragment('pipelineSchedule')}
        uuid
        cronline
        label
        nextBuildAt
        commit
        branch
        message
        env
        createdBy {
          name
        }
        pipeline {
          slug
          organization {
            slug
          }
        }
        permissions {
          pipelineScheduleUpdate {
            allowed
          }
          pipelineScheduleDelete {
            allowed
          }
        }
        builds(first: 5) {
          edges {
            node {
              id
              ${Build.getFragment('build')}
            }
          }
        }
      }
    `
  }
});
<|endoftext|>


<commit_before>/*global Marionette, Backbone, WebFormSession, Util */

/**
 * The primary Marionette application managing menu navigation and launching form entry
 */

var FormplayerFrontend = new Marionette.Application();

var showError = hqImport('cloudcare/js/util.js').showError;
var showHTMLError = hqImport('cloudcare/js/util.js').showHTMLError;
var showSuccess = hqImport('cloudcare/js/util.js').showSuccess;
var tfLoading = hqImport('cloudcare/js/util.js').tfLoading;
var tfLoadingComplete = hqImport('cloudcare/js/util.js').tfLoadingComplete;
var tfSyncComplete = hqImport('cloudcare/js/util.js').tfSyncComplete;

FormplayerFrontend.on("before:start", function () {
    var RegionContainer = Marionette.LayoutView.extend({
        el: "#menu-container",

        regions: {
            main: "#menu-region",
            loadingProgress: "#formplayer-progress-container",
            breadcrumb: "#breadcrumb-region",
            persistentCaseTile: "#persistent-case-tile",
            restoreAsBanner: '#restore-as-region',
        },
    });

    FormplayerFrontend.regions = new RegionContainer();
});

FormplayerFrontend.navigate = function (route, options) {
    options || (options = {});
    Backbone.history.navigate(route, options);
};

FormplayerFrontend.getCurrentRoute = function () {
    return Backbone.history.fragment;
};

/**
 * This function maps a jr:// media path to its HTML path IE
 * jr://images/icon/mother.png -> https://commcarehq.org/hq/multimedia/file/CommCareImage/[app_id]/mother.png
 * The actual mapping is contained in the app Couch document
 */
FormplayerFrontend.reqres.setHandler('resourceMap', function (resource_path, app_id) {
    var currentApp = FormplayerFrontend.request("appselect:getApp", app_id);
    if (!currentApp) {
        console.warn('App is undefined for app_id: ' + app_id);
        console.warn('Not processing resource: ' + resource_path);
        return;
    }
    if (resource_path.substring(0, 7) === 'http://') {
        return resource_path;
    } else if (!_.isEmpty(currentApp.get("multimedia_map"))) {
        var resource = currentApp.get('multimedia_map')[resource_path];
        if (!resource) {
            console.warn('Unable to find resource ' + resource_path + 'in multimedia map');
            return;
        }
        var id = resource.multimedia_id;
        var media_type = resource.media_type;
        var name = _.last(resource_path.split('/'));
        return '/hq/multimedia/file/' + media_type + '/' + id + '/' + name;
    }
});

FormplayerFrontend.reqres.setHandler('gridPolyfillPath', function(path) {
    if (path) {
        FormplayerFrontend.gridPolyfillPath = path;
    } else {
        return FormplayerFrontend.gridPolyfillPath;
    }
});

FormplayerFrontend.reqres.setHandler('currentUser', function () {
    if (!FormplayerFrontend.currentUser) {
        FormplayerFrontend.currentUser = new FormplayerFrontend.Users.Models.CurrentUser();
    }
    return FormplayerFrontend.currentUser;
});

FormplayerFrontend.on('clearForm', function () {
    $('#webforms').html("");
    $('#menu-container').removeClass('hide');
    $('#webforms-nav').html("");
    $('#cloudcare-debugger').html("");
    $('.atwho-container').remove();
});

FormplayerFrontend.reqres.setHandler('clearMenu', function () {
    $('#menu-region').html("");
});

$(document).on("ajaxStart", function () {
    $(".formplayer-request").addClass('formplayer-requester-disabled');
    tfLoading();
}).on("ajaxStop", function () {
    $(".formplayer-request").removeClass('formplayer-requester-disabled');
    tfLoadingComplete();
});

FormplayerFrontend.on('showError', function (errorMessage, isHTML) {
    if (isHTML) {
        showHTMLError(errorMessage, $("#cloudcare-notifications"));
    } else {
        showError(errorMessage, $("#cloudcare-notifications"));
    }
});

FormplayerFrontend.reqres.setHandler('showSuccess', function(successMessage) {
    showSuccess(successMessage, $("#cloudcare-notifications"), 10000);
});

FormplayerFrontend.reqres.setHandler('handleNotification', function(notification) {
    if(notification.error){
        FormplayerFrontend.request('showError', notification.message);
    } else{
        FormplayerFrontend.request('showSuccess', notification.message);
    }
});

FormplayerFrontend.on('startForm', function (data) {
    FormplayerFrontend.request("clearMenu");
    var urlObject = Util.currentUrlToObject();
    urlObject.setSessionId(data.session_id);
    Util.setUrlToObject(urlObject, true);
    data.onLoading = tfLoading;
    data.onLoadingComplete = tfLoadingComplete;
    var user = FormplayerFrontend.request('currentUser');
    data.xform_url = user.formplayer_url;
    data.domain = user.domain;
    data.username = user.username;
    data.restoreAs = user.restoreAs;
    data.formplayerEnabled = true;
    data.displayOptions = $.extend(true, {}, user.displayOptions);
    data.onerror = function (resp) {
        showError(resp.human_readable_message || resp.message, $("#cloudcare-notifications"));
    };
    data.onsubmit = function (resp) {
        if (resp.status === "success") {
            showSuccess(gettext("Form successfully saved"), $("#cloudcare-notifications"), 10000);

            // After end of form nav, we want to clear everything except app and sesson id
            var urlObject = Util.currentUrlToObject();
            urlObject.onSubmit();
            Util.setUrlToObject(urlObject);

            if(resp.nextScreen !== null && resp.nextScreen !== undefined) {
                FormplayerFrontend.trigger("renderResponse", resp.nextScreen);
            } else if(urlObject.appId !== null && urlObject.appId !== undefined) {
                FormplayerFrontend.trigger("apps:currentApp");
            } else {
                FormplayerFrontend.navigate('/apps', { trigger: true });
            }
        } else {
            showError(resp.output, $("#cloudcare-notifications"));
        }
    };
    data.debuggerEnabled = user.debuggerEnabled;
    data.resourceMap = function(resource_path) {
        var urlObject = Util.currentUrlToObject();
        var appId = urlObject.appId;
        return FormplayerFrontend.request('resourceMap', resource_path, appId);
    };
    var sess = new WebFormSession(data);
    sess.renderFormXml(data, $('#webforms'));
    $('#menu-container').addClass('hide');
});

FormplayerFrontend.on("start", function (options) {
    var user = FormplayerFrontend.request('currentUser'),
        savedDisplayOptions,
        appId;
    user.username = options.username;
    user.language = options.language;
    user.apps = options.apps;
    user.domain = options.domain;
    user.formplayer_url = options.formplayer_url;
    user.debuggerEnabled = options.debuggerEnabled;
    user.restoreAs = FormplayerFrontend.request('restoreAsUser', user.domain, user.username);

    savedDisplayOptions = _.pick(
        Util.getSavedDisplayOptions(),
        FormplayerFrontend.Constants.ALLOWED_SAVED_OPTIONS
    );
    user.displayOptions = _.defaults(savedDisplayOptions, {
        singleAppMode: options.singleAppMode,
        phoneMode: options.phoneMode,
        oneQuestionPerScreen: options.oneQuestionPerScreen,
    });

    FormplayerFrontend.request('gridPolyfillPath', options.gridPolyfillPath);
    if (Backbone.history) {
        Backbone.history.start();
        FormplayerFrontend.regions.restoreAsBanner.show(
            new FormplayerFrontend.Users.Views.RestoreAsBanner({
                model: user,
            })
        );
        if (user.displayOptions.singleAppMode) {
            appId = options.apps[0]['_id'];
        }

        // will be the same for every domain. TODO: get domain/username/pass from django
        if (this.getCurrentRoute() === "") {
            if (user.displayOptions.singleAppMode) {
                FormplayerFrontend.trigger('setAppDisplayProperties', options.apps[0]);
                FormplayerFrontend.trigger("app:singleApp", appId);
            } else {
                FormplayerFrontend.trigger("apps:list", options.apps);
            }
            if (user.displayOptions.phoneMode) {
                // Refresh on start of preview mode so it ensures we're on the latest app
                // since app updates do not work.
                FormplayerFrontend.trigger('refreshApplication', appId);
            }
        }
    }

    if (options.allowedHost) {
        window.addEventListener(
            "message",
            new FormplayerFrontend.HQ.Events.Receiver(options.allowedHost),
            false
        );
    }
});

FormplayerFrontend.reqres.setHandler('getCurrentSessionId', function() {
    return Util.currentUrlToObject().sessionId;
});

FormplayerFrontend.reqres.setHandler('getCurrentAppId', function() {
    // First attempt to grab app id from URL
    var urlObject = Util.currentUrlToObject(),
        user = FormplayerFrontend.request('currentUser'),
        appId;

    appId = urlObject.appId;

    if (appId) {
        return appId;
    }

    // If it's not in the URL, then we are either on the home screen of formplayer
    // and there is no app selected, or we are in preview mode.
    appId = user.previewAppId;

    return appId || null;
});

FormplayerFrontend.on('navigation:back', function() {
    var url = Backbone.history.getFragment();
    if (!url.includes('single_app')) {
        window.history.back();
    }
});

FormplayerFrontend.on('setAppDisplayProperties', function(app) {
    FormplayerFrontend.DisplayProperties = app.profile.properties;
    if (Object.freeze) {
        Object.freeze(FormplayerFrontend.DisplayProperties);
    }
});

FormplayerFrontend.reqres.setHandler('getAppDisplayProperties', function() {
    return FormplayerFrontend.DisplayProperties || {};
});

FormplayerFrontend.reqres.setHandler('restoreAsUser', function(domain, username) {
    return FormplayerFrontend.Utils.Users.getRestoreAsUser(
        domain,
        username
    );
});

/**
 * clearRestoreAsUser
 *
 * This will unset the localStorage restore as user as well as
 * unset the restore as user from the currentUser. It then
 * navigates you to the main page.
 */
FormplayerFrontend.on('clearRestoreAsUser', function() {
    var user = FormplayerFrontend.request('currentUser'),
        appId;
    FormplayerFrontend.Utils.Users.clearRestoreAsUser(
        user.domain,
        user.username
    );
    user.restoreAs = null;
    FormplayerFrontend.regions.restoreAsBanner.show(
        new FormplayerFrontend.Users.Views.RestoreAsBanner({
            model: user,
        })
    );

    FormplayerFrontend.trigger('navigateHome');
});

FormplayerFrontend.on("sync", function () {
    var user = FormplayerFrontend.request('currentUser'),
        username = user.username,
        domain = user.domain,
        formplayer_url = user.formplayer_url,
        complete,
        options;

    complete = function(response) {
        if (response.responseJSON.status === 'retry') {
            FormplayerFrontend.trigger('retry', response.responseJSON, function() {
                $.ajax(options);
            }, gettext('Waiting for server progress'));
        } else {
            FormplayerFrontend.trigger('clearProgress');
            tfSyncComplete(response.responseJSON.status === 'error');
        }
    };
    options = {
        url: formplayer_url + "/sync-db",
        data: JSON.stringify({
            "username": username,
            "domain": domain,
            "restoreAs": user.restoreAs,
        }),
        complete: complete,
    };
    Util.setCrossDomainAjaxOptions(options);
    $.ajax(options);
});

/**
 * retry
 *
 * Will retry a restore when doing an async restore.
 *
 * @param {Object} response - An async restore response object
 * @param {function} retryFn - The function to be called when ready to retry restoring
 * @param {String} progressMessage - The message to be displayed above the progress bar
 */
FormplayerFrontend.on("retry", function(response, retryFn, progressMessage) {

    var progressView = FormplayerFrontend.regions.loadingProgress.currentView,
        retryTimeout = response.retryAfter * 1000;
    progressMessage = progressMessage || gettext('Please wait...');

    if (!progressView) {
        progressView = new FormplayerFrontend.Layout.Views.ProgressView({
            progressMessage: progressMessage,
        });
        FormplayerFrontend.regions.loadingProgress.show(progressView);
    }

    progressView.setProgress(response.done, response.total, retryTimeout);
    setTimeout(retryFn, retryTimeout);
});

FormplayerFrontend.on('view:tablet', function() {
    $('body').addClass('preview-tablet-mode');
});

FormplayerFrontend.on('view:phone', function() {
    $('body').removeClass('preview-tablet-mode');
});

/**
 * clearProgress
 *
 * Clears the progress bar. If currently in progress, wait 200 ms to transition
 * to complete progress.
 */
FormplayerFrontend.on('clearProgress', function() {
    var progressView = FormplayerFrontend.regions.loadingProgress.currentView,
        progressFinishTimeout = 0;
    if (progressView) {
        progressFinishTimeout = 200;
        progressView.setProgress(1, progressFinishTimeout);
    }

    setTimeout(function() {
        FormplayerFrontend.regions.loadingProgress.empty();
    }, progressFinishTimeout);
});

FormplayerFrontend.on('setVersionInfo', function(versionInfo) {
    var user = FormplayerFrontend.request('currentUser');
    $("#version-info").text(versionInfo || '');
    if (versionInfo) {
        user.set('versionInfo',  versionInfo);
    }
});

/**
 * refreshApplication
 *
 * This takes an appId and subsequently makes a request to formplayer to
 * delete the relevant application database so that on next request
 * it gets reinstalled. On completion, navigates back to the homescreen.
 *
 * @param {String} appId - The id of the application to refresh
 */
FormplayerFrontend.on('refreshApplication', function(appId) {
    if (!appId) {
        throw new Error('Attempt to refresh application for null appId');
    }
    var sessionId = FormplayerFrontend.request('getCurrentSessionId');
    var user = FormplayerFrontend.request('currentUser'),
        formplayer_url = user.formplayer_url,
        resp,
        options = {
            url: formplayer_url + "/delete_application_dbs",
            data: JSON.stringify({
                app_id: appId,
                domain: user.domain,
                username: user.username,
                restoreAs: user.restoreAs,
                sessionId: sessionId,
                updateMode: "save",
            }),
        };
    Util.setCrossDomainAjaxOptions(options);
    tfLoading();
    resp = $.ajax(options);
    resp.fail(function () {
        tfLoadingComplete(true);
    }).done(function() {
        tfLoadingComplete();
        if (!_.isUndefined(resp.responseJSON.tree)) {
            FormplayerFrontend.trigger('startForm', resp.responseJSON);
        } else {
            FormplayerFrontend.trigger('navigateHome');
        }

    });
});

FormplayerFrontend.on('navigateHome', function() {
    var urlObject = Util.currentUrlToObject(),
        appId,
        currentUser = FormplayerFrontend.request('currentUser');
    urlObject.clearExceptApp();
    FormplayerFrontend.regions.breadcrumb.empty();
    if (currentUser.displayOptions.singleAppMode) {
        appId = FormplayerFrontend.request('getCurrentAppId');
        FormplayerFrontend.navigate("/single_app/" + appId, { trigger: true });
    } else {
        FormplayerFrontend.navigate("/apps", { trigger: true });
    }
});
<commit_msg>revert remaining changes from PR 14004<commit_after>/*global Marionette, Backbone, WebFormSession, Util */

/**
 * The primary Marionette application managing menu navigation and launching form entry
 */

var FormplayerFrontend = new Marionette.Application();

var showError = hqImport('cloudcare/js/util.js').showError;
var showHTMLError = hqImport('cloudcare/js/util.js').showHTMLError;
var showSuccess = hqImport('cloudcare/js/util.js').showSuccess;
var tfLoading = hqImport('cloudcare/js/util.js').tfLoading;
var tfLoadingComplete = hqImport('cloudcare/js/util.js').tfLoadingComplete;
var tfSyncComplete = hqImport('cloudcare/js/util.js').tfSyncComplete;

FormplayerFrontend.on("before:start", function () {
    var RegionContainer = Marionette.LayoutView.extend({
        el: "#menu-container",

        regions: {
            main: "#menu-region",
            loadingProgress: "#formplayer-progress-container",
            breadcrumb: "#breadcrumb-region",
            persistentCaseTile: "#persistent-case-tile",
            restoreAsBanner: '#restore-as-region',
        },
    });

    FormplayerFrontend.regions = new RegionContainer();
});

FormplayerFrontend.navigate = function (route, options) {
    options || (options = {});
    Backbone.history.navigate(route, options);
};

FormplayerFrontend.getCurrentRoute = function () {
    return Backbone.history.fragment;
};

/**
 * This function maps a jr:// media path to its HTML path IE
 * jr://images/icon/mother.png -> https://commcarehq.org/hq/multimedia/file/CommCareImage/[app_id]/mother.png
 * The actual mapping is contained in the app Couch document
 */
FormplayerFrontend.reqres.setHandler('resourceMap', function (resource_path, app_id) {
    var currentApp = FormplayerFrontend.request("appselect:getApp", app_id);
    if (!currentApp) {
        console.warn('App is undefined for app_id: ' + app_id);
        console.warn('Not processing resource: ' + resource_path);
        return;
    }
    if (resource_path.substring(0, 7) === 'http://') {
        return resource_path;
    } else if (!_.isEmpty(currentApp.get("multimedia_map"))) {
        var resource = currentApp.get('multimedia_map')[resource_path];
        if (!resource) {
            console.warn('Unable to find resource ' + resource_path + 'in multimedia map');
            return;
        }
        var id = resource.multimedia_id;
        var media_type = resource.media_type;
        var name = _.last(resource_path.split('/'));
        return '/hq/multimedia/file/' + media_type + '/' + id + '/' + name;
    }
});

FormplayerFrontend.reqres.setHandler('gridPolyfillPath', function(path) {
    if (path) {
        FormplayerFrontend.gridPolyfillPath = path;
    } else {
        return FormplayerFrontend.gridPolyfillPath;
    }
});

FormplayerFrontend.reqres.setHandler('currentUser', function () {
    if (!FormplayerFrontend.currentUser) {
        FormplayerFrontend.currentUser = new FormplayerFrontend.Users.Models.CurrentUser();
    }
    return FormplayerFrontend.currentUser;
});

FormplayerFrontend.on('clearForm', function () {
    $('#webforms').html("");
    $('#menu-container').removeClass('hide');
    $('#webforms-nav').html("");
    $('#cloudcare-debugger').html("");
    $('.atwho-container').remove();
});

FormplayerFrontend.reqres.setHandler('clearMenu', function () {
    $('#menu-region').html("");
});

$(document).on("ajaxStart", function () {
    $(".formplayer-request").addClass('formplayer-requester-disabled');
    tfLoading();
}).on("ajaxStop", function () {
    $(".formplayer-request").removeClass('formplayer-requester-disabled');
    tfLoadingComplete();
});

FormplayerFrontend.on('showError', function (errorMessage, isHTML) {
    if (isHTML) {
        showHTMLError(errorMessage, $("#cloudcare-notifications"));
    } else {
        showError(errorMessage, $("#cloudcare-notifications"));
    }
});

FormplayerFrontend.reqres.setHandler('showSuccess', function(successMessage) {
    showSuccess(successMessage, $("#cloudcare-notifications"), 10000);
});

FormplayerFrontend.reqres.setHandler('handleNotification', function(notification) {
    if(notification.error){
        FormplayerFrontend.request('showError', notification.message);
    } else{
        FormplayerFrontend.request('showSuccess', notification.message);
    }
});

FormplayerFrontend.on('startForm', function (data) {
    FormplayerFrontend.request("clearMenu");
    data.onLoading = tfLoading;
    data.onLoadingComplete = tfLoadingComplete;
    var user = FormplayerFrontend.request('currentUser');
    data.xform_url = user.formplayer_url;
    data.domain = user.domain;
    data.username = user.username;
    data.restoreAs = user.restoreAs;
    data.formplayerEnabled = true;
    data.displayOptions = $.extend(true, {}, user.displayOptions);
    data.onerror = function (resp) {
        showError(resp.human_readable_message || resp.message, $("#cloudcare-notifications"));
    };
    data.onsubmit = function (resp) {
        if (resp.status === "success") {
            showSuccess(gettext("Form successfully saved"), $("#cloudcare-notifications"), 10000);

            // After end of form nav, we want to clear everything except app and sesson id
            var urlObject = Util.currentUrlToObject();
            urlObject.onSubmit();
            Util.setUrlToObject(urlObject);

            if(resp.nextScreen !== null && resp.nextScreen !== undefined) {
                FormplayerFrontend.trigger("renderResponse", resp.nextScreen);
            } else if(urlObject.appId !== null && urlObject.appId !== undefined) {
                FormplayerFrontend.trigger("apps:currentApp");
            } else {
                FormplayerFrontend.navigate('/apps', { trigger: true });
            }
        } else {
            showError(resp.output, $("#cloudcare-notifications"));
        }
    };
    data.debuggerEnabled = user.debuggerEnabled;
    data.resourceMap = function(resource_path) {
        var urlObject = Util.currentUrlToObject();
        var appId = urlObject.appId;
        return FormplayerFrontend.request('resourceMap', resource_path, appId);
    };
    var sess = new WebFormSession(data);
    sess.renderFormXml(data, $('#webforms'));
    $('#menu-container').addClass('hide');
});

FormplayerFrontend.on("start", function (options) {
    var user = FormplayerFrontend.request('currentUser'),
        savedDisplayOptions,
        appId;
    user.username = options.username;
    user.language = options.language;
    user.apps = options.apps;
    user.domain = options.domain;
    user.formplayer_url = options.formplayer_url;
    user.debuggerEnabled = options.debuggerEnabled;
    user.restoreAs = FormplayerFrontend.request('restoreAsUser', user.domain, user.username);

    savedDisplayOptions = _.pick(
        Util.getSavedDisplayOptions(),
        FormplayerFrontend.Constants.ALLOWED_SAVED_OPTIONS
    );
    user.displayOptions = _.defaults(savedDisplayOptions, {
        singleAppMode: options.singleAppMode,
        phoneMode: options.phoneMode,
        oneQuestionPerScreen: options.oneQuestionPerScreen,
    });

    FormplayerFrontend.request('gridPolyfillPath', options.gridPolyfillPath);
    if (Backbone.history) {
        Backbone.history.start();
        FormplayerFrontend.regions.restoreAsBanner.show(
            new FormplayerFrontend.Users.Views.RestoreAsBanner({
                model: user,
            })
        );
        if (user.displayOptions.singleAppMode) {
            appId = options.apps[0]['_id'];
        }

        // will be the same for every domain. TODO: get domain/username/pass from django
        if (this.getCurrentRoute() === "") {
            if (user.displayOptions.singleAppMode) {
                FormplayerFrontend.trigger('setAppDisplayProperties', options.apps[0]);
                FormplayerFrontend.trigger("app:singleApp", appId);
            } else {
                FormplayerFrontend.trigger("apps:list", options.apps);
            }
            if (user.displayOptions.phoneMode) {
                // Refresh on start of preview mode so it ensures we're on the latest app
                // since app updates do not work.
                FormplayerFrontend.trigger('refreshApplication', appId);
            }
        }
    }

    if (options.allowedHost) {
        window.addEventListener(
            "message",
            new FormplayerFrontend.HQ.Events.Receiver(options.allowedHost),
            false
        );
    }
});

FormplayerFrontend.reqres.setHandler('getCurrentAppId', function() {
    // First attempt to grab app id from URL
    var urlObject = Util.currentUrlToObject(),
        user = FormplayerFrontend.request('currentUser'),
        appId;

    appId = urlObject.appId;

    if (appId) {
        return appId;
    }

    // If it's not in the URL, then we are either on the home screen of formplayer
    // and there is no app selected, or we are in preview mode.
    appId = user.previewAppId;

    return appId || null;
});

FormplayerFrontend.on('navigation:back', function() {
    var url = Backbone.history.getFragment();
    if (!url.includes('single_app')) {
        window.history.back();
    }
});

FormplayerFrontend.on('setAppDisplayProperties', function(app) {
    FormplayerFrontend.DisplayProperties = app.profile.properties;
    if (Object.freeze) {
        Object.freeze(FormplayerFrontend.DisplayProperties);
    }
});

FormplayerFrontend.reqres.setHandler('getAppDisplayProperties', function() {
    return FormplayerFrontend.DisplayProperties || {};
});

FormplayerFrontend.reqres.setHandler('restoreAsUser', function(domain, username) {
    return FormplayerFrontend.Utils.Users.getRestoreAsUser(
        domain,
        username
    );
});

/**
 * clearRestoreAsUser
 *
 * This will unset the localStorage restore as user as well as
 * unset the restore as user from the currentUser. It then
 * navigates you to the main page.
 */
FormplayerFrontend.on('clearRestoreAsUser', function() {
    var user = FormplayerFrontend.request('currentUser'),
        appId;
    FormplayerFrontend.Utils.Users.clearRestoreAsUser(
        user.domain,
        user.username
    );
    user.restoreAs = null;
    FormplayerFrontend.regions.restoreAsBanner.show(
        new FormplayerFrontend.Users.Views.RestoreAsBanner({
            model: user,
        })
    );

    FormplayerFrontend.trigger('navigateHome');
});

FormplayerFrontend.on("sync", function () {
    var user = FormplayerFrontend.request('currentUser'),
        username = user.username,
        domain = user.domain,
        formplayer_url = user.formplayer_url,
        complete,
        options;

    complete = function(response) {
        if (response.responseJSON.status === 'retry') {
            FormplayerFrontend.trigger('retry', response.responseJSON, function() {
                $.ajax(options);
            }, gettext('Waiting for server progress'));
        } else {
            FormplayerFrontend.trigger('clearProgress');
            tfSyncComplete(response.responseJSON.status === 'error');
        }
    };
    options = {
        url: formplayer_url + "/sync-db",
        data: JSON.stringify({
            "username": username,
            "domain": domain,
            "restoreAs": user.restoreAs,
        }),
        complete: complete,
    };
    Util.setCrossDomainAjaxOptions(options);
    $.ajax(options);
});

/**
 * retry
 *
 * Will retry a restore when doing an async restore.
 *
 * @param {Object} response - An async restore response object
 * @param {function} retryFn - The function to be called when ready to retry restoring
 * @param {String} progressMessage - The message to be displayed above the progress bar
 */
FormplayerFrontend.on("retry", function(response, retryFn, progressMessage) {

    var progressView = FormplayerFrontend.regions.loadingProgress.currentView,
        retryTimeout = response.retryAfter * 1000;
    progressMessage = progressMessage || gettext('Please wait...');

    if (!progressView) {
        progressView = new FormplayerFrontend.Layout.Views.ProgressView({
            progressMessage: progressMessage,
        });
        FormplayerFrontend.regions.loadingProgress.show(progressView);
    }

    progressView.setProgress(response.done, response.total, retryTimeout);
    setTimeout(retryFn, retryTimeout);
});

FormplayerFrontend.on('view:tablet', function() {
    $('body').addClass('preview-tablet-mode');
});

FormplayerFrontend.on('view:phone', function() {
    $('body').removeClass('preview-tablet-mode');
});

/**
 * clearProgress
 *
 * Clears the progress bar. If currently in progress, wait 200 ms to transition
 * to complete progress.
 */
FormplayerFrontend.on('clearProgress', function() {
    var progressView = FormplayerFrontend.regions.loadingProgress.currentView,
        progressFinishTimeout = 0;
    if (progressView) {
        progressFinishTimeout = 200;
        progressView.setProgress(1, progressFinishTimeout);
    }

    setTimeout(function() {
        FormplayerFrontend.regions.loadingProgress.empty();
    }, progressFinishTimeout);
});

FormplayerFrontend.on('setVersionInfo', function(versionInfo) {
    var user = FormplayerFrontend.request('currentUser');
    $("#version-info").text(versionInfo || '');
    if (versionInfo) {
        user.set('versionInfo',  versionInfo);
    }
});

/**
 * refreshApplication
 *
 * This takes an appId and subsequently makes a request to formplayer to
 * delete the relevant application database so that on next request
 * it gets reinstalled. On completion, navigates back to the homescreen.
 *
 * @param {String} appId - The id of the application to refresh
 */
FormplayerFrontend.on('refreshApplication', function(appId) {
    if (!appId) {
        throw new Error('Attempt to refresh application for null appId');
    }
    var user = FormplayerFrontend.request('currentUser'),
        formplayer_url = user.formplayer_url,
        resp,
        options = {
            url: formplayer_url + "/delete_application_dbs",
            data: JSON.stringify({
                app_id: appId,
                domain: user.domain,
                username: user.username,
                restoreAs: user.restoreAs,
            }),
        };
    Util.setCrossDomainAjaxOptions(options);
    tfLoading();
    resp = $.ajax(options);
    resp.fail(function () {
        tfLoadingComplete(true);
    }).done(function() {
        tfLoadingComplete();
    });
});

FormplayerFrontend.on('navigateHome', function() {
    var urlObject = Util.currentUrlToObject(),
        appId,
        currentUser = FormplayerFrontend.request('currentUser');
    urlObject.clearExceptApp();
    FormplayerFrontend.regions.breadcrumb.empty();
    if (currentUser.displayOptions.singleAppMode) {
        appId = FormplayerFrontend.request('getCurrentAppId');
        FormplayerFrontend.navigate("/single_app/" + appId, { trigger: true });
    } else {
        FormplayerFrontend.navigate("/apps", { trigger: true });
    }
});
<|endoftext|>


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

 * Problem: https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/
    Given an array of integers that is already sorted in ascending order, find two numbers such that they add up to a specific target number.
	NOTE: index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based.

 * Example 1:
    Input: numbers={2, 7, 11, 15}, target=9
	Output: index1=1, index2=2

 * @param {number[]} numbers
 * @param {number} target
 * @return {number[]}

 * Analysis: Binary Search is the natural choice for sorted array.

************************************************************/

export const twoSumSorted = {};

/**
 * Solution 1: one pointer as anchor, the other area with bSearch
 *
 * "N" is sortedNums.length.
 * Time complexity: O(NlgN)
 * Space complexity: O(1)
 */
twoSumSorted.bSearch = (sortedNums, target) => {
    if (!(Array.isArray(sortedNums) && sortedNums.length > 1) || typeof target !== "number") {
        return;
    }

    let i, lo, hi, mid, compensate;

    // loop over the anchor num starting from smallest num.
    anchorEleLoop:
    for (i = 0; i < sortedNums.length - 1; i++) {
        lo = i + 1;
        hi = sortedNums.length - 1;
        compensate = target - sortedNums[i];

        // skip in vain searches
        if (compensate > sortedNums[hi]) {
            continue;
        }

        // binary search the compensate target
    	bSearchLoop:
        while (lo <= hi) {
        	// handle 1 or 2 nums on the binarySearch area's case
	        if (lo === hi) {
	            if (compensate === sortedNums[hi]) {
	                return [(i + 1), (hi + 1)];
	            } else {
	                continue anchorEleLoop;
	            }
	        } else if (lo + 1 === hi) {
	            if (compensate === sortedNums[hi]) {
	                return [(i + 1), (hi + 1)];
	            } else if (compensate === sortedNums[lo]) {
	                return [(i + 1), (lo + 1)];
	            } else {
	                continue anchorEleLoop;
	            }
	        }

            // Key is in a[lo..hi] or not present, goes to N/2 if has even nums
            mid = Math.floor((hi + lo) / 2);

            if (compensate < sortedNums[mid]) {
                hi = mid - 1;
            } else if (compensate > sortedNums[mid]) {
                lo = mid + 1;
            } else {
                return [(i + 1), (mid + 1)];
            }
        }
    }

    return;
};

/************************************************************

 * Lessons:
   1. Some edge cases of binary search needs to be handled carefully.
   2. Nested loop can be continue respectively with label, which was rarely used.

************************************************************/
<commit_msg>Added solution 2 of 2pointers for problem167.<commit_after>/************************************************************

 * Problem: https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/
    Given an array of integers that is already sorted in ascending order, find two numbers such that they add up to a specific target number.
	NOTE: index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based.

 * Example 1:
    Input: numbers={2, 7, 11, 15}, target=9
	Output: index1=1, index2=2

 * @param {number[]} numbers
 * @param {number} target
 * @return {number[]}

 * Analysis: Binary Search is the natural choice for sorted array.

************************************************************/

export const twoSumSorted = {};

/**
 * Solution 1: one pointer as anchor, the other area with bSearch
 *
 * "N" is sortedNums.length.
 * Time complexity: O(NlgN)
 * Space complexity: O(1)
 */
twoSumSorted.bSearch = (sortedNums, target) => {
    if (!(Array.isArray(sortedNums) && sortedNums.length > 1) || typeof target !== "number") {
        return;
    }

    let i, lo, hi, mid, compensate;

    // loop over the anchor num starting from smallest num.
    anchorEleLoop:
    for (i = 0; i < sortedNums.length - 1; i++) {
        lo = i + 1;
        hi = sortedNums.length - 1;
        compensate = target - sortedNums[i];

        // skip in vain searches
        if (compensate > sortedNums[hi]) {
            continue;
        }

        // binary search the compensate target
    	bSearchLoop:
        while (lo <= hi) {
        	// handle 1 or 2 nums on the binarySearch area's case
	        if (lo === hi) {
	            if (compensate === sortedNums[hi]) {
	                return [(i + 1), (hi + 1)];
	            } else {
	                continue anchorEleLoop;
	            }
	        } else if (lo + 1 === hi) {
	            if (compensate === sortedNums[hi]) {
	                return [(i + 1), (hi + 1)];
	            } else if (compensate === sortedNums[lo]) {
	                return [(i + 1), (lo + 1)];
	            } else {
	                continue anchorEleLoop;
	            }
	        }

            // Key is in a[lo..hi] or not present, goes to N/2 if has even nums
            mid = Math.floor((hi + lo) / 2);

            if (compensate < sortedNums[mid]) {
                hi = mid - 1;
            } else if (compensate > sortedNums[mid]) {
                lo = mid + 1;
            } else {
                return [(i + 1), (mid + 1)];
            }
        }
    }

    return;
};

/**
 * Solution 2: two pointers, shrink the range.
 *
 * "N" is sortedNums.length.
 * Time complexity: O(N)
 * Space complexity: O(1)
 */
twoSumSorted.twoPointer = (sortedNums, target) => {
    if (!(Array.isArray(sortedNums) && sortedNums.length > 1) || typeof target !== "number") {
        return;
    }

    let lo = 0,
        hi = sortedNums.length - 1;

    while (lo < hi) {
        if (sortedNums[lo] + sortedNums[hi] > target) {
            hi--;
        } else if (sortedNums[lo] + sortedNums[hi] < target) {
            lo++;
        } else {
            return [lo + 1, hi + 1];
        }
    }

    return;
};

/************************************************************

 * Lessons:
   1. Some edge cases of binary search needs to be handled carefully.
   2. Nested loop can be continue respectively with label, which was rarely used.

************************************************************/
<|endoftext|>


<commit_before>exports = module.exports = function() {

  function transition(req, res, next) {
    req.state.authN = req.state.authN || {};
    req.state.authN.via = req.state.authN.via || [];
    if (req.authInfo) {
      req.state.authN.via.push(req.authInfo)
    }
    return next();
  }

  function errorHandler(err, req, res, next) {
    if (req.yieldState) {
      req.state.authN = req.state.authN || {};
      req.state.authN.failureCount =  req.yieldState.failureCount;
    }

    return next(err);
  }

  return [
    transition,
    errorHandler
  ];

};

exports['@implements'] = 'http://i.bixbyjs.org/http/flows/transition';
exports['@complete'] = 'login';
exports['@continue'] = 'oauth2-authorize';
exports['@require'] = [];
<commit_msg>Implement yeilder interface.<commit_after>exports = module.exports = function() {

  function transition(req, res, next) {
    console.log('YEILD TO OAUTH2');

    req.state.authN = req.state.authN || {};
    req.state.authN.via = req.state.authN.via || [];
    if (req.authInfo) {
      req.state.authN.via.push(req.authInfo)
    }
    return next();
  }

  function errorHandler(err, req, res, next) {
    console.log('YEILD ERROR OAUTH2');

    if (req.yieldState) {
      req.state.authN = req.state.authN || {};
      req.state.authN.failureCount =  req.yieldState.failureCount;
    }

    return next(err);
  }

  return [
    transition,
    errorHandler
  ];

};

exports['@implements'] = 'http://i.bixbyjs.org/http/state/yielder';
exports['@resume'] = 'oauth2-authorize';
exports['@result'] = 'login';
exports['@require'] = [];
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2017 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import React from 'react'
import I18n from 'i18n!new_user_tutorial'
import plainStoreShape from 'jsx/shared/proptypes/plainStoreShape'
import Tray from 'instructure-ui/lib/components/Tray'
import Button from 'instructure-ui/lib/components/Button'
import NewUserTutorialToggleButton from '../NewUserTutorialToggleButton'
import ConfirmEndTutorialDialog from '../ConfirmEndTutorialDialog'

class TutorialTray extends React.Component {

  static propTypes = {
    // Used as a label for the content (screenreader-only)
    label: React.PropTypes.string.isRequired,
    // The specific tray that will be wrapped, unusable without this.
    children: React.PropTypes.node.isRequired,
    // The store to control the status of everything
    store: React.PropTypes.shape(plainStoreShape).isRequired,
    // Should return an element that focus can be set to
    returnFocusToFunc: React.PropTypes.func.isRequired
  }

  constructor (props) {
    super(props);
    this.state = {
      ...props.store.getState(),
      endUserTutorialShown: false
    };
  }

  componentDidMount () {
    this.props.store.addChangeListener(this.handleStoreChange)
  }

  componentWillUnmount () {
    this.props.store.removeChangeListener(this.handleStoreChange)
  }

  handleStoreChange = () => {
    this.setState(this.props.store.getState());
  }

  handleToggleClick = () => {
    this.props.store.setState({
      isCollapsed: !this.state.isCollapsed
    });
  }

  handleEndTutorialClick = () => {
    this.setState({
      endUserTutorialShown: true
    });
  }

  closeEndTutorialDialog = () => {
    this.setState({
      endUserTutorialShown: false
    });
    if (this.endTutorialButton) {
      this.endTutorialButton.focus();
    }
  }

  handleEntering = () => {
    this.toggleButton.focus()
  }

  handleExiting = () => {
    this.props.returnFocusToFunc().focus();
  }

  render () {
    return (
      <Tray
        label={this.props.label}
        isDismissable={false}
        isOpen={!this.state.isCollapsed}
        placement="right"
        zIndex="100"
        onEntering={this.handleEntering}
        onExiting={this.handleExiting}
        onEntered={this.handleEntering}
      >

            <NewUserTutorialToggleButton
              ref={(c) => { this.toggleButton = c; }}
              onClick={this.handleToggleClick}
              store={this.props.store}
            />

          {this.props.children}

            <Button
              onClick={this.handleEndTutorialClick}
              ref={(c) => { this.endTutorialButton = c; }}
            >
              {I18n.t('End Tutorial')}
            </Button>

          <ConfirmEndTutorialDialog
            isOpen={this.state.endUserTutorialShown}
            handleRequestClose={this.closeEndTutorialDialog}
          />

      </Tray>
    );
  }
}

export default TutorialTray
<commit_msg>Make tutorial tray focus management consistent<commit_after>/*
 * Copyright (C) 2017 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import React from 'react'
import I18n from 'i18n!new_user_tutorial'
import plainStoreShape from 'jsx/shared/proptypes/plainStoreShape'
import Tray from 'instructure-ui/lib/components/Tray'
import Button from 'instructure-ui/lib/components/Button'
import NewUserTutorialToggleButton from '../NewUserTutorialToggleButton'
import ConfirmEndTutorialDialog from '../ConfirmEndTutorialDialog'

class TutorialTray extends React.Component {

  static propTypes = {
    // Used as a label for the content (screenreader-only)
    label: React.PropTypes.string.isRequired,
    // The specific tray that will be wrapped, unusable without this.
    children: React.PropTypes.node.isRequired,
    // The store to control the status of everything
    store: React.PropTypes.shape(plainStoreShape).isRequired,
    // Should return an element that focus can be set to
    returnFocusToFunc: React.PropTypes.func.isRequired
  }

  constructor (props) {
    super(props);
    this.state = {
      ...props.store.getState(),
      endUserTutorialShown: false
    };
  }

  componentDidMount () {
    this.props.store.addChangeListener(this.handleStoreChange)
  }

  componentWillUnmount () {
    this.props.store.removeChangeListener(this.handleStoreChange)
  }

  handleStoreChange = () => {
    this.setState(this.props.store.getState());
  }

  handleToggleClick = () => {
    this.props.store.setState({
      isCollapsed: !this.state.isCollapsed
    });
  }

  handleEndTutorialClick = () => {
    this.setState({
      endUserTutorialShown: true
    });
  }

  closeEndTutorialDialog = () => {
    this.setState({
      endUserTutorialShown: false
    });
    if (this.endTutorialButton) {
      this.endTutorialButton.focus();
    }
  }

  handleEntering = () => {
    this.toggleButton.focus()
  }

  handleExiting = () => {
    this.props.returnFocusToFunc().focus();
  }

  render () {
    return (
      <Tray
        label={this.props.label}
        isDismissable={false}
        isOpen={!this.state.isCollapsed}
        placement="right"
        zIndex="100"
        onEntering={this.handleEntering}
        onExiting={this.handleExiting}
        onEntered={this.handleEntering}
        trapFocus
      >

            <NewUserTutorialToggleButton
              ref={(c) => { this.toggleButton = c; }}
              onClick={this.handleToggleClick}
              store={this.props.store}
            />

          {this.props.children}

            <Button
              onClick={this.handleEndTutorialClick}
              ref={(c) => { this.endTutorialButton = c; }}
            >
              {I18n.t('End Tutorial')}
            </Button>

          <ConfirmEndTutorialDialog
            isOpen={this.state.endUserTutorialShown}
            handleRequestClose={this.closeEndTutorialDialog}
          />

      </Tray>
    );
  }
}

export default TutorialTray
<|endoftext|>