<commit_before>'use strict';

(function() {

    /* Main functions */
    var APP = {

        template: $(document.body).data('template'),

        /* calls automagically the template init function */
        onReady: function() {

            this.initCommon();

            if(this.template) {

                var init_function = 'init' + this.template[0].toUpperCase() + this.template.substring(1);

                if(this[init_function]) {
                    this[init_function]();
                }
            }
        },

        onLoad: function() {

        },

        /* initializes shared components  */
        initCommon: function() {
            console.log('common init');
        },

        /* Template specific load functions */
        initHomepage: function() {
            console.log('homepage goodies');
        },

        initNews: function() {
            console.log('news goodies');
        }

    };

    /* Utilities and goodies */
    var Utils = {

        scrollCenterElement: function(elem) {
            var top = elem.offset()['top'];
            $(window).scrollTop(top - Math.ceil($(window).height() / 2) + Math.ceil(elem.height() / 2));
        },

        pushState: function(title, href) {

            document.title = title;

            if(!!history.pushState) {
                history.pushState('', title, href);
            } else {
                window.location.hash = '!'+href;
            }
        },

        setLoading: function() {
            $('html').addClass('loading');
        },

        removeLoading: function() {
            $('html').removeClass('loading');
        },

        openPopup: function(url, title, w, h) {

            var dualScreenLeft = window.screenLeft != undefined ? window.screenLeft : screen.left;
            var dualScreenTop = window.screenTop != undefined ? window.screenTop : screen.top;

            width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
            height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;

            var left       = ((width / 2) - (w / 2)) + dualScreenLeft,
                top        = ((height / 2) - (h / 2)) + dualScreenTop,
                new_window = window.open(url, title, 'scrollbars=yes, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left);

            if(window.focus) {
                new_window.focus();
            }
        }
    };

    /* Init awesome stuff */
    $(function() { APP.onReady(); });
    $(window).load(function() { APP.onLoad(); });

})();<commit_msg>Added APP.init function; added support for non alpha numeric chars on data-template<commit_after>'use strict';

(function() {

    /* Main functions */
    var APP = {

        template: $(document.body).data('template'),

        /* initialize before onReady */
        init: function() {

            $(function() { APP.onReady(); });
            $(window).load(function() { APP.onLoad(); });
        },

        /* calls automagically the template init function */
        onReady: function() {

            this.initCommon();

            if(this.template) {

                var template_name = this.template.toLowerCase().replace(/\W/g, ' ').split(' '),
                    function_name = '';

                for(var i = 0; i < template_name.length; i++) {
                    function_name += template_name[i][0].toUpperCase() + template_name[i].substring(1);
                }

                function_name = 'init'+function_name;

                if(this[function_name]) {
                    this[function_name]();
                }
            }
        },

        /* window.onload */
        onLoad: function() {

        },

        /* initializes shared components  */
        initCommon: function() {
            console.log('common init');
        },

        /* Template specific load functions */
        initHomepage: function() {
            console.log('homepage goodies');
        },

        initNews: function() {
            console.log('news goodies');
        }

    };

    /* Utilities and goodies */
    var Utils = {

        scrollCenterElement: function(elem) {
            var top = elem.offset()['top'];
            $(window).scrollTop(top - Math.ceil($(window).height() / 2) + Math.ceil(elem.height() / 2));
        },

        pushState: function(title, href) {

            document.title = title;

            if(!!history.pushState) {
                history.pushState('', title, href);
            } else {
                window.location.hash = '!'+href;
            }
        },

        setLoading: function() {
            $('html').addClass('loading');
        },

        removeLoading: function() {
            $('html').removeClass('loading');
        },

        openPopup: function(url, title, w, h) {

            var dualScreenLeft = window.screenLeft != undefined ? window.screenLeft : screen.left;
            var dualScreenTop = window.screenTop != undefined ? window.screenTop : screen.top;

            width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
            height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;

            var left       = ((width / 2) - (w / 2)) + dualScreenLeft,
                top        = ((height / 2) - (h / 2)) + dualScreenTop,
                new_window = window.open(url, title, 'scrollbars=yes, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left);

            if(window.focus) {
                new_window.focus();
            }
        }
    };

    /* Init awesome stuff */
    APP.init();

})();<|endoftext|>


<commit_before>/*!
    Copyright (C) 2015 Google Inc., authors, and contributors <see AUTHORS file>
    Licensed under http://www.apache.org/licenses/LICENSE-2.0 <see LICENSE file>
    Created By: ivan@reciprocitylabs.com
    Maintained By: ivan@reciprocitylabs.com
*/
(function($) {
  var MAX_RESULTS = 20;
  $.widget(
    "ggrc.autocomplete",
    $.ui.autocomplete,
    {
      options: {
        // Ensure that the input.change event still occurs
        change: function(event, ui) {
          if (!$(event.target).parents(document.body).length)
            console.warn("autocomplete menu change event is coming from detached nodes");
          $(event.target).trigger("change");
        },

        minLength: 0,

        source: function(request, response) {
          // Search based on the term
          var query = request.term || '',
            queue = new RefreshQueue(),
            that = this,
            is_next_page = request.start != null,
            dfd;

          if (query.indexOf('@') > -1)
            query = '"' + query + '"';

          this.last_request = request;
          if (is_next_page) {
            dfd = $.when(this.last_stubs);
          } else {
            request.start = 0;
            dfd = this.options.source_for_refreshable_objects.call(this, request);
          }

          dfd.then(function(objects) {
            that.last_stubs = objects;
            can.each(objects.slice(request.start, request.start + MAX_RESULTS), function(object) {
              queue.enqueue(object);
            });
            queue.trigger().then(function(objs) {
              objs = that.options.apply_filter.call(that, objs, request);
              if (objs.length || is_next_page) {
                // Envelope the object to not break model instance due to
                // shallow copy done by jQuery in `response()`
                objs = can.map(objs, function(obj) {
                  return {
                    item: obj
                  };
                });
                response(objs);
              } else {
                // show the no-results option iff no results come through here,
                //  and not merely showing paging.
                that._suggest([]);
                that._trigger("open");
              }
            });
          });

          if (this.options.controller) {
          this.options.controller.bindXHRToButton(dfd,
            $(this.element), null, false);
          }
        },

        apply_filter: function(objects) {
          return objects;
        },

        source_for_refreshable_objects: function(request) {
          var that = this;

          if (this.options.searchlist) {
            return this.options.searchlist.then(function() {
              var filtered_list = [];
              return $.map(arguments, function(item) {
                if (!item) {
                  return;
                }
                var search_attr = item.title || "",
                  term = request.term.toLowerCase();

                // Filter out duplicates:
                if (filtered_list.indexOf(item._cid) > -1) {
                  return;
                }
                filtered_list.push(item._cid);

                // Perform search based on term:
                if (search_attr.toLowerCase().indexOf(term) === -1) {
                  return;
                }
                return item;
              });
            });
          }

          return GGRC.Models.Search
            .search_for_types(
              request.term || '',
              this.options.searchtypes,
              this.options.search_params
            )
            .then(function(search_result) {
              var objects = [];

              can.each(that.options.searchtypes, function(searchtype) {
                objects.push.apply(
                  objects, search_result.getResultsForType(searchtype));
              });
              return objects;
            });
        },

        select: function(ev, ui) {
          var original_event,
              $this = $(this),
              ctl = $this.data($this.data("autocomplete-widget-name")).options.controller;

          if (ui.item) {
            $this.trigger("autocomplete:select", [ui]);
            if (ctl) {
              if (ctl.scope && ctl.scope.autocomplete_select) {
                return ctl.scope.autocomplete_select($this, ev, ui);
              } else if (ctl.autocomplete_select) {
                return ctl.autocomplete_select($this, ev, ui);
              }
            }
          } else {
            original_event = ev;
            $(document.body).off(".autocomplete").one("modal:success.autocomplete", function(_ev, new_obj) {
              if (ctl) {
                if (ctl.scope && ctl.scope.autocomplete_select) {
                  return ctl.scope.autocomplete_select(
                    $this, original_event, {item: new_obj});
                } else if (ctl.autocomplete_select) {
                  return ctl.autocomplete_select(
                    $this, original_event, {item: new_obj});
                }
              }
              $this.trigger("autocomplete:select", [{
                item: new_obj
              }]);
              $this.trigger("modal:success", new_obj);
            }).one("hidden", function() {
              setTimeout(function() {
                $(this).off(".autocomplete");
              }, 100);
            });
            while (original_event = original_event.originalEvent) {
              if (original_event.type === "keydown") {
                //This selection event was generated from a keydown, so click the add new link.
                var widget_name = el.data("autocompleteWidgetName");
                el.data(widget_name).menu.active.find("a").click();
                break;
              }
            }
            return false;
          }
        },

        close: function() {
          delete this.scroll_op_in_progress;
          //$that.val($that.attr("value"));
        }
      },

      _create: function() {
        var that = this,
          $that = $(this.element),
          base_search = $that.data("lookup"),
          from_list = $that.data("from-list"),
          search_params = $that.data("params"),
          permission = $that.data("permission-type"),
          searchtypes;

        this._super.apply(this, arguments);
        this.options.search_params = {
          extra_params: search_params
        };
        if (permission) {
          this.options.search_params.__permission_type = permission;
        }

        $that.data("autocomplete-widget-name", this.widgetFullName);

        $that.focus(function() {
          $(this).data(that.widgetFullName).search($(this).val());
        });

        if (from_list) {
          this.options.searchlist = $.when.apply(this, $.map(from_list.list, function(item) {
            var props = base_search.trim().split('.');
            return item.instance.refresh_all.apply(item.instance, props);
          }));
        } else if (base_search) {
          base_search = base_search.trim();
          if (base_search.indexOf("__mappable") === 0 || base_search.indexOf("__all") === 0) {
            searchtypes = GGRC.Mappings.get_canonical_mappings_for(
              this.options.parent_instance.constructor.shortName
            );
            if (base_search.indexOf("__mappable") === 0) {
              searchtypes = can.map(searchtypes, function(mapping) {
                return mapping instanceof GGRC.ListLoaders.ProxyListLoader ? mapping : undefined;
              });
            }
            if (base_search.indexOf("_except:")) {
              can.each(base_search.substr(base_search.indexOf("_except:") + 8).split(","), function(remove) {
                delete searchtypes[remove];
              });
            }
            searchtypes = Object.keys(searchtypes);
          } else {
            searchtypes = base_search.split(",");
          }

          this.options.searchtypes = can.map(searchtypes, function(t) {
            return CMS.Models[t].model_singular;
          });
        }
      },

      _setup_menu_context: function(items) {
        var model_class = this.element.data("lookup"),

          model = CMS.Models[model_class || this.element.data("model")]
            || GGRC.Models[model_class || this.element.data("model")]
        ;

        return {
          model_class: model_class,
          model: model,
          // Reverse the enveloping we did 25 lines up
          items: can.map(items, function(item) {
            return item.item;
          }),
        };
      },

      _renderMenu: function(ul, items) {
        var template = this.element.data("template"),
          context = new can.Observe(this._setup_menu_context(items)),
          model = context.model,
          that = this,
          $ul = $(ul)
        ;

        if (!template) {
          if (model && GGRC.Templates[model.table_plural + "/autocomplete_result"]) {
            template = '/' + model.table_plural + '/autocomplete_result.mustache';
          } else {
            template = '/base_objects/autocomplete_result.mustache';
          }
        }

        $ul.unbind("scrollNext")
          .bind("scrollNext", function(ev, data) {
            if (that.scroll_op_in_progress) {
              return;
            }
            that.scroll_op_in_progress = true;
            that.last_request = that.last_request || {};
            that.last_request.start = that.last_request.start || 0;
            that.last_request.start += MAX_RESULTS;
            context.attr("items_loading", true);
            that.source(that.last_request, function(items) {
              context.items.push.apply(context.items, can.map(items, function(item) {
                return item.item;
              }));
              context.removeAttr("items_loading");
              setTimeout(function() {
                delete that.scroll_op_in_progress;
              }, 10);
            });
          });

        can.view.render(
          GGRC.mustache_path + template,
          context,
          function(frag) {
            $ul.html(frag);
            $ul.cms_controllers_lhn_tooltips().cms_controllers_infinite_scroll();
            can.view.hookup(ul);
          });
      }
    });
  $.widget.bridge("ggrc_autocomplete", $.ggrc.autocomplete);

  $.widget("ggrc.mapping_autocomplete", $.ggrc.autocomplete, {
    options: {
      source_for_refreshable_objects: function(request) {
        var $el = $(this.element),
          mapping = this.options.controller.options;

        if (mapping.scope) {
          mapping = mapping.scope.source_mapping;
        } else {
          mapping = inst.source_mapping;
        }

        return $.when(can.map(mapping, function(binding) {
          return binding.instance;
        }));
      },
      apply_filter: function(objects, request) {
        return can.map(objects, function(object) {
          if (!request.term || object.title && ~object.title.indexOf(request.term))
            return object;
          else
            return undefined;
        });
      }
    },
    _setup_menu_context: function(items) {
      return $.extend(this._super(items), {
        mapping: this.options.mapping == null ? this.element.data("mapping") : this.options.mapping
      });
    }
  });
  $.widget.bridge("ggrc_mapping_autocomplete", $.ggrc.mapping_autocomplete);

  $.cms_autocomplete = function(el) {
    var ctl = this;
    // Add autocomplete to the owner field
    ($(el) || this.element.find('input[data-lookup]'))
      .filter("[name][name!='']")
      .ggrc_autocomplete({
        controller: ctl
      });
  };
})(jQuery);
<commit_msg>Lint autocomplete plugin<commit_after>/*!
    Copyright (C) 2015 Google Inc., authors, and contributors <see AUTHORS file>
    Licensed under http://www.apache.org/licenses/LICENSE-2.0 <see LICENSE file>
    Created By: ivan@reciprocitylabs.com
    Maintained By: ivan@reciprocitylabs.com
*/
(function ($) {
  'use strict';
  var MAX_RESULTS = 20;

  $.widget('ggrc.autocomplete', $.ui.autocomplete, {
    options: {
      // Ensure that the input.change event still occurs
      change: function (event, ui) {
        if (!$(event.target).parents(document.body).length) {
          console.warn('autocomplete menu change event is coming from detached nodes');
        }
        $(event.target).trigger('change');
      },
      minLength: 0,
      source: function (request, response) {
        // Search based on the term
        var query = request.term || '';
        var queue = new RefreshQueue();
        var isNextPage = _.isNumber(request.start);
        var dfd;

        if (query.indexOf('@') > -1) {
          query = '"' + query + '"';
        }

        this.last_request = request;
        if (isNextPage) {
          dfd = $.when(this.last_stubs);
        } else {
          request.start = 0;
          dfd = this.options.source_for_refreshable_objects.call(this, request);
        }

        dfd.then(function (objects) {
          this.last_stubs = objects;
          can.each(objects.slice(request.start, request.start + MAX_RESULTS), function (object) {
            queue.enqueue(object);
          });
          queue.trigger().then(function (objs) {
            objs = this.options.apply_filter(objs, request);
            if (objs.length || isNextPage) {
              // Envelope the object to not break model instance due to
              // shallow copy done by jQuery in `response()`
              objs = can.map(objs, function (obj) {
                return {
                  item: obj
                };
              });
              response(objs);
            } else {
              // show the no-results option iff no results come through here,
              //  and not merely showing paging.
              this._suggest([]);
              this._trigger('open');
            }
          }.bind(this));
        }.bind(this));

        if (this.options.controller) {
          this.options.controller.bindXHRToButton(dfd,
            $(this.element), null, false);
        }
      },

      apply_filter: function (objects) {
        return objects;
      },

      source_for_refreshable_objects: function (request) {
        var that = this;

        if (this.options.searchlist) {
          this.options.searchlist.then(function () {
            var filteredList = [];
            return $.map(arguments, function (item) {
              var searchAttr;
              var term;
              if (!item) {
                return;
              }
              searchAttr = item.title || '';
              term = request.term.toLowerCase();

              // Filter out duplicates:
              if (filteredList.indexOf(item._cid) > -1) {
                return;
              }
              filteredList.push(item._cid);

              // Perform search based on term:
              if (searchAttr.toLowerCase().indexOf(term) === -1) {
                return;
              }
              return item;
            });
          });
          return this.options.searchlist;
        }

        return GGRC.Models.Search.search_for_types(
          request.term || '',
          this.options.searchtypes,
          this.options.search_params
        )
        .then(function (searchResult) {
          var objects = [];

          can.each(that.options.searchtypes, function (searchtype) {
            objects.push.apply(objects,
              searchResult.getResultsForType(searchtype));
          });
          return objects;
        });
      },

      select: function (ev, ui) {
        var original_event;
        var $this = $(this);
        var widgetName = $this.data('autocomplete-widget-name');
        var ctl = $this.data(widgetName).options.controller;
        var widget_name;

        if (ui.item) {
          $this.trigger('autocomplete:select', [ui]);
          if (ctl) {
            if (ctl.scope && ctl.scope.autocomplete_select) {
              return ctl.scope.autocomplete_select($this, ev, ui);
            } else if (ctl.autocomplete_select) {
              return ctl.autocomplete_select($this, ev, ui);
            }
          }
        } else {
          original_event = ev;
          $(document.body)
            .off('.autocomplete')
            .one('modal:success.autocomplete', function (_ev, new_obj) {
              if (ctl) {
                if (ctl.scope && ctl.scope.autocomplete_select) {
                  return ctl.scope.autocomplete_select(
                    $this, original_event, {item: new_obj});
                } else if (ctl.autocomplete_select) {
                  return ctl.autocomplete_select(
                    $this, original_event, {item: new_obj});
                }
              }
              $this.trigger('autocomplete:select', [{
                item: new_obj
              }]);
              $this.trigger('modal:success', new_obj);
            })
            .one('hidden', function () {
              setTimeout(function () {
                $(this).off('.autocomplete');
              }, 100);
            });

          while ((original_event = original_event.originalEvent)) {
            if (original_event.type === 'keydown') {
              // This selection event was generated from a keydown, so click the add new link.
              widget_name = el.data('autocompleteWidgetName');
              el.data(widget_name).menu.active.find('a').click();
              break;
            }
          }
          return false;
        }
      },

      close: function () {
        this.scroll_op_in_progress = undefined;
      }
    },
    _create: function () {
      var that = this;
      var $that = $(this.element);
      var base_search = $that.data('lookup');
      var from_list = $that.data('from-list');
      var search_params = $that.data('params');
      var permission = $that.data('permission-type');
      var searchtypes;

      this._super.apply(this, arguments);
      this.options.search_params = {
        extra_params: search_params
      };
      if (permission) {
        this.options.search_params.__permission_type = permission;
      }

      $that.data('autocomplete-widget-name', this.widgetFullName);

      $that.focus(function () {
        $(this).data(that.widgetFullName).search($(this).val());
      });

      if (from_list) {
        this.options.searchlist = $.when.apply(this, $.map(from_list.list, function (item) {
          var props = base_search.trim().split('.');
          return item.instance.refresh_all.apply(item.instance, props);
        }));
      } else if (base_search) {
        base_search = base_search.trim();
        if (base_search.indexOf('__mappable') === 0 || base_search.indexOf('__all') === 0) {
          searchtypes = GGRC.Mappings.get_canonical_mappings_for(
            this.options.parent_instance.constructor.shortName
          );
          if (base_search.indexOf('__mappable') === 0) {
            searchtypes = can.map(searchtypes, function (mapping) {
              return mapping instanceof GGRC.ListLoaders.ProxyListLoader ? mapping : undefined;
            });
          }
          if (base_search.indexOf('_except:')) {
            can.each(base_search.substr(base_search.indexOf('_except:') + 8).split(','), function (remove) {
              delete searchtypes[remove];
            });
          }
          searchtypes = Object.keys(searchtypes);
        } else {
          searchtypes = base_search.split(',');
        }

        this.options.searchtypes = can.map(searchtypes, function (t) {
          return CMS.Models[t].model_singular;
        });
      }
    },

    _setup_menu_context: function (items) {
      var modelClass = this.element.data('lookup');
      var dataModel = this.element.data('model');
      var model = CMS.Models[modelClass || dataModel] ||
                  GGRC.Models[modelClass || dataModel];

      return {
        model_class: modelClass,
        model: model,
        // Reverse the enveloping we did 25 lines up
        items: can.map(items, function (item) {
          return item.item;
        })
      };
    },

    _renderMenu: function (ul, items) {
      var template = this.element.data('template');
      var context = new can.Observe(this._setup_menu_context(items));
      var model = context.model;
      var $ul = $(ul);

      if (!template) {
        if (model && GGRC.Templates[model.table_plural + '/autocomplete_result']) {
          template = '/' + model.table_plural + '/autocomplete_result.mustache';
        } else {
          template = '/base_objects/autocomplete_result.mustache';
        }
      }

      $ul.unbind('scrollNext')
        .bind('scrollNext', function (ev, data) {
          if (this.scroll_op_in_progress) {
            return;
          }
          this.scroll_op_in_progress = true;
          this.last_request = this.last_request || {};
          this.last_request.start = this.last_request.start || 0;
          this.last_request.start += MAX_RESULTS;
          context.attr('items_loading', true);
          this.source(this.last_request, function (items) {
            context.items.push.apply(context.items, can.map(items, function (item) {
              return item.item;
            }));
            context.removeAttr('items_loading');
            setTimeout(function () {
              this.scroll_op_in_progress = undefined;
            }.bind(this), 10);
          });
        }.bind(this));

      can.view.render(GGRC.mustache_path + template,
        context, function (frag) {
          $ul.html(frag);
          $ul.cms_controllers_lhn_tooltips().cms_controllers_infinite_scroll();
          can.view.hookup(ul);
        });
    }
  });

  $.widget.bridge('ggrc_autocomplete', $.ggrc.autocomplete);
  $.widget('ggrc.mapping_autocomplete', $.ggrc.autocomplete, {
    options: {
      source_for_refreshable_objects: function (request) {
        var mapping = this.options.controller.options;

        if (mapping.scope) {
          mapping = mapping.scope.source_mapping;
        }

        return $.when(can.map(mapping || [], function (binding) {
          return binding.instance;
        }));
      },
      apply_filter: function (objects, request) {
        return can.map(objects, function (object) {
          if (!request.term || object.title && ~object.title.indexOf(request.term)) {
            return object;
          }
          return undefined;
        });
      }
    },
    _setup_menu_context: function (items) {
      return $.extend(this._super(items), {
        mapping: _.isUndefined(this.options.mapping) ? this.element.data('mapping') : this.options.mapping
      });
    }
  });

  $.widget.bridge('ggrc_mapping_autocomplete', $.ggrc.mapping_autocomplete);

  $.cms_autocomplete = function (el) {
    var ctl = this;
    // Add autocomplete to the owner field
    if (!el) {
      el = this.element.find('input[data-lookup]');
    } else {
      el = $(el);
    }
    el.filter("[name][name!='']")
      .ggrc_autocomplete({
        controller: ctl
      });
  };
})(jQuery);
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import _ from 'lodash';

import {
  colors,
  typography,
  renderThemeIfPresentOrDefault,
  renderThemeKeyOrDefaultValue,
} from '../styles';
import Icons from '../icons';

import SelectInputLabel from './SelectInputLabel';
import { OverflowWrapper } from './SelectInputThemes';
import { SelectOptionHeight } from './SelectOptions';

const Input = styled.div`
  width: ${(props) => {
    if (props.theme.inputWidth) {
      return props.theme.inputWidth;
    }
    return '100%';
  }};
  background: ${(props) => {
    if (props.theme.background) {
      return renderThemeKeyOrDefaultValue({ props, key: 'noValue', defaultValue: props.theme.background });
    }

    return renderThemeKeyOrDefaultValue({ props, key: 'primary05', defaultValue: colors.white10 });
  }};
  border-radius: ${(props) => {
    if (props.theme.borderRadius) {
      return `${props.theme.borderRadius}px`
    }
    return '2px';
  }};
  box-sizing: border-box;
  border-bottom: ${(props) => {
    if (props.theme.borderColor) {
      return `1px solid ${renderThemeKeyOrDefaultValue({ props, key: 'white10', defaultValue: props.theme.borderColor })}`;
    }

    return 0;
  }};

  padding-left: ${(props) => {
    if (props.theme.noLeftPadding) return `${0}px`;
    return '10px';
  }};

  padding-right: ${(props) => {
    if (props.theme.inputPaddingRight) return `${props.theme.inputPaddingRight}px`;
    return '10px';
  }};

  color: ${(props) => {
    if (!props.isDisabled && props.theme.textColor) return renderThemeKeyOrDefaultValue({ props, key: 'white60', defaultValue: props.theme.textColor });
    if (!props.isDisabled) return renderThemeKeyOrDefaultValue({ props, key: 'white90', defaultValue: props.theme.inputColor });
    return renderThemeKeyOrDefaultValue({ props, key: 'white40', defaultValue: props.theme.disabledInputColor });
  }};
  cursor: ${(props) => {
    if (!props.isDisabled) return 'pointer';
    return 'default';
  }};

  display: flex;
  justify-content: ${(props) => {
    if (props.selectArrowFollows) return 'flex-start';
    return 'space-between';
  }};
  align-items: center;

  height: ${(props) => {
    if (props.theme.optionHeight === 'auto') return 'auto';
    if (props.theme.optionHeight) return `${props.theme.optionHeight}px`;
    return `${SelectOptionHeight}px`;
  }};

  min-height: ${(props) => {
    if (props.theme.optionMinHeight) return `${props.theme.optionMinHeight}px`;
    return 0;
  }};

  transition: ${(props) => {
    if (props.theme.transition) {
      return props.theme.transition;
    }
    return 'background .25s ease-in-out';
  }};

  &:focus {
    color: ${(props) => {
      if (props.isDisabled && props.theme.disabledInputColor) {
        return renderThemeKeyOrDefaultValue({ props, key: 'white90', defaultValue: props.theme.disabledInputColor });
      } else if (props.theme.focusTextColor) {
        return renderThemeKeyOrDefaultValue({ props, key: 'white90', defaultValue: props.theme.focusTextColor });
      }
    }};

    .input-carat {
      border-top: ${(props) => {
        if (props.isDisabled && props.theme.disabledSelectArrowColor) return `5px solid ${renderThemeKeyOrDefaultValue({ props, key: 'brand01', defaultValue: props.theme.disabledSelectArrowColor })}`;
        else if (props.theme.focusSelectArrowColor) {
          return `5px solid ${renderThemeKeyOrDefaultValue({ props, key: 'brand01', defaultValue: props.theme.focusSelectArrowColor })}`;
        }
      }};
    }
  }

  &:hover {
    background: ${(props) => {
      if (props.theme.background) {
        return renderThemeKeyOrDefaultValue({ props, key: '?', defaultValue: props.theme.background });
      }

      return renderThemeKeyOrDefaultValue({ props, key: '?', defaultValue: colors.white20 })
    }};

    color: ${(props) => {
      if (props.isDisabled && props.theme.disabledInputColor) {
        return renderThemeKeyOrDefaultValue({ props, key: 'white40', defaultValue: props.theme.disabledInputColor });
      } else if (props.theme.hoverTextColor) {
        return renderThemeKeyOrDefaultValue({ props, key: 'white60', defaultValue: props.theme.hoverTextColor });
      }
    }};

    .input-carat {
      border-top: ${(props) => {
        if (props.isDisabled && props.theme.disabledSelectArrowColor) return `5px solid ${renderThemeKeyOrDefaultValue({ props, key: 'white90', defaultValue: props.theme.disabledSelectArrowColor })}`;
        else if (props.theme.hoverSelectArrowColor) {
          return `5px solid ${renderThemeKeyOrDefaultValue({ props, key: 'white90', defaultValue: props.theme.hoverSelectArrowColor })}`;
        }
      }};
    }
  }
  &:focus {
    box-shadow: ${(props) => {
      if (!props.theme) {
        return `inset 0 1px 1px transparent, 0 0 5px ${renderThemeIfPresentOrDefault({ key: 'black40', defaultValue: colors.white60 })}`;
      }
    }};
    outline: none;
  }
`;

const Carat = styled.div`
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: ${(props) => {
    if (props.isDisabled && props.theme.disabledSelectArrowColor) return `5px solid ${renderThemeKeyOrDefaultValue({ props, key: 'white60', defaultValue: props.theme.disabledSelectArrowColor })}`;
    else if (props.theme.iconColor) return `5px solid ${renderThemeKeyOrDefaultValue({ props, key: 'white40', defaultValue: props.theme.iconColor })}`;
    else if (props.theme.selectArrowColor) return `5px solid ${renderThemeKeyOrDefaultValue({ props, key: 'white60', defaultValue: props.theme.selectArrowColor })}`;
  }};

  position: ${(props) => {
    if (props.theme.iconPosition) return props.theme.iconPosition;
    return 'relative';
  }};
  margin-left: ${(props) => {
    if (props.theme.iconMargin) return props.theme.iconMargin;
    if (props.selectArrowFollows) return '11px';
    return '0px';
  }};
  display: ${(props) => {
    if (!props.noCarat && (!props.isDisabled || props.theme.caratVisibleWhenDisabled)) return 'block';
    return 'none';
  }};
  flex-shrink: 0;
  right: ${(props) => {
    if (props.theme.iconRightPosition) return props.theme.iconRightPosition;
    return 'unset';
  }};
  transition: ${(props) => {
    if (props.theme.caratTransition) {
      return props.theme.caratTransition;
    }
  }};
`;

const Label = styled.div`
  margin: 0;
  width: 100%;
  padding-right: ${(props) => {
    if (props.theme.textPaddingRight) return props.theme.textPaddingRight;
    return 0;
  }};
  padding-left: ${(props) => {
    return props.theme.textPaddingLeft || 0;
  }};
  text-transform: ${(props) => {
    if (props.theme.textTransform) return props.theme.textTransform;
    return 'inherit';
  }};

  vertical-align: middle;

  ${(props) => {
    if (props.theme.textTypography) {
      return props.theme.textTypography;
    }
    return typography.subhead1;
  }};
`;

const AddButton = styled.div`
  ${typography.body2};
  color: ${renderThemeIfPresentOrDefault({ key: 'brand01', defaultValue: colors.green })};
  padding-left: 32px;
  position: relative;

  svg {
    position: absolute;
    left: 0;
  }
`;

const LabelWrapper = styled.div`
  width: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
`;

class SelectInputDisplay extends React.Component {
  render() {
    const { onClick, label, isDisabled, selectArrowFollows, noCarat, defaultLabel } = this.props;
    const clonedLabel = typeof (label) === 'string' ? <OverflowWrapper>{label}</OverflowWrapper> : React.cloneElement(label, { isClickable: true, allowOverflow: true });

    return (

        {this.props.addButtonList &&
          <AddButton><Icons.AddCircleIcon fill={colors.green} size={{ width: 24, height: 24 }} /> {defaultLabel}</AddButton>
        }
        {!this.props.addButtonList &&
          <Input
            id="select-input__input"
            onClick={onClick}
            tabIndex={(isDisabled) ? -1 : 0}
            selectArrowFollows={selectArrowFollows}
            isDisabled={isDisabled}
          >
            <LabelWrapper>
              {this.props.headerLabel && !this.props.addButtonList &&
                <SelectInputLabel>{this.props.headerLabel}</SelectInputLabel>
              }
              {clonedLabel}
            </LabelWrapper>
            <Carat isDisabled={isDisabled} selectArrowFollows={selectArrowFollows} noCarat={noCarat} className="input-carat" />
          </Input>
        }

    );
  }
}

SelectInputDisplay.propTypes = {
  label: PropTypes.any.isRequired,
  onClick: PropTypes.func.isRequired,
  headerLabel: PropTypes.any,
  value: PropTypes.string
};

SelectInputDisplay.defaultProps = {
  label: 'Select',
  headerLabel: '',
  onClick: () => {},
  value: 'select',
};

export default SelectInputDisplay;
<commit_msg>fix(SelectInputDisplay): Add class name  (#295)<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import _ from 'lodash';

import {
  colors,
  typography,
  renderThemeIfPresentOrDefault,
  renderThemeKeyOrDefaultValue,
} from '../styles';
import Icons from '../icons';

import SelectInputLabel from './SelectInputLabel';
import { OverflowWrapper } from './SelectInputThemes';
import { SelectOptionHeight } from './SelectOptions';

const Input = styled.div`
  width: ${(props) => {
    if (props.theme.inputWidth) {
      return props.theme.inputWidth;
    }
    return '100%';
  }};
  background: ${(props) => {
    if (props.theme.background) {
      return renderThemeKeyOrDefaultValue({ props, key: 'noValue', defaultValue: props.theme.background });
    }

    return renderThemeKeyOrDefaultValue({ props, key: 'primary05', defaultValue: colors.white10 });
  }};
  border-radius: ${(props) => {
    if (props.theme.borderRadius) {
      return `${props.theme.borderRadius}px`
    }
    return '2px';
  }};
  box-sizing: border-box;
  border-bottom: ${(props) => {
    if (props.theme.borderColor) {
      return `1px solid ${renderThemeKeyOrDefaultValue({ props, key: 'white10', defaultValue: props.theme.borderColor })}`;
    }

    return 0;
  }};

  padding-left: ${(props) => {
    if (props.theme.noLeftPadding) return `${0}px`;
    return '10px';
  }};

  padding-right: ${(props) => {
    if (props.theme.inputPaddingRight) return `${props.theme.inputPaddingRight}px`;
    return '10px';
  }};

  color: ${(props) => {
    if (!props.isDisabled && props.theme.textColor) return renderThemeKeyOrDefaultValue({ props, key: 'white60', defaultValue: props.theme.textColor });
    if (!props.isDisabled) return renderThemeKeyOrDefaultValue({ props, key: 'white90', defaultValue: props.theme.inputColor });
    return renderThemeKeyOrDefaultValue({ props, key: 'white40', defaultValue: props.theme.disabledInputColor });
  }};
  cursor: ${(props) => {
    if (!props.isDisabled) return 'pointer';
    return 'default';
  }};

  display: flex;
  justify-content: ${(props) => {
    if (props.selectArrowFollows) return 'flex-start';
    return 'space-between';
  }};
  align-items: center;

  height: ${(props) => {
    if (props.theme.optionHeight === 'auto') return 'auto';
    if (props.theme.optionHeight) return `${props.theme.optionHeight}px`;
    return `${SelectOptionHeight}px`;
  }};

  min-height: ${(props) => {
    if (props.theme.optionMinHeight) return `${props.theme.optionMinHeight}px`;
    return 0;
  }};

  transition: ${(props) => {
    if (props.theme.transition) {
      return props.theme.transition;
    }
    return 'background .25s ease-in-out';
  }};

  &:focus {
    color: ${(props) => {
      if (props.isDisabled && props.theme.disabledInputColor) {
        return renderThemeKeyOrDefaultValue({ props, key: 'white90', defaultValue: props.theme.disabledInputColor });
      } else if (props.theme.focusTextColor) {
        return renderThemeKeyOrDefaultValue({ props, key: 'white90', defaultValue: props.theme.focusTextColor });
      }
    }};

    .input-carat {
      border-top: ${(props) => {
        if (props.isDisabled && props.theme.disabledSelectArrowColor) return `5px solid ${renderThemeKeyOrDefaultValue({ props, key: 'brand01', defaultValue: props.theme.disabledSelectArrowColor })}`;
        else if (props.theme.focusSelectArrowColor) {
          return `5px solid ${renderThemeKeyOrDefaultValue({ props, key: 'brand01', defaultValue: props.theme.focusSelectArrowColor })}`;
        }
      }};
    }
  }

  &:hover {
    background: ${(props) => {
      if (props.theme.background) {
        return renderThemeKeyOrDefaultValue({ props, key: '?', defaultValue: props.theme.background });
      }

      return renderThemeKeyOrDefaultValue({ props, key: '?', defaultValue: colors.white20 })
    }};

    color: ${(props) => {
      if (props.isDisabled && props.theme.disabledInputColor) {
        return renderThemeKeyOrDefaultValue({ props, key: 'white40', defaultValue: props.theme.disabledInputColor });
      } else if (props.theme.hoverTextColor) {
        return renderThemeKeyOrDefaultValue({ props, key: 'white60', defaultValue: props.theme.hoverTextColor });
      }
    }};

    .input-carat {
      border-top: ${(props) => {
        if (props.isDisabled && props.theme.disabledSelectArrowColor) return `5px solid ${renderThemeKeyOrDefaultValue({ props, key: 'white90', defaultValue: props.theme.disabledSelectArrowColor })}`;
        else if (props.theme.hoverSelectArrowColor) {
          return `5px solid ${renderThemeKeyOrDefaultValue({ props, key: 'white90', defaultValue: props.theme.hoverSelectArrowColor })}`;
        }
      }};
    }
  }
  &:focus {
    box-shadow: ${(props) => {
      if (!props.theme) {
        return `inset 0 1px 1px transparent, 0 0 5px ${renderThemeIfPresentOrDefault({ key: 'black40', defaultValue: colors.white60 })}`;
      }
    }};
    outline: none;
  }
`;

const Carat = styled.div`
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: ${(props) => {
    if (props.isDisabled && props.theme.disabledSelectArrowColor) return `5px solid ${renderThemeKeyOrDefaultValue({ props, key: 'white60', defaultValue: props.theme.disabledSelectArrowColor })}`;
    else if (props.theme.iconColor) return `5px solid ${renderThemeKeyOrDefaultValue({ props, key: 'white40', defaultValue: props.theme.iconColor })}`;
    else if (props.theme.selectArrowColor) return `5px solid ${renderThemeKeyOrDefaultValue({ props, key: 'white60', defaultValue: props.theme.selectArrowColor })}`;
  }};

  position: ${(props) => {
    if (props.theme.iconPosition) return props.theme.iconPosition;
    return 'relative';
  }};
  margin-left: ${(props) => {
    if (props.theme.iconMargin) return props.theme.iconMargin;
    if (props.selectArrowFollows) return '11px';
    return '0px';
  }};
  display: ${(props) => {
    if (!props.noCarat && (!props.isDisabled || props.theme.caratVisibleWhenDisabled)) return 'block';
    return 'none';
  }};
  flex-shrink: 0;
  right: ${(props) => {
    if (props.theme.iconRightPosition) return props.theme.iconRightPosition;
    return 'unset';
  }};
  transition: ${(props) => {
    if (props.theme.caratTransition) {
      return props.theme.caratTransition;
    }
  }};
`;

const Label = styled.div`
  margin: 0;
  width: 100%;
  padding-right: ${(props) => {
    if (props.theme.textPaddingRight) return props.theme.textPaddingRight;
    return 0;
  }};
  padding-left: ${(props) => {
    return props.theme.textPaddingLeft || 0;
  }};
  text-transform: ${(props) => {
    if (props.theme.textTransform) return props.theme.textTransform;
    return 'inherit';
  }};

  vertical-align: middle;

  ${(props) => {
    if (props.theme.textTypography) {
      return props.theme.textTypography;
    }
    return typography.subhead1;
  }};
`;

const AddButton = styled.div`
  ${typography.body2};
  color: ${renderThemeIfPresentOrDefault({ key: 'brand01', defaultValue: colors.green })};
  padding-left: 32px;
  position: relative;

  svg {
    position: absolute;
    left: 0;
  }
`;

const LabelWrapper = styled.div`
  width: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
`;

class SelectInputDisplay extends React.Component {
  render() {
    const { onClick, label, isDisabled, selectArrowFollows, noCarat, defaultLabel } = this.props;
    const clonedLabel = typeof (label) === 'string' ? <OverflowWrapper>{label}</OverflowWrapper> : React.cloneElement(label, { isClickable: true, allowOverflow: true });

    return (

        {this.props.addButtonList &&
          <AddButton><Icons.AddCircleIcon fill={colors.green} size={{ width: 24, height: 24 }} /> {defaultLabel}</AddButton>
        }
        {!this.props.addButtonList &&
          <Input
            id="select-input__input"
            onClick={onClick}
            tabIndex={(isDisabled) ? -1 : 0}
            selectArrowFollows={selectArrowFollows}
            isDisabled={isDisabled}
          >
            <LabelWrapper>
              {this.props.headerLabel && !this.props.addButtonList &&
                <SelectInputLabel>{this.props.headerLabel}</SelectInputLabel>
              }
              {clonedLabel}
            </LabelWrapper>
            <Carat isDisabled={isDisabled} selectArrowFollows={selectArrowFollows} noCarat={noCarat} className="input-carat" />
          </Input>
        }

    );
  }
}

SelectInputDisplay.propTypes = {
  label: PropTypes.any.isRequired,
  onClick: PropTypes.func.isRequired,
  headerLabel: PropTypes.any,
  value: PropTypes.string
};

SelectInputDisplay.defaultProps = {
  label: 'Select',
  headerLabel: '',
  onClick: () => {},
  value: 'select',
};

export default SelectInputDisplay;
<|endoftext|>


<commit_before>// This scheme was built on the assumption that users log in EACH TIME manually, and that's patently untrue

import {
    take,
    call,
    put,
    cancelled,
    cancel,
    fork,
    apply,
    select,
} from 'redux-saga/effects';
import { Alert, Platform } from 'react-native';
import { GoogleSignin } from 'react-native-google-signin';

import {
    STORE_INITIALIZED,
    LOGIN_REQUEST,
    LOGOUT,
    CLEAR_TOKENS,
    REAUTHENTICATE_REQUEST,
} from 'app/ActionTypes';
import API from 'app/services/API';
import * as AuthSelectors from 'app/redux/selectors/AuthSelectors';
import * as AuthActionCreators from 'app/redux/shared_actions/AuthActionCreators';
import * as Analytics from 'app/services/Analytics';
import * as SetsSelectors from 'app/redux/selectors/SetsSelectors';

const AuthSaga = function * AuthSaga() {
    // handle anonymous logins
    yield fork(executeAnonymousLogin);

    while (true) {
        // login
        const loginTask = yield fork(executeLogin);
        const reauthenticateTask = yield fork(executeReauthenticate);

        // logout
        yield take([LOGOUT, CLEAR_TOKENS]);
        yield cancel(loginTask);
        yield cancel(reauthenticateTask);
        try {
            // reset and logout
            Analytics.setUserID();
            const user = yield apply(GoogleSignin, GoogleSignin.currentUserAsync);
            yield apply(GoogleSignin, GoogleSignin.signOut);

            // analytics
            const state = yield select();
            logLogoutAnalytics(state);
        } catch(error) {
            console.tron.log("LOGOUT SIGN OUT ERROR " + error);
            const state = yield select();
            logLogoutErrorAnalytics(state, error);
        }
    }
};

function* executeAnonymousLogin() {
    while(true) {
        try {
            yield take([STORE_INITIALIZED, CLEAR_TOKENS, LOGOUT]);

            // login immediately
            let refreshToken = yield select(AuthSelectors.getRefreshToken);
            if (!refreshToken) {
                let json = yield call(API.loginAnonymously);
                yield put(AuthActionCreators.saveTokens(json.accessToken, json.refreshToken, new Date()));

                // analytics
                const state = yield select();
                logLoginAnonymouslyAnalytics(state);
            }
        } catch(error) {
            console.tron.log("ERROR CODE " + error.code + " ERROR " + error);
            const state = yield select();
            logLoginAnonymouslyErrorAnalytics(state, error);
        }
    }
}

function* executeLogin() {
    try {
        yield take(LOGIN_REQUEST);

        // sign into google
        let state = yield select();
        logAttemptLoginGoogleAnalytics(state);
        const user = yield apply(GoogleSignin, GoogleSignin.signIn);

        // sign into our servers
        Analytics.setUserID(user.id);
        state = yield select();
        logAttemptLoginOpenBarbellAnalytics(state);
        let json = yield call(API.login, user.idToken);

        // success
        yield put(AuthActionCreators.loginSucceeded(json.accessToken, json.refreshToken, user.email, new Date(), json.revision, json.sets));
        state = yield select();
        logLoginAnalytics(state);

        yield apply(GoogleSignin, GoogleSignin.signOut);
    } catch(error) {
        console.tron.log("ERROR CODE " + error.code + " ERROR " + error);
        let state = yield select();
        if (error.code === -5 || error.code === 12501) {
            // -5 is when the user cancels the sign in on iOS
            // 12501 is when the user cancels the sign in on Android
            logCancelLoginAnalytics(state);
        } else {
            showSignInErrorAlert();
            logLoginErrorAnalytics(state, error);
        }
        yield put(AuthActionCreators.logout());
    } finally {
        if (yield cancelled()) {
            // TODO: Fix double logout on errors
            // Login Error causes a logout, which will cause a cancel of login, which then causes a second logout
            // not a big deal as it's an edge case, but would be nice to fix
            yield put(AuthActionCreators.logout());
        }
    }
}

function* executeReauthenticate() {
    while (true) {
        yield take(REAUTHENTICATE_REQUEST);
        const isLoggedIn = yield select(AuthSelectors.getIsLoggedIn);
        if (isLoggedIn) {
            yield call(executeReauthenticateLoggedInUser);
        } else {
            yield call(executeReauthenticateLoggedOutUser);
        }
    }
}

function* executeReauthenticateLoggedOutUser() {
    // this will cause CLEAR_TOKENS to be called, thereby creating a new anonymous user
    yield put(AuthActionCreators.logout(false));
    const state = yield select();
    logReauthenticateAnonymousAnalytics(state);
}

function* executeReauthenticateLoggedInUser() {
    try {
        // sign into google
        let state = yield select();
        logAttemptReauthenticateGoogleAnalytics(state);
        const user = yield apply(GoogleSignin, GoogleSignin.signIn);

        // sign into our servers
        Analytics.setUserID(user.id);
        state = yield select();
        logAttemptReauthenticateOpenBarbellAnalytics(state);
        let json = yield call(API.login, user.idToken);

        const origEmail = yield select(AuthSelectors.getEmail);
        const isDifferentUser = origEmail !== user.email;
        if (isDifferentUser) {
            // switch accounts, aka lose everything
            yield put(AuthActionCreators.loginSucceeded(json.accessToken, json.refreshToken, user.email, new Date(), json.revision, json.sets));
        } else {
            // success
            // note that we do not utilize the revisions or the set data
            // reason being that we only want to exchange our google token for access and refresh tokens
            // data cannot be pulled yet as they might have data waiting to go to the server first
            yield put(AuthActionCreators.reauthenticateSucceeded(json.accessToken, json.refreshToken, user.email, new Date()));
        }
        state = yield select();
        logReauthenticatedAnalytics(state, isDifferentUser);
    } catch(error) {
        console.tron.log("ERROR CODE " + error.code + " ERROR " + error);
        let state = yield select();
        if (error.code === -5 || error.code === 12501) {
            // -5 is when the user cancels the sign in on iOS
            // 12501 is when the user cancels the sign in on Android
            logCancelReauthenticateAnalytics(state);
            yield put(AuthActionCreators.logout(true)); // this will pop the alert
        } else {
            // actual error
            logReauthenticateErrorAnalytics(state, error);
            if (error.type === "401") {
                // server rejected, NOW logout
                yield put(AuthActionCreators.logout(true)); // this will pop the alert
            }
        }
    }
    // NOTE: theoretically don't need the finally logout call as canceling executeLogin should handle that
}

// ALERTS

const showSignInErrorAlert = () => {
    alert("Oops!", "Something went wrong during the signin process, please try again.");
};

const alert = (title, message) => {
    if (Platform.OS === 'ios') {
        // timeout is required to get around the window switching that the google sign in does on iOS
        // without it, the alert will not show
        setTimeout(() => {
            Alert.alert(
                title,
                message,
            );
        }, 500);
    } else {
        // execute immediately on android
        Alert.alert(
            title,
            message,
        );
    }
};

// ANALYTICS

// login

const logAttemptLoginGoogleAnalytics = (state) => {
    Analytics.logEventWithAppState('attempt_login_google', {
    }, state);
};

const logAttemptLoginOpenBarbellAnalytics = (state) => {
    Analytics.logEventWithAppState('attempt_login_openbarbell', {
    }, state);
};

const logCancelLoginAnalytics = (state) => {
    Analytics.logEventWithAppState('cancel_login', {
    }, state);
};

const logLoginErrorAnalytics = (state, error) => {
    Analytics.logErrorWithAppState(error, 'login_error', {
    }, state);
};

// TODO: test this
// this is waiting for a system to test sagas
const logLoginAnalytics = (state) => {
    const revision = SetsSelectors.getRevision(state);
    const has_nonzero_revision = revision > 0;

    Analytics.logEventWithAppState('login', {
        revision: revision,
        has_nonzero_revision: has_nonzero_revision,
    }, state);
};

// reauth

const logAttemptReauthenticateGoogleAnalytics = (state) => {
    Analytics.logEventWithAppState('attempt_reauthenticate_google', {
    }, state);
};

const logAttemptReauthenticateOpenBarbellAnalytics = (state) => {
    Analytics.logEventWithAppState('attempt_reauthenticate_openbarbell', {
    }, state);
};

const logCancelReauthenticateAnalytics = (state) => {
    Analytics.logEventWithAppState('cancel_reauthenticate', {
    }, state);
};

const logReauthenticateErrorAnalytics = (state, error) => {
    Analytics.logErrorWithAppState(error, 'reauthenticate_error', {
    }, state);
};

const logReauthenticatedAnalytics = (state, isDiffUser) => {
    Analytics.logEventWithAppState('reauthenticated', {
        is_diff_user: isDiffUser,
    }, state);
};

const logReauthenticateAnonymousAnalytics = (state) => {
    Analytics.logEventWithAppState('reauthenticated_anonymous', {
    }, state);
};

// anonymous

const logLoginAnonymouslyAnalytics = (state) => {
    Analytics.logEventWithAppState('login_anonymously', {
    }, state);
};

const logLoginAnonymouslyErrorAnalytics = (state, error) => {
    Analytics.logErrorWithAppState(error, 'login_anonymously_error', {
    }, state);
};

// logout

const logLogoutErrorAnalytics = (state, error) => {
    Analytics.logErrorWithAppState(error, 'logout_error', {
    }, state);
};

const logLogoutAnalytics = (state) => {
    Analytics.logEventWithAppState('logout', {
    }, state);
};

export default AuthSaga;
<commit_msg>remove debug force logout on login<commit_after>// This scheme was built on the assumption that users log in EACH TIME manually, and that's patently untrue

import {
    take,
    call,
    put,
    cancelled,
    cancel,
    fork,
    apply,
    select,
} from 'redux-saga/effects';
import { Alert, Platform } from 'react-native';
import { GoogleSignin } from 'react-native-google-signin';

import {
    STORE_INITIALIZED,
    LOGIN_REQUEST,
    LOGOUT,
    CLEAR_TOKENS,
    REAUTHENTICATE_REQUEST,
} from 'app/ActionTypes';
import API from 'app/services/API';
import * as AuthSelectors from 'app/redux/selectors/AuthSelectors';
import * as AuthActionCreators from 'app/redux/shared_actions/AuthActionCreators';
import * as Analytics from 'app/services/Analytics';
import * as SetsSelectors from 'app/redux/selectors/SetsSelectors';

const AuthSaga = function * AuthSaga() {
    // handle anonymous logins
    yield fork(executeAnonymousLogin);

    while (true) {
        // login
        const loginTask = yield fork(executeLogin);
        const reauthenticateTask = yield fork(executeReauthenticate);

        // logout
        yield take([LOGOUT, CLEAR_TOKENS]);
        yield cancel(loginTask);
        yield cancel(reauthenticateTask);
        try {
            // reset and logout
            Analytics.setUserID();
            const user = yield apply(GoogleSignin, GoogleSignin.currentUserAsync);
            yield apply(GoogleSignin, GoogleSignin.signOut);

            // analytics
            const state = yield select();
            logLogoutAnalytics(state);
        } catch(error) {
            console.tron.log("LOGOUT SIGN OUT ERROR " + error);
            const state = yield select();
            logLogoutErrorAnalytics(state, error);
        }
    }
};

function* executeAnonymousLogin() {
    while(true) {
        try {
            yield take([STORE_INITIALIZED, CLEAR_TOKENS, LOGOUT]);

            // login immediately
            let refreshToken = yield select(AuthSelectors.getRefreshToken);
            if (!refreshToken) {
                let json = yield call(API.loginAnonymously);
                yield put(AuthActionCreators.saveTokens(json.accessToken, json.refreshToken, new Date()));

                // analytics
                const state = yield select();
                logLoginAnonymouslyAnalytics(state);
            }
        } catch(error) {
            console.tron.log("ERROR CODE " + error.code + " ERROR " + error);
            const state = yield select();
            logLoginAnonymouslyErrorAnalytics(state, error);
        }
    }
}

function* executeLogin() {
    try {
        yield take(LOGIN_REQUEST);

        // sign into google
        let state = yield select();
        logAttemptLoginGoogleAnalytics(state);
        const user = yield apply(GoogleSignin, GoogleSignin.signIn);

        // sign into our servers
        Analytics.setUserID(user.id);
        state = yield select();
        logAttemptLoginOpenBarbellAnalytics(state);
        let json = yield call(API.login, user.idToken);

        // success
        yield put(AuthActionCreators.loginSucceeded(json.accessToken, json.refreshToken, user.email, new Date(), json.revision, json.sets));
        state = yield select();
        logLoginAnalytics(state);
    } catch(error) {
        console.tron.log("ERROR CODE " + error.code + " ERROR " + error);
        let state = yield select();
        if (error.code === -5 || error.code === 12501) {
            // -5 is when the user cancels the sign in on iOS
            // 12501 is when the user cancels the sign in on Android
            logCancelLoginAnalytics(state);
        } else {
            showSignInErrorAlert();
            logLoginErrorAnalytics(state, error);
        }
        yield put(AuthActionCreators.logout());
    } finally {
        if (yield cancelled()) {
            // TODO: Fix double logout on errors
            // Login Error causes a logout, which will cause a cancel of login, which then causes a second logout
            // not a big deal as it's an edge case, but would be nice to fix
            yield put(AuthActionCreators.logout());
        }
    }
}

function* executeReauthenticate() {
    while (true) {
        yield take(REAUTHENTICATE_REQUEST);
        const isLoggedIn = yield select(AuthSelectors.getIsLoggedIn);
        if (isLoggedIn) {
            yield call(executeReauthenticateLoggedInUser);
        } else {
            yield call(executeReauthenticateLoggedOutUser);
        }
    }
}

function* executeReauthenticateLoggedOutUser() {
    // this will cause CLEAR_TOKENS to be called, thereby creating a new anonymous user
    yield put(AuthActionCreators.logout(false));
    const state = yield select();
    logReauthenticateAnonymousAnalytics(state);
}

function* executeReauthenticateLoggedInUser() {
    try {
        // sign into google
        let state = yield select();
        logAttemptReauthenticateGoogleAnalytics(state);
        const user = yield apply(GoogleSignin, GoogleSignin.signIn);

        // sign into our servers
        Analytics.setUserID(user.id);
        state = yield select();
        logAttemptReauthenticateOpenBarbellAnalytics(state);
        let json = yield call(API.login, user.idToken);

        const origEmail = yield select(AuthSelectors.getEmail);
        const isDifferentUser = origEmail !== user.email;
        if (isDifferentUser) {
            // switch accounts, aka lose everything
            yield put(AuthActionCreators.loginSucceeded(json.accessToken, json.refreshToken, user.email, new Date(), json.revision, json.sets));
        } else {
            // success
            // note that we do not utilize the revisions or the set data
            // reason being that we only want to exchange our google token for access and refresh tokens
            // data cannot be pulled yet as they might have data waiting to go to the server first
            yield put(AuthActionCreators.reauthenticateSucceeded(json.accessToken, json.refreshToken, user.email, new Date()));
        }
        state = yield select();
        logReauthenticatedAnalytics(state, isDifferentUser);
    } catch(error) {
        console.tron.log("ERROR CODE " + error.code + " ERROR " + error);
        let state = yield select();
        if (error.code === -5 || error.code === 12501) {
            // -5 is when the user cancels the sign in on iOS
            // 12501 is when the user cancels the sign in on Android
            logCancelReauthenticateAnalytics(state);
            yield put(AuthActionCreators.logout(true)); // this will pop the alert
        } else {
            // actual error
            logReauthenticateErrorAnalytics(state, error);
            if (error.type === "401") {
                // server rejected, NOW logout
                yield put(AuthActionCreators.logout(true)); // this will pop the alert
            }
        }
    }
    // NOTE: theoretically don't need the finally logout call as canceling executeLogin should handle that
}

// ALERTS

const showSignInErrorAlert = () => {
    alert("Oops!", "Something went wrong during the signin process, please try again.");
};

const alert = (title, message) => {
    if (Platform.OS === 'ios') {
        // timeout is required to get around the window switching that the google sign in does on iOS
        // without it, the alert will not show
        setTimeout(() => {
            Alert.alert(
                title,
                message,
            );
        }, 500);
    } else {
        // execute immediately on android
        Alert.alert(
            title,
            message,
        );
    }
};

// ANALYTICS

// login

const logAttemptLoginGoogleAnalytics = (state) => {
    Analytics.logEventWithAppState('attempt_login_google', {
    }, state);
};

const logAttemptLoginOpenBarbellAnalytics = (state) => {
    Analytics.logEventWithAppState('attempt_login_openbarbell', {
    }, state);
};

const logCancelLoginAnalytics = (state) => {
    Analytics.logEventWithAppState('cancel_login', {
    }, state);
};

const logLoginErrorAnalytics = (state, error) => {
    Analytics.logErrorWithAppState(error, 'login_error', {
    }, state);
};

// TODO: test this
// this is waiting for a system to test sagas
const logLoginAnalytics = (state) => {
    const revision = SetsSelectors.getRevision(state);
    const has_nonzero_revision = revision > 0;

    Analytics.logEventWithAppState('login', {
        revision: revision,
        has_nonzero_revision: has_nonzero_revision,
    }, state);
};

// reauth

const logAttemptReauthenticateGoogleAnalytics = (state) => {
    Analytics.logEventWithAppState('attempt_reauthenticate_google', {
    }, state);
};

const logAttemptReauthenticateOpenBarbellAnalytics = (state) => {
    Analytics.logEventWithAppState('attempt_reauthenticate_openbarbell', {
    }, state);
};

const logCancelReauthenticateAnalytics = (state) => {
    Analytics.logEventWithAppState('cancel_reauthenticate', {
    }, state);
};

const logReauthenticateErrorAnalytics = (state, error) => {
    Analytics.logErrorWithAppState(error, 'reauthenticate_error', {
    }, state);
};

const logReauthenticatedAnalytics = (state, isDiffUser) => {
    Analytics.logEventWithAppState('reauthenticated', {
        is_diff_user: isDiffUser,
    }, state);
};

const logReauthenticateAnonymousAnalytics = (state) => {
    Analytics.logEventWithAppState('reauthenticated_anonymous', {
    }, state);
};

// anonymous

const logLoginAnonymouslyAnalytics = (state) => {
    Analytics.logEventWithAppState('login_anonymously', {
    }, state);
};

const logLoginAnonymouslyErrorAnalytics = (state, error) => {
    Analytics.logErrorWithAppState(error, 'login_anonymously_error', {
    }, state);
};

// logout

const logLogoutErrorAnalytics = (state, error) => {
    Analytics.logErrorWithAppState(error, 'logout_error', {
    }, state);
};

const logLogoutAnalytics = (state) => {
    Analytics.logEventWithAppState('logout', {
    }, state);
};

export default AuthSaga;
<|endoftext|>


<commit_before>$("document").ready(function() {
  // all the markers
  var allMarkers = [];

  // search index for the markers
  var markerSearch = [];

  // map context
  var map;
  var markers;

  /// Link Helper functions
  function generateTreasurersLink(parcelid){
    return "http://www.mctreas.org/master.cfm?parid=" + parcelid.replace(" ", "%20") + "&taxyr=2016&own1=SMITH";
  };

  function generateGISLink(parcelid){
    return "http://www.mcegisohio.org/geobladeweb/default.aspx?config=aud&field='" + parcelid + "'";
  };

  /// typeahead helper
  function substringMatcher(strs) {
    return function findMatches(q, cb) {
      var matches, substrRegex;
      // an array that will be populated with substring matches
      matches = [];

      // regex used to determine if a string contains the substring `q`
      substrRegex = new RegExp(q, 'i');

      // iterate through the pool of strings and for any string that
      // contains the substring `q`, add it to the `matches` array

      // Practically clearing the previous search results
      // Does using .map change the performance?
      for (var i = 0; i < allMarkers.length; i++) {
        allMarkers[i].found = false;
      }

      $.each(strs, function(i, str) {
        if (substrRegex.test(str)) {
          // the typeahead jQuery plugin expects suggestions to a
          // JavaScript object, refer to typeahead docs for more info

          // Update the found attribute on the master marker list
          // This will later used to update the markers on the map
          allMarkers[i % allMarkers.length].found = true;
          matches.push({ value: str });
        }
      });

      setMarkers();
      cb(matches);
    };
  };

  /* Highlight search box text on click */
  $("#addressInput").click(function () {
    $(this).select();
  });

  /* Prevent hitting enter from refreshing the page */
  $("#addressInput").keypress(function (e) {
    if (e.which == 13) {
      e.preventDefault();
    }
  });

  $("#addressInput").on('typeahead:selected', function(evt, item) {
    // User selected a text from the search results box
    // We need to update the map with the markers matching exactly that text
    // If there's only one result, we will select that property
    // This is going to be a subset of all the markers on the map,
    //    however Leaflet update efficiency should be considered.

    var value = item.value;
    // Clearing the previous search results and searching for the selected marker(s)
    for (var i = 0; i < allMarkers.length; i++) {
      allMarkers[i].found = false;
      if (allMarkers[i].parcelid === value || allMarkers[i].address === value) allMarkers[i].found = true;
    }

    // Let's update the markers on the map to selected markers
    // This will also select the marker if there's only one result
    setMarkers();
  });

  $("#addressInput").typeahead({
    minLength: 0,
    highlight: true,
    hint: false
  }, {
    name: "AllMarkers",
    displayKey: "value",
    source: substringMatcher(markerSearch),
    limit: 15,
    templates: {
      empty: '
No Lot Links eligible properties were found.
If you provided a complete address, then the property is not eligible for Lot Links at this time.
'
    }
  });

  /// Lookup based on typeahead and updating right bar
  function selectedProperty(address,parcelid, claimed) {
    $('#selectedAddress').text(address);
    $('#selectedParcelId').text(parcelid);
    $('#linkToTreasuresSite').html("View Property on Treasurer's Site");
    $('#linkToGISSite').html("View Property on GIS Site");
    if (claimed) {
      $('#claimedWarning').html("This property has been claimed. It is not available at this time.");
    } else {
      $('#claimedWarning').html("");
    }
    $(".introcontainer").css("margin-top", "255px");
  }

  function clearMarkers() {
    // Let's not update the map if all the markers are already in there
    if (markers.getLayers().length === allMarkers.length) return;

    // Leaflet says it's more efficient to remove all the markers and then inseart the new ones.
    markers.clearLayers();

    // Let's put everything back onto the map
    // We should also set the found attributes to false on the master list
    var cleanMarkers = [];
    for (var i = 0; i < allMarkers.length; i++) {
      allMarkers[i].found = false;
      // We are already looping over all the markers
      // It might be more efficient to update cleanMarkers in this loop
      cleanMarkers.push(allMarkers[i]);
    }
    markers.addLayers(cleanMarkers);
  }

  function setMarkers() {
    // Original design calls for putting all the markers back if there's nothing found during the search
    // This feature is removed due to search slugishness

    // Let's find all the found markers
    var foundMarkers = [];
    for (var i = 0; i < allMarkers.length; i++) {
      if (allMarkers[i].found) foundMarkers.push(allMarkers[i]);
    }
    // Let's not update the map if we found all markers in the search and they are already on the map
    if (foundMarkers.length === allMarkers.length && markers.getLayers().length === allMarkers.length) return;

    // Marker attributes such as .claim and .new can be used here to filter search results.

    // Leaflet says it's more efficient to remove all the markers and then inseart the new ones.
    markers.clearLayers();
    markers.addLayers(foundMarkers);

    // Let's select the marker if there's only one of them is found
    if (foundMarkers.length === 1 ) {
      selectedProperty(foundMarkers[0].address, foundMarkers[0].parcelid, foundMarkers[0].claimed);
    }
  }

  // NEEDS UPDATE: Broken by search update
  function showOnlyNewProperties(showNew) {
    markers.clearLayers();
    if (showNew) {
      var filteredMarkers = markerList.filter(function(marker) { return marker.new == true; });
      markers.addLayers(filteredMarkers);
    } else {
      markers.addLayers(markerList);
    }
  }

  // NEEDS UPDATE: Broken by search update
  //Filter new properties
  $("#showOnlyNewProperties").change(function () {
    $(this).is(":checked") ? showOnlyNewProperties(true) : showOnlyNewProperties(false);
  });

  /// Site initialization
  function createMap() {
    var satTiles = L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
      maxZoom: 18,
      attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
    }),
    mapTiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
      maxZoom: 18,
      attribution: '© OpenStreetMap contributors, Points &copy 2012 LINZ'
    }),
    latlng = L.latLng(39.758948, -84.191607);
    map = L.map('map', { center: latlng, zoom: 10, layers: [mapTiles] });
    L.control.layers({"Map":mapTiles,"Satellite":satTiles}).addTo(map);
  }

  function popup(street, parcel) {
    return '
' + street + '
' +
           '
' + parcel + '
';
  }

  function initMarkers() {

    var redMarker = L.AwesomeMarkers.icon({
      icon: 'close-round',
      markerColor: 'red',
      prefix: 'ion'
    });

    var blueMarker = L.AwesomeMarkers.icon({
      icon: 'home',
      markerColor: 'blue',
      prefix: 'ion'
    });

    var lotMarker = L.AwesomeMarkers.icon({
      icon: 'leaf',
      markerColor: 'blue',
      prefix: 'ion'
    });

    // points object is loaded from the data file
    for (var i = 0; i < points.length; i++) {
      var a = points[i];
      var title = a.street;
      var icon = blueMarker;
      if (a.lot)
        icon = lotMarker;
      if (a.claimed)
        icon = redMarker;

      var marker = L.marker(L.latLng(parseFloat(a.lat), parseFloat(a.lon)), { title: title, icon: icon });

      marker.address = a.street;
      marker.parcelid = a.parcelid;
      marker.claimed = a.claimed;
      marker.new = a.new;
      marker.found = false; // Helper attribute for the search function.

      marker.on('click', function(e) {
        selectedProperty(e.target.address, e.target.parcelid, e.target.claimed);
      });

      marker.bindPopup(popup(title, a.parcelid));
      allMarkers.push(marker);
    }

    // markerSearch array contains the search terms
    // Its size should be kept as multiples of allMarkers.length
    for (var i = 0; i < allMarkers.length; i++) {
        markerSearch.push(allMarkers[i].parcelid);
    }
    for (var i = 0; i < allMarkers.length; i++) {
        markerSearch.push(allMarkers[i].address);
    }

    markers = L.markerClusterGroup({
      chunkedLoading: true,
      chunkInterval: 20,
      chunkDelay: 50
    });

    markers.addLayers(allMarkers);
    map.addLayer(markers);
  }

  function initSite() {
    $('#last_update').text(lastupdated);

    createMap();
    initMarkers();

  }

  initSite();

  //HTML5 input placeholder fix for < ie10
  $('input, textarea').placeholder();

  function uiFixes() {
     //JS to fix the Twitter Typeahead styling, as it is unmodifyable in the bower folder
    $('.twitter-typeahead').css('display', '');
    //Fix for the Twitter Typeahead styling of the pre tag causing issues with horizontal scrolling in conentpanel
    $('pre').css("margin-left", "-50%");
  }

  uiFixes();

  //JS FAQ triggers

  function clickedFAQ(element) {
    var clickedFAQ = element.id;
    var expandFAQ = clickedFAQ + "-expand";
    var isExpandedFAQ = $("#"+expandFAQ).css("display");

    if (isExpandedFAQ === "block"){
      $("#"+expandFAQ).hide("slow");
      $("#"+expandFAQ+" *").hide("slow");
      $("#"+clickedFAQ+" h4 span.expanded-icon").replaceWith("+");
      console.log(clickedFAQ+" h4 span.expand-icon");
    }else{

      $("#"+expandFAQ).show();
      $("#"+expandFAQ+" *").show("fast");
      $("#"+clickedFAQ+" h4 span.expand-icon").replaceWith("‒");
    }

  }

  $("[id^=FAQ-]").click( function() {
    clickedFAQ(this);
  });
});
<commit_msg>Trying to fix my mess.<commit_after>$("document").ready(function() {
  // current marker list
  var markerList = [];

  // all the markers
  var allMarkers = [];

  // search index for the markers
  var markerSearch = [];

  // map context
  var map;
  var markers;

  /// Link Helper functions
  function generateTreasurersLink(parcelid){
    return "http://www.mctreas.org/master.cfm?parid=" + parcelid.replace(" ", "%20") + "&taxyr=2016&own1=SMITH";
  };

  function generateGISLink(parcelid){
    return "http://www.mcegisohio.org/geobladeweb/default.aspx?config=aud&field='" + parcelid + "'";
  };

  /// typeahead helper
  function substringMatcher(strs) {
    return function findMatches(q, cb) {
      var matches, substrRegex;

      // an array that will be populated with substring matches
      matches = [];

      // regex used to determine if a string contains the substring `q`
      substrRegex = new RegExp(q, 'i');

      // iterate through the pool of strings and for any string that
      // contains the substring `q`, add it to the `matches` array
      markerList = [];
      $.each(strs, function(i, str) {
        if (substrRegex.test(str)) {
          // the typeahead jQuery plugin expects suggestions to a
          // JavaScript object, refer to typeahead docs for more info
          updateMarkers(i, str);
          matches.push({ value: str });
        }
      });

      setMarkers();
      cb(matches);
    };
  };

  /* Highlight search box text on click */
  $("#addressInput").click(function () {
    $(this).select();
  });

  /* Prevent hitting enter from refreshing the page */
  $("#addressInput").keypress(function (e) {
    if (e.which == 13) {
      e.preventDefault();
    }
  });

  $("#addressInput").on('typeahead:selected', function(evt, item) {
    lookupValue(item.value);
  });

  $("#addressInput").typeahead({
    minLength: 0,
    highlight: true,
    hint: false
  }, {
    name: "AllMarkers",
    displayKey: "value",
    source: substringMatcher(markerSearch),
    templates: {
      empty: '
No Lot Links eligible properties were found.
If you provided a complete address, then the property is not eligible for Lot Links at this time.
'
    }
  });

  /// Lookup based on typeahead and updating right bar
  function selectedProperty(address,parcelid, claimed) {
    $('#selectedAddress').text(address);
    $('#selectedParcelId').text(parcelid);
    $('#linkToTreasuresSite').html("View Property on Treasurer's Site");
    $('#linkToGISSite').html("View Property on GIS Site");
    if (claimed) {
      $('#claimedWarning').html("This property has been claimed. It is not available at this time.");
    } else {
      $('#claimedWarning').html("");
    }
    $(".introcontainer").css("margin-top", "255px");
  }

  function updateMarkers(index, value) {
    if (allMarkers[index] !== undefined &&
      (allMarkers[index].parcelid === value || allMarkers[index].address === value))
    {
      markerList.push(allMarkers[index]);
    } else if (index >= allMarkerLength &&
        (allMarkers[index-allMarkerLength].parcelid === value || allMarkers[index-allMarkerLength].address === value))
    {
      markerList.push(allMarkers[index-allMarkerLength]);
    }
  }

  function clearMarkers() {
    if (markerList.length !== allMarkerLength) {
      markers.clearLayers();
      markerList = allMarkers;
      markers.addLayers(markerList);
    }
  }

  function setMarkers() {
    if (markerList.length === 0) {
      markerList = allMarkers;
    }
    markers.clearLayers();
    markers.addLayers(markerList);
  }

  function lookupValue(value) {
    var val = $('#addressInput').val().toUpperCase();
    var refreshNeeded = false;
    var newMarkers = [];

    if (val.length > 3 && val.substring(0,3) === 'R72') {
      for (var i = 0; i < allMarkerLength; i++) {
        if (allMarkers[i].parcelid && allMarkers[i].parcelid.indexOf(val) >= 0) {
          newMarkers.push(allMarkers[i]);
        }
      }
      refreshNeeded = true;
      markerList = newMarkers;
    }
    else if (val.length > 2 && val !== 'R' && val !== 'R7') {
      for (var j = 0; j < allMarkerLength; j++) {
        if (allMarkers[j].address && allMarkers[j].address.indexOf(val) >= 0) {
          newMarkers.push(allMarkers[j]);
        }
      }
      refreshNeeded = true;
      markerList = newMarkers;
    }
    else if (val.length < 3 && markerList.length !== allMarkerLength) {
      markerList = [];
    }

    if (newMarkers.length === 0) {
      refreshNeeded = true;
      markerList = allMarkers;
    }
    else if (newMarkers.length === 1) {
      selectedProperty(newMarkers[0].address, newMarkers[0].parcelid, newMarkers[0].claimed);
    }

    if (refreshNeeded) {
      markers.clearLayers();
      markers.addLayers(markerList);
    }
  }

  function showOnlyNewProperties(showNew) {
    markers.clearLayers();
    if (showNew) {
      var filteredMarkers = markerList.filter(function(marker) { return marker.new == true; });
      markers.addLayers(filteredMarkers);
    } else {
      markers.addLayers(markerList);
    }
  }

  //Filter new properties
  $("#showOnlyNewProperties").change(function () {
    $(this).is(":checked") ? showOnlyNewProperties(true) : showOnlyNewProperties(false);
  });

  /// Site initialization
  function createMap() {
    var satTiles = L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
      maxZoom: 18,
      attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
    }),
    mapTiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
      maxZoom: 18,
      attribution: '© OpenStreetMap contributors, Points &copy 2012 LINZ'
    }),
    latlng = L.latLng(39.758948, -84.191607);
    map = L.map('map', { center: latlng, zoom: 10, layers: [mapTiles] });
    L.control.layers({"Map":mapTiles,"Satellite":satTiles}).addTo(map);
  }

  function popup(street, parcel) {
    return '
' + street + '
' +
           '
' + parcel + '
';
  }

  function initMarkers() {
    markerList = [];

    var redMarker = L.AwesomeMarkers.icon({
      icon: 'close-round',
      markerColor: 'red',
      prefix: 'ion'
    });

    var blueMarker = L.AwesomeMarkers.icon({
      icon: 'home',
      markerColor: 'blue',
      prefix: 'ion'
    });

    var lotMarker = L.AwesomeMarkers.icon({
      icon: 'leaf',
      markerColor: 'blue',
      prefix: 'ion'
    });

    for (var i = 0; i < points.length; i++) {
      var a = points[i];
      var title = a.street;
      var icon = blueMarker;
      if (a.lot)
        icon = lotMarker;
      if (a.claimed)
        icon = redMarker;
      var marker = L.marker(L.latLng(parseFloat(a.lat), parseFloat(a.lon)), { title: title, icon: icon });
      marker.address = a.street;
      marker.parcelid = a.parcelid;
      marker.claimed = a.claimed;
      marker.new = a.new;

      marker.on('click', function(e) {
        selectedProperty(e.target.address, e.target.parcelid, e.target.claimed);
      });
      marker.bindPopup(popup(title, a.parcelid));
      markerList.push(marker);
    }
    allMarkers = markerList;
    allMarkerLength = allMarkers.length;

    for (var i = 0; i < allMarkerLength; i++) {
        markerSearch.push(allMarkers[i].parcelid);
    }
    for (i = 0; i < allMarkerLength; i++) {
        markerSearch.push(allMarkers[i].address);
    }

    markers = L.markerClusterGroup({
      chunkedLoading: true,
      chunkInterval: 20,
      chunkDelay: 50

    });
    markers.addLayers(markerList);
    map.addLayer(markers);
  }

  function initSite() {
    $('#last_update').text(lastupdated);

    createMap();
    initMarkers();

  }

  initSite();

  //HTML5 input placeholder fix for < ie10
  $('input, textarea').placeholder();

  function uiFixes() {
     //JS to fix the Twitter Typeahead styling, as it is unmodifyable in the bower folder
    $('.twitter-typeahead').css('display', '');
    //Fix for the Twitter Typeahead styling of the pre tag causing issues with horizontal scrolling in conentpanel
    $('pre').css("margin-left", "-50%");
  }

  uiFixes();

  //JS FAQ triggers

  function clickedFAQ(element) {
    var clickedFAQ = element.id;
    var expandFAQ = clickedFAQ + "-expand";
    var isExpandedFAQ = $("#"+expandFAQ).css("display");

    if (isExpandedFAQ === "block"){
      $("#"+expandFAQ).hide("slow");
      $("#"+expandFAQ+" *").hide("slow");
      $("#"+clickedFAQ+" h4 span.expanded-icon").replaceWith("+");
      console.log(clickedFAQ+" h4 span.expand-icon");
    }else{

      $("#"+expandFAQ).show();
      $("#"+expandFAQ+" *").show("fast");
      $("#"+clickedFAQ+" h4 span.expand-icon").replaceWith("‒");
    }

  }

  $("[id^=FAQ-]").click( function() {
    clickedFAQ(this);
  });
});
<|endoftext|>


<commit_before>var xmlToJSON = {};											// create a namespace

xmlToJSON.Parser = (function() {							// declare the object, Parser

    var parserConstructor = function Parser() {				// with a constructor to return (see end of function)
        if(false === (this instanceof Parser)) {
            return new Parser();							// it doesn't do anything, but gives an object to provide method upon
        }
    }

    parserConstructor.prototype.parseString = function (xmlString) {	// such as this one!  add parseString method to the Parser object

			parser = sax.parser(true, {xmlns: true, trim: true});		// get an instance of the sax.js parser
			stack = [];													// create an empty array to hold and collapse objects

																		// some tag identifiers for various xml bits, such as
			namespaceKey = '_ns';										//   namespaces,
			attributeKey = '_at';										//   attributes, and
			textKey = '_t';												//   text (also used for attribute values), and
			cdataKey = '_c';											//   cdata

			// sax.js functions begin here
			parser.onopentag = function (node) {
		  		nodeTmp = {};											// node object
		  		data = {}												// node data
		  		name = node.name;										// tag name to use for this node (default)
		  		if (node.uri) {											// test for namespaces
		  			name = node.local;									// switch tag name to local part (no prefixes in tags)
					data[namespaceKey] = node.uri;						// set the namespace as well
				}

				if (!isEmpty(node.attributes) ) {						// do we have an attributes?
					data[attributeKey] = {};							// create an empty data object for them
					for (attr in node.attributes) {						// iterate over the attribute names
						attrTmp = stack.pop();							// pop the attribute object from the stack
																		//	sax.js encounters attributes ahead of open tags
																		//	so, we created them in the "onattribute" method
						for(var key in attrTmp) break;					// neat trick to fetch the first tag name
						data[attributeKey][key] = attrTmp[key];			// set the attribute
					}
				}
				nodeTmp[name] = data;									// copy the data into the node object
				stack.push(nodeTmp);									// and push it the stack (happens for every element)
			};

			parser.onclosetag = function (name) {
				if (stack.length < 2) {									// make sure there are at least two things on the stack
					return;												// if not, bail
				} else {
					child = stack.pop();								// the most recent item on the stack is a child not
					for(var ckey in child) break;						// get it's tag name

					parent = stack.pop();								// the second item will be it's parent (since we're processing on closetags)
					for(var pkey in parent) break;						// get it's tag name as well

					if ( !(parent[pkey][ckey]) ) {						// if the parent does not contain an item under the child's tag name
						parent[pkey][ckey] = [child[ckey]];				// add it (in an array to support multiple xml elements of the same name)
					} else {											// otherwise
						parent[pkey][ckey].push(child[ckey]);			// push the child object to the existing array of similiarly named children
					}

					stack.push(parent);									// push the updated parent back to the stack
				}
			};

			parser.onattribute = function (attr) {

		  	  		attrTmp = {};										// attribute object
		 			data = {};											// attribute data
		 			name = attr.name;									// tag name to use for this attribute (default)
		 			if (attr.uri) {										// test for namespaces
		 				name = attr.local;								// switch tag name to local part (no prefixes in tags)
						data[namespaceKey] = attr.uri;  				// set the namespace as well
					}
					data[textKey] = attr.value;							// set the value of the attribute

					attrTmp[name] = data;								// copy the data into the attribute object
					stack.push(attrTmp);								// and push it to the stack
			};

			parser.ontext = function (text) {
				if (stack.length > 0) {									// make sure there is at least one object on the stack
					nodeTmp = stack.pop();								// get the object
					for(var key in nodeTmp) break;						// get it's tag name
					nodeTmp[key][textKey] = text;						// set the text value of the node
					stack.push(nodeTmp);								// push the updated object back to the stack
				}
			};

			parser.oncdata = function (cdata) {
				if (stack.length > 0) {									// make sure there is at least one object on the stack
					nodeTmp = stack.pop();								// get the object
					for(var key in nodeTmp) break;						// get it's tag name
					nodeTmp[key][cdataKey] = cdata;						// set the text value of the node
					stack.push(nodeTmp);								// push the updated object back to the stack
				}
			};

			parser.onerror = function (error) {
			  console.debug('error ' + error);							// print errors as they occur
			};
			// end of sax.js methods 									// several nodes were not implemented, such as
																		//   processing isntructions, cdata, comments, etc...

			function isEmpty(obj) {										// utility method to test for empty objects, e.g.
				for(var prop in obj) {									//   var mtObj = {}
					if(obj.hasOwnProperty(prop))
						return false;
				}
				return true;
			}

			parser.write(xmlString).close();							// launches the sax parser
			return stack[0];											// leaving the result as the only object on the stack
    }

    return parserConstructor;											// returns the Parser object mentioned earlier
}());
<commit_msg>Delete xmlToJSON.js<commit_after><|endoftext|>


<commit_before>import React, { Component } from 'react';

export default class SudokuBoardView extends Component {
    constructor(props) {
        super(props);

        this.state = {
            solvedCells: [
                [false, false, false, false, false, false, false, false, false],
                [false, false, false, false, false, false, false, false, false],
                [false, false, false, false, false, false, false, false, false],
                [false, false, false, false, false, false, false, false, false],
                [false, false, false, false, false, false, false, false, false],
                [false, false, false, false, false, false, false, false, false],
                [false, false, false, false, false, false, false, false, false],
                [false, false, false, false, false, false, false, false, false],
                [false, false, false, false, false, false, false, false, false]
            ],
            solved: false
        };

        this.renderSudokuBoard = this.renderSudokuBoard.bind(this);
        this.solve = this.solve.bind(this);
        this.reset = this.reset.bind(this);
        this.getCellValue = this.getCellValue.bind(this);
        this.handleCellInput = this.handleCellInput.bind(this);
        this.getCellStyling = this.getCellStyling.bind(this);
    }

    render() {
        return (

Sudoku AI

                {this.renderSudokuBoard()}
                <button
                    className={'button'}
                    onClick={this.solve}
                    disabled={!this.props.sudokuBoard.isValid || this.state.solved}
                >
                    Solve
                </button>
                <button className={'button'} onClick={this.reset}>
                    Reset
                </button>

        );
    }

    renderSudokuBoard() {
        const board = this.props.sudokuBoard.board;
        let cellCount = 0;
        return (

                    {board.map((row, rowIndex) => (
                        <tr key={`row-${cellCount}`}>
                            {row.map((cell, colIndex) => (
                                <td key={`data-${cellCount}`}>
                                    <input
                                        id={`cell-${cellCount++}`}
                                        type="text"
                                        value={this.getCellValue(rowIndex, colIndex)}
                                        onChange={event => this.handleCellInput(event, rowIndex, colIndex)}
                                        className={this.getCellStyling(rowIndex, colIndex)}
                                        disabled={this.state.solved}
                                    />

                            ))}

                    ))}

        );
    }

    solve() {
        const board = this.props.sudokuBoard.board;
        const solvedCells = board.map(row => row.map(cell => !(cell >= 1 && cell <= 9)));
        this.setState({ solvedCells, solved: true });
        this.props.solveBoard(board);
    }

    reset() {
        this.setState({ solved: false });
        this.props.resetBoard();
    }

    getCellValue(row, col) {
        const board = this.props.sudokuBoard.board;
        const value = Math.abs(board[row][col]);
        return value >= 1 && value <= 9 ? value : '';
    }

    getCellStyling(row, col) {
        const board = this.props.sudokuBoard.board;
        if (this.state.solvedCells[row][col] && this.state.solved) {
            return 'solved-cell';
        }
        if (board[row][col] >= 1 && board[row][col] <= 9) {
            return 'valid-cell';
        }
        if (board[row][col] === 0) {
            return null;
        }
        return 'invalid-cell';
    }

    handleCellInput(event, row, col) {
        const board = this.props.sudokuBoard.board;
        let value = event.target.value[event.target.value.length - 1];
        this.props.updateCell(row, col, value, board);
        if (!value) {
            return this.props.clearBoardErrors(board);
        }
        setTimeout(() => this.props.clearBoardErrors(board), 500);
    }
}
<commit_msg>Add redux thunk refactor to view<commit_after>import React, { Component } from 'react';

export default class SudokuBoardView extends Component {
    constructor(props) {
        super(props);

        this.renderSudokuBoard = this.renderSudokuBoard.bind(this);
        this.getCellValue = this.getCellValue.bind(this);
        this.handleCellInput = this.handleCellInput.bind(this);
        this.getCellStyling = this.getCellStyling.bind(this);
    }

    render() {
        return (

Sudoku AI

                {this.renderSudokuBoard()}
                <button
                    className={'button'}
                    onClick={this.props.solveBoard}
                    disabled={!this.props.sudokuBoard.isValid || this.props.sudokuBoard.isSolved}
                >
                    Solve
                </button>
                <button className={'button'} onClick={this.props.reset}>
                    Reset
                </button>

        );
    }

    renderSudokuBoard() {
        const board = this.props.sudokuBoard.board;
        let cellCount = 0;
        return (

                    {board.map((row, rowIndex) => (
                        <tr key={`row-${cellCount}`}>
                            {row.map((cell, colIndex) => (
                                <td key={`data-${cellCount}`}>
                                    <input
                                        id={`cell-${cellCount++}`}
                                        type="text"
                                        value={this.getCellValue(rowIndex, colIndex)}
                                        onChange={event => this.handleCellInput(event, rowIndex, colIndex)}
                                        className={this.getCellStyling(rowIndex, colIndex)}
                                        disabled={this.props.sudokuBoard.isSolved}
                                    />

                            ))}

                    ))}

        );
    }

    getCellValue(row, col) {
        const board = this.props.sudokuBoard.board;
        const value = board[row][col];
        return value >= 1 && value <= 9 ? value : '';
    }

    getCellStyling(row, col) {
        const board = this.props.sudokuBoard.board;
        if (this.props.sudokuBoard.solvedCells[row][col] && this.props.sudokuBoard.isSolved) {
            return 'solved-cell';
        }
        if (this.props.sudokuBoard.errorCells[row][col]) {
            return 'invalid-cell';
        }
        if (board[row][col] === 0) {
            return null;
        }
        return 'valid-cell';
    }

    handleCellInput(event, row, col) {
        let value = event.target.value[event.target.value.length - 1];
        this.props.updateCell(row, col, value);
        if (!value) {
            return this.props.clearBoardErrors();
        }
        setTimeout(() => this.props.clearBoardErrors(), 500);
    }
}
<|endoftext|>