<commit_before>/* global XRegExp, rangy, $, _ */
/* global alert, prompt, AposMediaLibrary, AposTagEditor */

if (!window.apos) {
  window.apos = {};
}

$( document ).tooltip({
  show: { effect: "fadeIn", duration: 200 },
  hide: { effect: "fadeOut", duration: 200 },
  position: { my: "left top+10", at: "left bottom" }
});

var apos = window.apos;

apos.widgetTypes = {};

var defaultWidgetTypes = [ 'slideshow', 'buttons', 'marquee', 'files', 'video', 'embed', 'pullquote', 'code', 'html' ];

// Add a widget type on the browser side. If typeName is `slideshow`, then Apostrophe invokes
// the constructor function named `AposSlideshowWidgetEditor`, unless
// `apos.data.widgetOptions.slideshow.constructorName` has been set to another name.
// This means your server-side code can specify an alternate constructor by calling:
//
//     apos.pushGlobalData({
//       widgetOptions: {
//         slideshow: {
//           constructorName: 'MySlideshowEditor'
//         }
//       }
//     })
//
// If you are developing your own widgets you should specify your own defaultConstructorName
// so that you can avoid conflicts with future widgets in the Apos namespace.
//
// This happens after domready so that all javascript inline in the page, including
// the calls that set up `apos.data`, can complete first.

apos.addWidgetType = function(typeName, defaultConstructorName) {
  $(function() {
    var constructorName = (apos.data.widgetOptions && apos.data.widgetOptions[typeName] && apos.data.widgetOptions[typeName].constructorName) || defaultConstructorName || ('Apos' + apos.capitalizeFirst(typeName) + 'WidgetEditor');
    var Constructor = window[constructorName];
    try {
      apos.widgetTypes[typeName] = {
        label: Constructor.label,
        editor: Constructor
      };
      // Allows properties known about the widget on the server side to be
      // seen on the browser side
      $.extend(true, apos.widgetTypes[typeName], (apos.data.widgetOptions && apos.data.widgetOptions[typeName]) || {});
    } catch (e) {
      apos.log('Error constructing widget ' + typeName + ', expected constructor function named ' + constructorName + ', error was:');
      throw e;
    }
  });
};

_.each(defaultWidgetTypes, function(type) {
  apos.addWidgetType(type);
});

// KEEP IN SYNC WITH SERVER SIDE IMPLEMENTATION in search.js

apos.slugify = window.sluggo;

// Borrowed from the regexp-quote module for node
apos.regExpQuote = function (string) {
  return string.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
};

// For use when storing DOM attributes meant to be compatible
// with jQuery's built-in support for JSON parsing of
// objects and arrays in data attributes. We'll be passing
// the result to .attr, so we don't have to worry about
// HTML escaping.

apos.jsonAttribute = function(value) {
  if (typeof(value) === 'object') {
    return JSON.stringify(value);
  } else {
    return value;
  }
};

// Given an array of items for an area, return true if the area
// is considered empty. TODO: this is a lousy hack right now,
// we don't have the capabilities we do on the server side to
// identify empty blog widgets, etc.

apos.areaIsEmpty = function(area) {
  return !_.find(area, function(item) {
    if ((area.type === 'richText') && (!area.content)) {
      return false;
    }
    return true;
  });
};

// Given an array of items for a singleton, return true if the
// singleton is considered empty. TODO: right now this just
// calls apos.areaIsEmpty, it should take the type into
// account.

apos.singletonIsEmpty = function(area, type) {
  return apos.areaIsEmpty(area);
};

// Reusable utility to watch one jquery element's value and use it to
// suggest valid slugs by updating the second jquery element's value.
// If the initial slug contains slashes, only the last component
// (after the last slash) is changed on title edits. If the original
// slug (or its last component) is not in sync with the title, it is
// assumed that the user already made deliberate changes to the slug, and
// the slug is not automatically updated.

apos.suggestSlugOnTitleEdits = function($title, $slug) {
  // Watch the title for changes, update the slug - but only if
  // the slug was in sync with the title to start with

  var originalTitle = $title.val();
  var currentSlug = $slug.val();
  var components = currentSlug.split('/');
  var currentSlugTitle = components.pop();
  if (currentSlugTitle === apos.slugify(originalTitle)) {
    $title.on('keyup', function() {
      var title = $title.val();
      if (title !== originalTitle) {
        var currentSlug = $slug.val();
        var components = currentSlug.split('/');
        if (components.length) {
          components.pop();
          var candidateSlugTitle = apos.slugify(title);
          components.push(candidateSlugTitle);
          var newSlug = components.join('/');
          $slug.val(newSlug);
        }
      }
    });
  }
};

// Accept tags as a comma-separated string and sanitize them,
// returning an array of zero or more nonempty strings. Must match
// server side implementation
apos.tagsToArray = function(tags) {
  if (typeof(tags) === 'number') {
    tags += '';
  }
  if (typeof(tags) !== 'string') {
    return [];
  }
  tags += '';
  tags = tags.split(/,\s*/);
  // split returns an array of one empty string for an empty source string ):
  tags = _.filter(tags, function(tag) { return tag.length > 0; });
  // Make them all strings
  tags = _.map(tags, function(tag) {
    // Tags are always lowercase otherwise they will not compare
    // properly in MongoDB. If you want to change this then you'll
    // need to address that deeper issue
    return (tag + '').toLowerCase();
  });
  return tags;
};

// Convert tags back to a string
apos.tagsToString = function(s) {
  if (!$.isArray(s)) {
    // This is legit if no tags property exists yet on an object, don't bomb
    return '';
  }
  var result = s.join(', ');
  return result;
};

// Fix lame URLs. If we can't fix the URL, return null.
//
// Accepts valid URLs and relative URLs. If the URL smells like
// it starts with a domain name, supplies an http:// prefix.
//
// KEEP IN SYNC WITH apostrophe.js SERVER SIDE VERSION

apos.fixUrl = function(href) {
  if (href.match(/^(((https?|ftp)\:\/\/)|mailto\:|\#|([^\/\.]+)?\/|[^\/\.]+$)/)) {
    // All good - no change required
    return href;
  } else if (href.match(/^[^\/\.]+\.[^\/\.]+/)) {
    // Smells like a domain name. Educated guess: they left off http://
    return 'http://' + href;
  } else {
    return null;
  }
};

// Keep all older siblings of node, remove the rest (including node) from the DOM.
apos.keepOlderSiblings = function(node) {
  while (node) {
    var next = node.nextSibling;
    node.parentNode.removeChild(node);
    node = next;
  }
};

// Append all DOM sibling nodes after node, including
// text nodes, to target.
apos.moveYoungerSiblings = function(node, target) {
  var sibling = node.nextSibling;
  while (sibling) {
    var next = sibling.nextSibling;
    target.insertBefore(sibling, null);
    sibling = next;
  }
};

// Enable autocomplete of tags. Expects the fieldset element
// (not the input element) and an array of existing tags already
// assigned to this item.
// Options are passed in from addFields
apos.enableTags = function($el, tags, field) {
  tags = tags || [];
  field = field || {};
  if (apos.data.lockTags) {
    $el.find('[data-add]').remove();
  }
  if(!field.limit) field.limit = undefined;
  if(!field.sortable) field.sortable = undefined;
  $el.selective({ preventDuplicates: true, add: !apos.data.lockTags, data: tags, source: '/apos/autocomplete-tag', addKeyCodes: [ 13, 'U+002C'], limit: field.limit, sortable: field.sortable });
};

// Initialize a yes/no select element. If value is undefined (not just false),
// def is used.
apos.enableBoolean = function($el, value, def) {
  if (value === undefined) {
    value = def;
  }
  value = value ? '1' : '0';
  $el.val(value);
};

// Return the value of a yes/no select element as a proper
// boolean (true or false).

apos.getBoolean = function($el) {
  var val = $el.val();
  // Quoted string '0' is considered truthy, fix that
  if (val === '0') {
    return false;
  }
  return !!val;
};

// Set things up to instantiate the media library when the button is clicked. This is set up
// to allow subclassing with an alternate constructor function

apos.enableMediaLibrary = function() {
  $('body').on('click', '.apos-media-library-button', function() {
    if (!apos.data.mediaLibrary) {
      apos.data.mediaLibrary = {};
    }
    var constructorName = apos.data.mediaLibrary.constructorName || 'AposMediaLibrary';
    var mediaLibrary = new window[constructorName](apos.data.mediaLibrary);
    mediaLibrary.modal();
    return false;
  });
};

// Set things up to instantiate the tag editor when the button is clicked. This is set up
// to allow subclassing with an alternate constructor function

apos.enableTagEditor = function() {
  $('body').on('click', '.apos-tag-editor-button', function() {
    if (!apos.data.tagEditorOptions) {
      apos.data.tagEditorOptions = {};
    }
    var constructorName = apos.data.tagEditorOptions.constructorName || 'AposTagEditor';
    var tagEditor = new window[constructorName](apos.tagEditorOptions);
    tagEditor.modal();
    return false;
  });
};

// Simple progress display. Locates a progress display inside the given
// element. If state is true, indicates activity, otherwise indicates
// complete. Supports nested calls; does not revert to indicating complete
// until the nesting level is 0.

apos.busy = function($el, state) {
  var busy = $el.data('busy') || 0;
  if (state) {
    busy++;
    $el.data('busy', busy);
    if (busy === 1) {
      $el.find('[data-progress]').show();
      $el.find('[data-finished]').hide();
    }
  } else {
    busy--;
    $el.data('busy', busy);
    if (!busy) {
      $el.find('[data-progress]').hide();
      $el.find('[data-finished]').show();
    }
  }
};

// Area editing is in apostrophe-editor-2, but
// singleton editing still lives in the core
apos.enableSingletons = function() {
  $('body').on('click', '.apos-edit-singleton', function() {
    var $singleton = $(this).closest('.apos-singleton');
    var slug = $singleton.attr('data-slug');
    var type = $singleton.attr('data-type');

    var itemData = { position: 'middle', size: 'full' };
    var $item = $singleton.find('.apos-content .apos-widget:first');
    if ($item.length) {
      itemData = apos.getWidgetData($item);
    }
    var editor = new apos.widgetTypes[type].editor({
      data: itemData,
      save: function(callback) {
        if (slug) {
          // Has a slug, save it
          $.jsonCall('/apos/edit-singleton',
            {
              dataType: 'html',
            },
            {
              slug: slug,
              options: JSON.parse($singleton.attr('data-options') || {}),
              // By now itemData has been updated (we passed it
              // into the widget and JavaScript passes objects by reference)
              content: itemData
            },
            function(markup) {
              $singleton.find('.apos-content').html(markup);
              apos.enablePlayers($singleton);
              callback(null);
            },
            function() {
            alert('Server error, please try again.');
            callback('error');
          });
        } else {
          // Virtual singletons must be saved in other ways. Add it as a
          // data attribute of the singleton, and post an event
          $singleton.attr('data-item', JSON.stringify(itemData));
          $singleton.trigger('aposEdited', itemData);
          return callback();
        }
      },
      // Options passed from the template or other environment
      options: $singleton.data('options')
    });
    editor.init();
    return false;
  });
};

apos.enableAreas = function() {
  apos.log('DEPRECATED: you do not have to call apos.enableAreas anymore. This stub is going away in 0.6.');
};

$(function() {
  // Do these late so that other code has a chance to override
  // None of these need to happen again on 'ready' events, so
  // just do them once
  apos.afterYield(function() {
    apos.enableMediaLibrary();
    apos.enableTagEditor();
    apos.enableSingletons();
  });
});

<commit_msg>data-page-versions="slug" attribute on a link can be used to open the page versions modal for any page object, including the global page, which may be necessary on a project-by-project basis<commit_after>/* global XRegExp, rangy, $, _ */
/* global alert, prompt, AposMediaLibrary, AposTagEditor, aposPages */

if (!window.apos) {
  window.apos = {};
}

$( document ).tooltip({
  show: { effect: "fadeIn", duration: 200 },
  hide: { effect: "fadeOut", duration: 200 },
  position: { my: "left top+10", at: "left bottom" }
});

var apos = window.apos;

apos.widgetTypes = {};

var defaultWidgetTypes = [ 'slideshow', 'buttons', 'marquee', 'files', 'video', 'embed', 'pullquote', 'code', 'html' ];

// Add a widget type on the browser side. If typeName is `slideshow`, then Apostrophe invokes
// the constructor function named `AposSlideshowWidgetEditor`, unless
// `apos.data.widgetOptions.slideshow.constructorName` has been set to another name.
// This means your server-side code can specify an alternate constructor by calling:
//
//     apos.pushGlobalData({
//       widgetOptions: {
//         slideshow: {
//           constructorName: 'MySlideshowEditor'
//         }
//       }
//     })
//
// If you are developing your own widgets you should specify your own defaultConstructorName
// so that you can avoid conflicts with future widgets in the Apos namespace.
//
// This happens after domready so that all javascript inline in the page, including
// the calls that set up `apos.data`, can complete first.

apos.addWidgetType = function(typeName, defaultConstructorName) {
  $(function() {
    var constructorName = (apos.data.widgetOptions && apos.data.widgetOptions[typeName] && apos.data.widgetOptions[typeName].constructorName) || defaultConstructorName || ('Apos' + apos.capitalizeFirst(typeName) + 'WidgetEditor');
    var Constructor = window[constructorName];
    try {
      apos.widgetTypes[typeName] = {
        label: Constructor.label,
        editor: Constructor
      };
      // Allows properties known about the widget on the server side to be
      // seen on the browser side
      $.extend(true, apos.widgetTypes[typeName], (apos.data.widgetOptions && apos.data.widgetOptions[typeName]) || {});
    } catch (e) {
      apos.log('Error constructing widget ' + typeName + ', expected constructor function named ' + constructorName + ', error was:');
      throw e;
    }
  });
};

_.each(defaultWidgetTypes, function(type) {
  apos.addWidgetType(type);
});

// KEEP IN SYNC WITH SERVER SIDE IMPLEMENTATION in search.js

apos.slugify = window.sluggo;

// Borrowed from the regexp-quote module for node
apos.regExpQuote = function (string) {
  return string.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
};

// For use when storing DOM attributes meant to be compatible
// with jQuery's built-in support for JSON parsing of
// objects and arrays in data attributes. We'll be passing
// the result to .attr, so we don't have to worry about
// HTML escaping.

apos.jsonAttribute = function(value) {
  if (typeof(value) === 'object') {
    return JSON.stringify(value);
  } else {
    return value;
  }
};

// Given an array of items for an area, return true if the area
// is considered empty. TODO: this is a lousy hack right now,
// we don't have the capabilities we do on the server side to
// identify empty blog widgets, etc.

apos.areaIsEmpty = function(area) {
  return !_.find(area, function(item) {
    if ((area.type === 'richText') && (!area.content)) {
      return false;
    }
    return true;
  });
};

// Given an array of items for a singleton, return true if the
// singleton is considered empty. TODO: right now this just
// calls apos.areaIsEmpty, it should take the type into
// account.

apos.singletonIsEmpty = function(area, type) {
  return apos.areaIsEmpty(area);
};

// Reusable utility to watch one jquery element's value and use it to
// suggest valid slugs by updating the second jquery element's value.
// If the initial slug contains slashes, only the last component
// (after the last slash) is changed on title edits. If the original
// slug (or its last component) is not in sync with the title, it is
// assumed that the user already made deliberate changes to the slug, and
// the slug is not automatically updated.

apos.suggestSlugOnTitleEdits = function($title, $slug) {
  // Watch the title for changes, update the slug - but only if
  // the slug was in sync with the title to start with

  var originalTitle = $title.val();
  var currentSlug = $slug.val();
  var components = currentSlug.split('/');
  var currentSlugTitle = components.pop();
  if (currentSlugTitle === apos.slugify(originalTitle)) {
    $title.on('keyup', function() {
      var title = $title.val();
      if (title !== originalTitle) {
        var currentSlug = $slug.val();
        var components = currentSlug.split('/');
        if (components.length) {
          components.pop();
          var candidateSlugTitle = apos.slugify(title);
          components.push(candidateSlugTitle);
          var newSlug = components.join('/');
          $slug.val(newSlug);
        }
      }
    });
  }
};

// Accept tags as a comma-separated string and sanitize them,
// returning an array of zero or more nonempty strings. Must match
// server side implementation
apos.tagsToArray = function(tags) {
  if (typeof(tags) === 'number') {
    tags += '';
  }
  if (typeof(tags) !== 'string') {
    return [];
  }
  tags += '';
  tags = tags.split(/,\s*/);
  // split returns an array of one empty string for an empty source string ):
  tags = _.filter(tags, function(tag) { return tag.length > 0; });
  // Make them all strings
  tags = _.map(tags, function(tag) {
    // Tags are always lowercase otherwise they will not compare
    // properly in MongoDB. If you want to change this then you'll
    // need to address that deeper issue
    return (tag + '').toLowerCase();
  });
  return tags;
};

// Convert tags back to a string
apos.tagsToString = function(s) {
  if (!$.isArray(s)) {
    // This is legit if no tags property exists yet on an object, don't bomb
    return '';
  }
  var result = s.join(', ');
  return result;
};

// Fix lame URLs. If we can't fix the URL, return null.
//
// Accepts valid URLs and relative URLs. If the URL smells like
// it starts with a domain name, supplies an http:// prefix.
//
// KEEP IN SYNC WITH apostrophe.js SERVER SIDE VERSION

apos.fixUrl = function(href) {
  if (href.match(/^(((https?|ftp)\:\/\/)|mailto\:|\#|([^\/\.]+)?\/|[^\/\.]+$)/)) {
    // All good - no change required
    return href;
  } else if (href.match(/^[^\/\.]+\.[^\/\.]+/)) {
    // Smells like a domain name. Educated guess: they left off http://
    return 'http://' + href;
  } else {
    return null;
  }
};

// Keep all older siblings of node, remove the rest (including node) from the DOM.
apos.keepOlderSiblings = function(node) {
  while (node) {
    var next = node.nextSibling;
    node.parentNode.removeChild(node);
    node = next;
  }
};

// Append all DOM sibling nodes after node, including
// text nodes, to target.
apos.moveYoungerSiblings = function(node, target) {
  var sibling = node.nextSibling;
  while (sibling) {
    var next = sibling.nextSibling;
    target.insertBefore(sibling, null);
    sibling = next;
  }
};

// Enable autocomplete of tags. Expects the fieldset element
// (not the input element) and an array of existing tags already
// assigned to this item.
// Options are passed in from addFields
apos.enableTags = function($el, tags, field) {
  tags = tags || [];
  field = field || {};
  if (apos.data.lockTags) {
    $el.find('[data-add]').remove();
  }
  if(!field.limit) field.limit = undefined;
  if(!field.sortable) field.sortable = undefined;
  $el.selective({ preventDuplicates: true, add: !apos.data.lockTags, data: tags, source: '/apos/autocomplete-tag', addKeyCodes: [ 13, 'U+002C'], limit: field.limit, sortable: field.sortable });
};

// Initialize a yes/no select element. If value is undefined (not just false),
// def is used.
apos.enableBoolean = function($el, value, def) {
  if (value === undefined) {
    value = def;
  }
  value = value ? '1' : '0';
  $el.val(value);
};

// Return the value of a yes/no select element as a proper
// boolean (true or false).

apos.getBoolean = function($el) {
  var val = $el.val();
  // Quoted string '0' is considered truthy, fix that
  if (val === '0') {
    return false;
  }
  return !!val;
};

// Set things up to instantiate the media library when the button is clicked. This is set up
// to allow subclassing with an alternate constructor function

apos.enableMediaLibrary = function() {
  $('body').on('click', '.apos-media-library-button', function() {
    if (!apos.data.mediaLibrary) {
      apos.data.mediaLibrary = {};
    }
    var constructorName = apos.data.mediaLibrary.constructorName || 'AposMediaLibrary';
    var mediaLibrary = new window[constructorName](apos.data.mediaLibrary);
    mediaLibrary.modal();
    return false;
  });
};

// Set things up to instantiate the tag editor when the button is clicked. This is set up
// to allow subclassing with an alternate constructor function

apos.enableTagEditor = function() {
  $('body').on('click', '.apos-tag-editor-button', function() {
    if (!apos.data.tagEditorOptions) {
      apos.data.tagEditorOptions = {};
    }
    var constructorName = apos.data.tagEditorOptions.constructorName || 'AposTagEditor';
    var tagEditor = new window[constructorName](apos.tagEditorOptions);
    tagEditor.modal();
    return false;
  });
};

// Simple progress display. Locates a progress display inside the given
// element. If state is true, indicates activity, otherwise indicates
// complete. Supports nested calls; does not revert to indicating complete
// until the nesting level is 0.

apos.busy = function($el, state) {
  var busy = $el.data('busy') || 0;
  if (state) {
    busy++;
    $el.data('busy', busy);
    if (busy === 1) {
      $el.find('[data-progress]').show();
      $el.find('[data-finished]').hide();
    }
  } else {
    busy--;
    $el.data('busy', busy);
    if (!busy) {
      $el.find('[data-progress]').hide();
      $el.find('[data-finished]').show();
    }
  }
};

// Area editing is in apostrophe-editor-2, but
// singleton editing still lives in the core
apos.enableSingletons = function() {
  $('body').on('click', '.apos-edit-singleton', function() {
    var $singleton = $(this).closest('.apos-singleton');
    var slug = $singleton.attr('data-slug');
    var type = $singleton.attr('data-type');

    var itemData = { position: 'middle', size: 'full' };
    var $item = $singleton.find('.apos-content .apos-widget:first');
    if ($item.length) {
      itemData = apos.getWidgetData($item);
    }
    var editor = new apos.widgetTypes[type].editor({
      data: itemData,
      save: function(callback) {
        if (slug) {
          // Has a slug, save it
          $.jsonCall('/apos/edit-singleton',
            {
              dataType: 'html',
            },
            {
              slug: slug,
              options: JSON.parse($singleton.attr('data-options') || {}),
              // By now itemData has been updated (we passed it
              // into the widget and JavaScript passes objects by reference)
              content: itemData
            },
            function(markup) {
              $singleton.find('.apos-content').html(markup);
              apos.enablePlayers($singleton);
              callback(null);
            },
            function() {
            alert('Server error, please try again.');
            callback('error');
          });
        } else {
          // Virtual singletons must be saved in other ways. Add it as a
          // data attribute of the singleton, and post an event
          $singleton.attr('data-item', JSON.stringify(itemData));
          $singleton.trigger('aposEdited', itemData);
          return callback();
        }
      },
      // Options passed from the template or other environment
      options: $singleton.data('options')
    });
    editor.init();
    return false;
  });
  $('body').on('click', '[data-page-versions]', function() {
    var slug = $(this).attr('data-page-versions');
    aposPages.browseVersions({ slug: slug });
    return false;
  });

};

apos.enableAreas = function() {
  apos.log('DEPRECATED: you do not have to call apos.enableAreas anymore. This stub is going away in 0.6.');
};

$(function() {
  // Do these late so that other code has a chance to override
  // None of these need to happen again on 'ready' events, so
  // just do them once
  apos.afterYield(function() {
    apos.enableMediaLibrary();
    apos.enableTagEditor();
    apos.enableSingletons();
  });
});

<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2018 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.view',
  name: 'RichChoiceView',
  extends: 'foam.u2.View',

  documentation: `
    This is similar to foam.u2.view.ChoiceView, but lets you provide views for
    the selection and options instead of strings. This allows you to create
    dropdowns with rich content like images and formatting using CSS.

    Example usage for a Reference property on a model:

      {
        class: 'Reference',
        of: 'foam.nanos.auth.User',
        name: 'exampleProperty',
        view: function(_, X) {
          return {
            class: 'foam.u2.view.RichChoiceView',
            selectionView: { class: 'a.b.c.MyCustomSelectionView' }, // Optional
            rowView: { class: 'a.b.c.MyCustomCitationView' }, // Optional
            sections: [
              {
                heading: 'Users',
                dao: X.userDAO.orderBy(foam.nanos.auth.User.LEGAL_NAME)
              },
              // Set "disabled: true" to render each object as non-selectable row
              // Set hideIfEmpty: true" to hide headers if not objects are present in dao provided.
              {
                disabled: true,
                heading: 'Disabled users',
                hideIfEmpty: true,
                dao: X.userDAO.where(this.EQ(foam.nanos.auth.User.STATUS, this.AccountStatus.DISABLED)),
              },
            ]
          };
        }
      }
  `,

  implements: [
    'foam.mlang.Expressions'
  ],

  exports: [
    'of'
  ],

  css: `
    ^ {
      position: relative;
      display: inline-block;
    }

    ^container {
      position: absolute;
      bottom: 0;
      left: 0;
      transform: translateY(100%);
      background: white;
      border: 1px solid #bdbdbd;
      max-height: 378px;
      overflow-y: scroll;
      box-sizing: border-box;
      width: 100%;
      min-width: fit-content;
      -webkit-appearance: textfield;
    }

    ^heading {
      border-bottom: 1px solid #f4f4f9;
      font-size: 12px;
      font-weight: 900;
      padding: 1px 2px;
    }

    ^selection-view {
      background-color: white;
      display: inline-flex;
      align-items: center;
      justify-content: space-between;
      font-size: 11px;
      box-sizing: border-box;
      -webkit-appearance: textfield;
      padding: 1px 2px;
      cursor: default;
      border: 1px solid;
      min-width: 94px;
    }

    ^chevron::before {
      content: '▾';
      padding-left: 4px;
    }

    ^custom-selection-view {
      flex-grow: 1;
    }

    ^ .search input {
      width: 100%;
      border: none;
    }

    ^ .search input:focus {
      border: none;
    }

    ^ .disabled {
      filter: grayscale(100%) opacity(60%);
    }

    ^ .disabled:hover {
      cursor: default;
    }
  `,

  properties: [
    {
      class: 'foam.u2.ViewSpec',
      name: 'rowView',
      documentation: `
        Set this to override the default view used for each row. It will be
        instantiated with an object from the DAO as the 'data' property.
      `,
      factory: function() {
        return this.DefaultRowView;
      }
    },
    {
      name: 'data',
      documentation: `
        The value that gets chosen. This is set whenever a user makes a choice.
      `
    },
    {
      class: 'Boolean',
      name: 'isOpen_',
      documentation: `
        An internal property used to determine whether the options list is
        visible or not.
      `
    },
    {
      class: 'foam.u2.ViewSpec',
      name: 'selectionView',
      documentation: `
        Set this to override the default view used for the input content. It
        will be instantiated with an object from the DAO as the 'fullObject'
        property and that object's id as the 'data' property.
      `,
      factory: function() {
        return this.DefaultSelectionView;
      }
    },
    {
      class: 'Array',
      name: 'sections',
      documentation: `
        This lets you pass different predicated versions of a dao in different
        sections, which can be used to do things like grouping by some property
        for each section.
        Each object in the array must have a 'label' property of type string
        which will be used for the section heading, and a 'dao' property of type
        DAO that will be used to populate the list in that section.
      `,
    },
    {
      class: 'FObjectProperty',
      name: 'of',
      documentation: 'The model stored in the DAO. Used intenrally.',
      expression: function(sections) {
        return sections[0].dao.of;
      }
    },
    {
      class: 'FObjectProperty',
      name: 'fullObject_',
      documentation: `
        The full object from the DAO. This property is only used internally, you
        do not need to set it as a consumer of this view.
      `
    },
    {
      class: 'Boolean',
      name: 'search',
      documentation: 'Set to true to enable searching.'
    },
    {
      class: 'String',
      name: 'filter_',
      documentation: 'The text that the user typed in to search by.',
      postSet: function(oldValue, newValue) {
        this.sections = this.sections.map((section) => {
          return Object.assign({}, section, {
            filtered: newValue
              ? section.dao.where(this.KEYWORD(newValue))
              : section.dao
          });
        });
      }
    }
  ],

  methods: [
    function initE() {
      var self = this;

      if ( ! Array.isArray(this.sections) || this.sections.length === 0 ) {
        throw new Error(`You must provide an array of sections. See documentation on the 'sections' property in RichTextView.js.`);
      }

      // If the property that this view is for already has a value when being
      // rendered, the 'data' property on this model will be set to an id for
      // the object being referenced by the Reference property being rendered.
      // Custom views might need the full object to render though, not just the
      // id, so we do a lookup here for the full object here. This then gets
      // passed to the selectionView to use it if it wants to.
      if ( this.data ) {
        this.sections[0].dao.find(this.data).then((result) => {
          this.fullObject_ = result;
        });
      }

      this
        .addClass(this.myClass())
        .start()
          .addClass(this.myClass('selection-view'))
          .on('click', function() {
            self.isOpen_ = ! self.isOpen_;
          })
          .start()
            .addClass(this.myClass('custom-selection-view'))
            .add(this.slot((data) => {
              return this.E().tag(self.selectionView, {
                data: data,
                fullObject$: this.fullObject_$
              });
            }))
          .end()
          .start()
            .addClass(this.myClass('chevron'))
          .end()
        .end()
        .start()
          .addClass(this.myClass('container'))
          .show(self.isOpen_$)
          .add(self.search$.map((searchEnabled) => {
            if ( ! searchEnabled ) return null;
            return this.E()
              .start()
                .startContext({ data: self })
                  .addClass('search')
                  .add(self.FILTER_.clone().copyFrom({ view: {
                    class: 'foam.u2.view.TextField',
                    placeholder: 'Search...',
                    onKey: true
                  } }))
                .endContext()
              .end();
          }))
          .add(this.slot(function(sections) {
            var promiseArray = [];
            sections.forEach(function(section) {
              promiseArray.push(section.dao.select(self.COUNT()));
            });

            return Promise.all(promiseArray).then((resp) => {
              var index = 0;
              return this.E().forEach(sections, function(section) {
                this
                  .start().hide(!! section.hideIfEmpty && resp[index].value <= 0)
                    .addClass(self.myClass('heading'))
                    .add(section.heading)
                  .end()
                  .start()
                    .select(section.filtered || section.dao, (obj) => {
                      return this.E()
                        .start(self.rowView, { data: obj })
                          .enableClass('disabled', section.disabled)
                          .callIf(! section.disabled, function() {
                            this.on('click', () => {
                              self.fullObject_ = obj;
                              self.data = obj;
                              self.isOpen_ = false;
                            });
                          })
                        .end();
                    })
                  .end();
                  index++;
              });
            });
          }))
        .end();
    }
  ],

  classes: [
    {
      name: 'DefaultRowView',
      extends: 'foam.u2.View',

      documentation: `
        This is the view that gets rendered for each item in the list.
      `,

      css: `
        ^row {
          background: white;
          padding: 1px 2px;
          font-size: 12px;
        }

        ^row:hover {
          background: #f4f4f9;
          cursor: pointer;
        }
      `,

      properties: [
        {
          name: 'data',
          documentation: 'The selected object.'
        }
      ],

      methods: [
        function initE() {
          return this
            .start()
              .addClass(this.myClass('row'))
              .add(this.data.id)
            .end();
        }
      ]
    },
    {
      name: 'DefaultSelectionView',
      extends: 'foam.u2.Element',

      documentation: `
        This is the view that gets rendered inside the select input. It is put
        to the left of the chevron (the triangle at the far right side of the
        select input). This is an Element instead of a simple string, meaning
        the select input can contain "rich" content like images and make use of
        CSS for styling and layout.
        As an example of why this is useful, imagine you wanted to show a
        dropdown to select a country. You could choose to display the flag of
        the selected country alongside its name after the user makes a
        selection by creating that custom view and providing it in place of this
        one by setting the selectionView property on RichChoiceView.
      `,

      imports: [
        'of'
      ],

      messages: [
        {
          name: 'CHOOSE_FROM',
          message: 'Choose from '
        }
      ],

      properties: [
        {
          name: 'data',
          documentation: 'The id of the selected object.'
        },
        {
          name: 'fullObject',
          documentation: `
            The full object. It's not used here in the default selection view,
            but this property is included to let you know that if you create a
            custom selection view, it will be passed the id of the object (data)
            as well as the full object.
          `
        }
      ],

      methods: [
        function initE() {
          var plural = this.of.model_.plural.toLowerCase();
          return this.add(this.data || this.CHOOSE_FROM + plural);
        }
      ]
    }
  ]
});
<commit_msg>Update RichChoiceView to support being disabled<commit_after>/**
 * @license
 * Copyright 2018 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.view',
  name: 'RichChoiceView',
  extends: 'foam.u2.View',

  documentation: `
    This is similar to foam.u2.view.ChoiceView, but lets you provide views for
    the selection and options instead of strings. This allows you to create
    dropdowns with rich content like images and formatting using CSS.

    Example usage for a Reference property on a model:

      {
        class: 'Reference',
        of: 'foam.nanos.auth.User',
        name: 'exampleProperty',
        view: function(_, X) {
          return {
            class: 'foam.u2.view.RichChoiceView',
            selectionView: { class: 'a.b.c.MyCustomSelectionView' }, // Optional
            rowView: { class: 'a.b.c.MyCustomCitationView' }, // Optional
            sections: [
              {
                heading: 'Users',
                dao: X.userDAO.orderBy(foam.nanos.auth.User.LEGAL_NAME)
              },
              // Set "disabled: true" to render each object as non-selectable row
              // Set hideIfEmpty: true" to hide headers if not objects are present in dao provided.
              {
                disabled: true,
                heading: 'Disabled users',
                hideIfEmpty: true,
                dao: X.userDAO.where(this.EQ(foam.nanos.auth.User.STATUS, this.AccountStatus.DISABLED)),
              },
            ]
          };
        }
      }
  `,

  implements: [
    'foam.mlang.Expressions'
  ],

  exports: [
    'of'
  ],

  css: `
    ^ {
      position: relative;
      display: inline-block;
    }

    ^container {
      position: absolute;
      bottom: 0;
      left: 0;
      transform: translateY(100%);
      background: white;
      border: 1px solid #bdbdbd;
      max-height: 378px;
      overflow-y: scroll;
      box-sizing: border-box;
      width: 100%;
      min-width: fit-content;
      -webkit-appearance: textfield;
    }

    ^heading {
      border-bottom: 1px solid #f4f4f9;
      font-size: 12px;
      font-weight: 900;
      padding: 1px 2px;
    }

    ^selection-view {
      background-color: white;
      display: inline-flex;
      align-items: center;
      justify-content: space-between;
      font-size: 11px;
      box-sizing: border-box;
      -webkit-appearance: textfield;
      padding: 1px 2px;
      cursor: default;
      border: 1px solid;
      min-width: 94px;
    }

    ^chevron::before {
      content: '▾';
      padding-left: 4px;
    }

    ^custom-selection-view {
      flex-grow: 1;
    }

    ^ .search input {
      width: 100%;
      border: none;
    }

    ^ .search input:focus {
      border: none;
    }

    ^ .disabled {
      filter: grayscale(100%) opacity(60%);
    }

    ^ .disabled:hover {
      cursor: default;
    }
  `,

  properties: [
    {
      class: 'foam.u2.ViewSpec',
      name: 'rowView',
      documentation: `
        Set this to override the default view used for each row. It will be
        instantiated with an object from the DAO as the 'data' property.
      `,
      factory: function() {
        return this.DefaultRowView;
      }
    },
    {
      name: 'data',
      documentation: `
        The value that gets chosen. This is set whenever a user makes a choice.
      `
    },
    {
      class: 'Boolean',
      name: 'isOpen_',
      documentation: `
        An internal property used to determine whether the options list is
        visible or not.
      `
    },
    {
      class: 'foam.u2.ViewSpec',
      name: 'selectionView',
      documentation: `
        Set this to override the default view used for the input content. It
        will be instantiated with an object from the DAO as the 'fullObject'
        property and that object's id as the 'data' property.
      `,
      factory: function() {
        return this.DefaultSelectionView;
      }
    },
    {
      class: 'Array',
      name: 'sections',
      documentation: `
        This lets you pass different predicated versions of a dao in different
        sections, which can be used to do things like grouping by some property
        for each section.
        Each object in the array must have a 'label' property of type string
        which will be used for the section heading, and a 'dao' property of type
        DAO that will be used to populate the list in that section.
      `,
    },
    {
      class: 'FObjectProperty',
      name: 'of',
      documentation: 'The model stored in the DAO. Used intenrally.',
      expression: function(sections) {
        return sections[0].dao.of;
      }
    },
    {
      class: 'FObjectProperty',
      name: 'fullObject_',
      documentation: `
        The full object from the DAO. This property is only used internally, you
        do not need to set it as a consumer of this view.
      `
    },
    {
      class: 'Boolean',
      name: 'search',
      documentation: 'Set to true to enable searching.'
    },
    {
      class: 'String',
      name: 'filter_',
      documentation: 'The text that the user typed in to search by.',
      postSet: function(oldValue, newValue) {
        this.sections = this.sections.map((section) => {
          return Object.assign({}, section, {
            filtered: newValue
              ? section.dao.where(this.KEYWORD(newValue))
              : section.dao
          });
        });
      }
    }
  ],

  methods: [
    function initE() {
      var self = this;

      if ( ! Array.isArray(this.sections) || this.sections.length === 0 ) {
        throw new Error(`You must provide an array of sections. See documentation on the 'sections' property in RichTextView.js.`);
      }

      // If the property that this view is for already has a value when being
      // rendered, the 'data' property on this model will be set to an id for
      // the object being referenced by the Reference property being rendered.
      // Custom views might need the full object to render though, not just the
      // id, so we do a lookup here for the full object here. This then gets
      // passed to the selectionView to use it if it wants to.
      if ( this.data ) {
        this.sections[0].dao.find(this.data).then((result) => {
          this.fullObject_ = result;
        });
      }

      this
        .addClass(this.myClass())
        .start()
          .addClass(this.myClass('selection-view'))
          .enableClass('disabled', this.mode$.map((mode) => mode === foam.u2.DisplayMode.DISABLED))
          .callIf(this.mode === foam.u2.DisplayMode.RW, function() {
            this.on('click', function() {
              self.isOpen_ = ! self.isOpen_;
            });
          })
          .start()
            .addClass(this.myClass('custom-selection-view'))
            .add(this.slot((data) => {
              return this.E().tag(self.selectionView, {
                data: data,
                fullObject$: this.fullObject_$
              });
            }))
          .end()
          .start()
            .addClass(this.myClass('chevron'))
          .end()
        .end()
        .start()
          .addClass(this.myClass('container'))
          .show(self.isOpen_$)
          .add(self.search$.map((searchEnabled) => {
            if ( ! searchEnabled ) return null;
            return this.E()
              .start()
                .startContext({ data: self })
                  .addClass('search')
                  .add(self.FILTER_.clone().copyFrom({ view: {
                    class: 'foam.u2.view.TextField',
                    placeholder: 'Search...',
                    onKey: true
                  } }))
                .endContext()
              .end();
          }))
          .add(this.slot(function(sections) {
            var promiseArray = [];
            sections.forEach(function(section) {
              promiseArray.push(section.dao.select(self.COUNT()));
            });

            return Promise.all(promiseArray).then((resp) => {
              var index = 0;
              return this.E().forEach(sections, function(section) {
                this
                  .start().hide(!! section.hideIfEmpty && resp[index].value <= 0)
                    .addClass(self.myClass('heading'))
                    .add(section.heading)
                  .end()
                  .start()
                    .select(section.filtered || section.dao, (obj) => {
                      return this.E()
                        .start(self.rowView, { data: obj })
                          .enableClass('disabled', section.disabled)
                          .callIf(! section.disabled, function() {
                            this.on('click', () => {
                              self.fullObject_ = obj;
                              self.data = obj;
                              self.isOpen_ = false;
                            });
                          })
                        .end();
                    })
                  .end();
                  index++;
              });
            });
          }))
        .end();
    }
  ],

  classes: [
    {
      name: 'DefaultRowView',
      extends: 'foam.u2.View',

      documentation: `
        This is the view that gets rendered for each item in the list.
      `,

      css: `
        ^row {
          background: white;
          padding: 1px 2px;
          font-size: 12px;
        }

        ^row:hover {
          background: #f4f4f9;
          cursor: pointer;
        }
      `,

      properties: [
        {
          name: 'data',
          documentation: 'The selected object.'
        }
      ],

      methods: [
        function initE() {
          return this
            .start()
              .addClass(this.myClass('row'))
              .add(this.data.id)
            .end();
        }
      ]
    },
    {
      name: 'DefaultSelectionView',
      extends: 'foam.u2.Element',

      documentation: `
        This is the view that gets rendered inside the select input. It is put
        to the left of the chevron (the triangle at the far right side of the
        select input). This is an Element instead of a simple string, meaning
        the select input can contain "rich" content like images and make use of
        CSS for styling and layout.
        As an example of why this is useful, imagine you wanted to show a
        dropdown to select a country. You could choose to display the flag of
        the selected country alongside its name after the user makes a
        selection by creating that custom view and providing it in place of this
        one by setting the selectionView property on RichChoiceView.
      `,

      imports: [
        'of'
      ],

      messages: [
        {
          name: 'CHOOSE_FROM',
          message: 'Choose from '
        }
      ],

      properties: [
        {
          name: 'data',
          documentation: 'The id of the selected object.'
        },
        {
          name: 'fullObject',
          documentation: `
            The full object. It's not used here in the default selection view,
            but this property is included to let you know that if you create a
            custom selection view, it will be passed the id of the object (data)
            as well as the full object.
          `
        }
      ],

      methods: [
        function initE() {
          var plural = this.of.model_.plural.toLowerCase();
          return this.add(this.data || this.CHOOSE_FROM + plural);
        }
      ]
    }
  ]
});
<|endoftext|>


<commit_before>String.prototype.format = function() {
    var formatted = this;
    for (var i = 0; i < arguments.length; i++) {
        var regexp = new RegExp('\\{'+i+'\\}', 'gi');
        formatted = formatted.replace(regexp, arguments[i]);
    }
    return formatted;
};

angular.module('fbFilters', [])
.filter('datetime', function() {
    return function(input, fmtString) {
      if(input && input.$date){
        var d = new Date(input.$date);
        if(fmtString && fmtString.length > 0){
          return moment(d).format(fmtString);
        }
        return d.toLocaleDateString() +" "+ d.toLocaleTimeString();
      }else if(input){
        try{
          var d = new Date(input);
          if(fmtString && fmtString.length > 0){
            return moment(d).format(fmtString);
          }
          return d.toLocaleDateString() +" "+ d.toLocaleTimeString();
        }catch(e) {}
      }
      return input;
    }
})
.filter('delivery_time', function() {
    return function(delivered_at, created_at) {
      if(delivered_at && delivered_at.$date){
        var d = new Date(delivered_at.$date);
        var c = new Date(created_at.$date);
        var diffInSec = Math.abs(d-c);
        var hrs = Math.floor(diffInSec/3600000);
        var mins = Math.floor((diffInSec%3600000)/60000);
        if(hrs <= 0){
          return "{0} mins".format(mins);
        }
        return "{0} hr{1} {2} min{3}".format(hrs,hrs>1?'s':'',mins,mins>1?'s':'');
      }
      return delivered_at;
    }
})
.filter('show_food_type', function(){
    return function(input) {
      var veg = ""
      var non_veg = ""

      if(input == 'non-veg'){
        return non_veg
      }else if(input != 'veg'){
        return veg + non_veg
      }
      return veg
    }
})
.filter('currency', function(){
    return function(input) {
      var symbol = "₹"
      return symbol + parseFloat(input).toFixed(2).toString()
    }
})
.filter('show_check_mark', function(){
    return function(input, compare_input) {
      var check_html = ""
      return input == compare_input ? check_html : ""
    }
})
.filter('default_product_img', function(){
    return function(input) {
      return input ? ('/static/images/products/' + input) : '/static/images/na-product.jpg'
    }
})
.filter('default_store_img', function(){
    return function(input) {
      return input ? ('/static/images/resturants/' + input) : '/static/images/na-resturant.jpg'
    }
})
<commit_msg>fix timezone offset display issue in admin page<commit_after>String.prototype.format = function() {
    var formatted = this;
    for (var i = 0; i < arguments.length; i++) {
        var regexp = new RegExp('\\{'+i+'\\}', 'gi');
        formatted = formatted.replace(regexp, arguments[i]);
    }
    return formatted;
};

angular.module('fbFilters', [])
.filter('datetime', function() {
    return function(input, fmtString) {
      if(input && input.$date){
        var d = moment(input.$date).utc();
        if(fmtString && fmtString.length > 0){
          return d.format(fmtString);
        }
        return d.calendar() + '  ' + d.format('DD/MM/YY LT');
      }else if(input){
        try{
          var d = moment(input).utc();
          if(fmtString && fmtString.length > 0){
            return d.format(fmtString);
          }
          return d.calendar() + '  ' + d.format('DD/MM/YY LT');
        }catch(e) {}
      }
      return input;
    }
})
.filter('delivery_time', function() {
    return function(delivered_at, created_at) {
      if(delivered_at && delivered_at.$date){
        var d = new Date(delivered_at.$date);
        var c = new Date(created_at.$date);
        var diffInSec = Math.abs(d-c);
        var hrs = Math.floor(diffInSec/3600000);
        var mins = Math.floor((diffInSec%3600000)/60000);
        if(hrs <= 0){
          return "{0} mins".format(mins);
        }
        return "{0} hr{1} {2} min{3}".format(hrs,hrs>1?'s':'',mins,mins>1?'s':'');
      }
      return delivered_at;
    }
})
.filter('show_food_type', function(){
    return function(input) {
      var veg = ""
      var non_veg = ""

      if(input == 'non-veg'){
        return non_veg
      }else if(input != 'veg'){
        return veg + non_veg
      }
      return veg
    }
})
.filter('currency', function(){
    return function(input) {
      var symbol = "₹"
      return symbol + parseFloat(input).toFixed(2).toString()
    }
})
.filter('show_check_mark', function(){
    return function(input, compare_input) {
      var check_html = ""
      return input == compare_input ? check_html : ""
    }
})
.filter('default_product_img', function(){
    return function(input) {
      return input ? ('/static/images/products/' + input) : '/static/images/na-product.jpg'
    }
})
.filter('default_store_img', function(){
    return function(input) {
      return input ? ('/static/images/resturants/' + input) : '/static/images/na-resturant.jpg'
    }
})
<|endoftext|>


<commit_before>// Copyright 2016, University of Colorado Boulder

/**
 * Base class for light bulbs in the model.
 *
 * @author John Blanco
 * @author Andrew Adare
 */
define( function( require ) {
  'use strict';

  // Modules
  var energyFormsAndChanges = require( 'ENERGY_FORMS_AND_CHANGES/energyFormsAndChanges' );
  var EFACConstants = require( 'ENERGY_FORMS_AND_CHANGES/common/EFACConstants' );
  var EFACModelImage = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/model/EFACModelImage' );
  var EnergyChunk = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyChunk' );
  var EnergyChunkPathMover = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/model/EnergyChunkPathMover' );
  var EnergyType = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyType' );
  var EnergyUser = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/model/EnergyUser' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Random = require( 'DOT/Random' );
  var Vector2 = require( 'DOT/Vector2' );

  // Images
  var ELEMENT_BASE_BACK = require( 'image!ENERGY_FORMS_AND_CHANGES/element_base_back.png' );
  var ELEMENT_BASE_FRONT = require( 'image!ENERGY_FORMS_AND_CHANGES/element_base_front.png' );
  var WIRE_BLACK_62 = require( 'image!ENERGY_FORMS_AND_CHANGES/wire_black_62.png' );
  var WIRE_BLACK_RIGHT = require( 'image!ENERGY_FORMS_AND_CHANGES/wire_black_right.png' );

  // Constants - uncomment as needed
  var WIRE_FLAT_IMAGE = new EFACModelImage( WIRE_BLACK_62, new Vector2( -0.036, -0.04 ) );
  var WIRE_CURVE_IMAGE = new EFACModelImage( WIRE_BLACK_RIGHT, new Vector2( -0.009, -0.016 ) );
  var ELEMENT_BASE_FRONT_IMAGE = new EFACModelImage( ELEMENT_BASE_FRONT, new Vector2( 0, 0.0 ) );
  var ELEMENT_BASE_BACK_IMAGE = new EFACModelImage( ELEMENT_BASE_BACK, new Vector2( 0, 0.0 ) );

  var OFFSET_TO_LEFT_SIDE_OF_WIRE = new Vector2( -0.04, -0.04 );
  var OFFSET_TO_LEFT_SIDE_OF_WIRE_BEND = new Vector2( -0.02, -0.04 );
  var OFFSET_TO_FIRST_WIRE_CURVE_POINT = new Vector2( -0.01, -0.0375 );
  var OFFSET_TO_SECOND_WIRE_CURVE_POINT = new Vector2( -0.001, -0.025 );
  var OFFSET_TO_THIRD_WIRE_CURVE_POINT = new Vector2( -0.0005, -0.0175 );
  var OFFSET_TO_BOTTOM_OF_CONNECTOR = new Vector2( 0, -0.01 );
  var OFFSET_TO_RADIATE_POINT = new Vector2( 0, 0.066 );

  var RADIATED_ENERGY_CHUNK_MAX_DISTANCE = 0.5;
  var RAND = new Random();
  var THERMAL_ENERGY_CHUNK_TIME_ON_FILAMENT = new Range( 2, 2.5 );
  var ENERGY_TO_FULLY_LIGHT = EFACConstants.MAX_ENERGY_PRODUCTION_RATE;
  var LIGHT_CHUNK_LIT_BULB_RADIUS = 0.1; // In meters.
  var LIGHT_CHANGE_RATE = 0.5; // In proportion per second.

  /**
   * @param {Image} iconImage
   * @param {Boolean} hasFilament
   * @param {Property.<boolean>} energyChunksVisibleProperty
   * @constructor
   */
  function LightBulb( iconImage, hasFilament, energyChunksVisibleProperty ) {

    EnergyUser.call( this, iconImage );

    this.hasFilament = hasFilament;
    this.energychunksVisibleProperty = energyChunksVisibleProperty;

    // Fewer thermal energy chunks are radiated for bulbs without a filament.
    this.proportionOfThermalChunksRadiated = hasFilament ? 0.35 : 0.2;

    this.addProperty( 'litProportion', 0 );
    this.electricalEnergyChunkMovers = [];
    this.filamentEnergyChunkMovers = [];
    this.radiatedEnergyChunkMovers = [];
    this.goRightNextTime = true; // @private
  }

  energyFormsAndChanges.register( 'LightBulb', LightBulb );

  return inherit( EnergyUser, LightBulb, {
    /**
     * [step description]
     *
     * @param  {Number} dt timestep
     * @param  {Energy} incomingEnergy
     * @public
     * @override
     */
    step: function( dt, incomingEnergy ) {},

    /**
     *
     *
     * @param  {Number} dt timestep
     * @private
     */
    moveRadiatedEnergyChunks: function( dt ) {},

    /**
     *
     *
     * @param  {Number} dt timestep
     * @private
     */
    moveFilamentEnergyChunks: function( dt ) {},

    /**
     *
     *
     * @param  {Number} dt timestep
     * @private
     */
    moveElectricalEnergyChunks: function( dt ) {},

    /**
     * [preLoadEnergyChunks description]
     *
     * @param  {Energy} incomingEnergy
     * @public
     * @override
     */
    preLoadEnergyChunks: function( incomingEnergy ) {

      this.clearEnergyChunks();

      if ( incomingEnergy.amount < EFACConstants.MAX_ENERGY_PRODUCTION_RATE / 10 ||
        incomingEnergy.type !== EnergyType.ELECTRICAL ) {
        // No energy chunk pre-loading needed.
        return;
      }

      var dt = 1 / EFACConstants.FRAMES_PER_SECOND;
      var energySinceLastChunk = EFACConstants.ENERGY_PER_CHUNK * 0.99;

      // Simulate energy chunks moving through the system.
      var preLoadComplete = false;
      while ( !preLoadComplete ) {
        this.energySinceLastChunk += incomingEnergy.amount * dt;

        // Determine if time to add a new chunk.
        if ( this.energySinceLastChunk >= EFACConstants.ENERGY_PER_CHUNK ) {
          var newEnergyChunk = new EnergyChunk(
            EnergyType.ELECTRICAL,
            this.position.plus( OFFSET_TO_LEFT_SIDE_OF_WIRE ),
            this.energyChunksVisibleProperty );

          this.energyChunkList.push( newEnergyChunk );
          // Add a 'mover' for this energy chunk.
          // And a "mover" that will move this energy chunk through
          // the wire to the heating element.
          this.electricalEnergyChunkMovers.push( new EnergyChunkPathMover( newEnergyChunk,
            this.createElectricalEnergyChunkPath( this.position ),
            EFACConstants.ENERGY_CHUNK_VELOCITY ) );

          // Update energy since last chunk.
          energySinceLastChunk = energySinceLastChunk - EFACConstants.ENERGY_PER_CHUNK;
        }

        this.moveElectricalEnergyChunks( dt );
        this.moveFilamentEnergyChunks( dt );

        if ( this.radiatedEnergyChunkMovers.length > 1 ) {
          // A couple of chunks are radiating, which completes the pre-load.
          preLoadComplete = true;
        }
      }
    },

    /**
     * @param  {EnergyChunk} energyChunk
     * @private
     */
    radiateEnergyChunk: function( energyChunk ) {
      if ( RAND.nextDouble() > this.proportionOfThermalChunksRadiated ) {
        energyChunk.energyType.set( EnergyType.LIGHT );
      } else {
        energyChunk.energyType.set( EnergyType.THERMAL );
      }

      // Path of radiated light chunks
      var path = [];

      path.push( this.position
        .plus( OFFSET_TO_RADIATE_POINT )
        .plus( new Vector2( 0, RADIATED_ENERGY_CHUNK_MAX_DISTANCE )
          .rotated( ( RAND.nextDouble() - 0.5 ) * ( Math.PI / 2 ) ) ) );

      this.radiatedEnergyChunkMovers.push( new EnergyChunkPathMover(
        energyChunk,
        path,
        EFACConstants.ENERGY_CHUNK_VELOCITY ) );
    },

    /**
     * @param  {Vector2} startingPoint
     *
     * @return {Vector2[]}
     * @private
     */
    createThermalEnergyChunkPath: function( startingPoint ) {
      var path = [];
      var filamentWidth = 0.03;
      var x = ( 0.5 + RAND.nextDouble() / 2 ) * filamentWidth / 2 * ( this.goRightNextTime ? 1 : -1 );

      path.push( new Vector2( x, 0 ) );
      this.goRightNextTime = !this.goRightNextTime;

      return path;
    },

    /**
     * @param  {Vector2} center
     *
     * @return {Vector2[]}
     * @private
     */
    createElectricalEnergyChunkPath: function( center ) {
      var path = [];

      path.push( center.plus( OFFSET_TO_LEFT_SIDE_OF_WIRE_BEND ) );
      path.push( center.plus( OFFSET_TO_FIRST_WIRE_CURVE_POINT ) );
      path.push( center.plus( OFFSET_TO_SECOND_WIRE_CURVE_POINT ) );
      path.push( center.plus( OFFSET_TO_THIRD_WIRE_CURVE_POINT ) );
      path.push( center.plus( OFFSET_TO_BOTTOM_OF_CONNECTOR ) );
      path.push( center.plus( OFFSET_TO_RADIATE_POINT ) );

      return path;
    },

    /**
     * @return {Number} time
     * @private
     */
    generateThermalChunkTimeOnFilament: function() {
      return THERMAL_ENERGY_CHUNK_TIME_ON_FILAMENT.min +
        RAND.nextDouble() * THERMAL_ENERGY_CHUNK_TIME_ON_FILAMENT.getLength();
    },

    /**
     * @param {Vector2} startingLocation
     * @param {Vector2[]} pathPoints
     * @return {Number}
     * @private
     */
    getTotalPathLength: function( startingLocation, pathPoints ) {
      if ( pathPoints.length === 0 ) {
        return 0;
      }

      var pathLength = startingLocation.distance( pathPoints[ 0 ] );
      for ( var i = 0; i < pathPoints.length - 1; i++ ) {
        pathLength += pathPoints[ i ].distance( pathPoints[ i + 1 ] );
      }

      return pathLength;
    },

    /**
     * Deactivate the light bulb.
     * @public
     * @override
     */
    deactivate: function() {
      EnergyUser.prototype.deactivate.call( this );
      this.litProportionProperty.set( 0 );
    },

    /**
     * @public
     * @override
     */
    clearEnergyChunks: function() {
      EnergyUser.prototype.clearEnergyChunks.call( this );
      this.electricalEnergyChunkMovers.length = 0;
      this.filamentEnergyChunkMovers.length = 0;
      this.radiatedEnergyChunkMovers.length = 0;
    }

  }, {
    // Export module-scope consts for static access
    WIRE_BLACK_RIGHT: WIRE_BLACK_RIGHT,
    WIRE_BLACK_62: WIRE_BLACK_62,
    ELEMENT_BASE_FRONT: ELEMENT_BASE_FRONT,
    ELEMENT_BASE_BACK: ELEMENT_BASE_BACK,

    WIRE_FLAT_IMAGE: WIRE_FLAT_IMAGE,
    WIRE_CURVE_IMAGE: WIRE_CURVE_IMAGE,
    ELEMENT_BASE_FRONT_IMAGE: ELEMENT_BASE_FRONT_IMAGE,
    ELEMENT_BASE_BACK_IMAGE: ELEMENT_BASE_BACK_IMAGE
  } );
} );

<commit_msg>Port code to move various types of energy chunks<commit_after>// Copyright 2016, University of Colorado Boulder

/**
 * Base class for light bulbs in the model.
 *
 * @author John Blanco
 * @author Andrew Adare
 */
define( function( require ) {
  'use strict';

  // Modules
  var energyFormsAndChanges = require( 'ENERGY_FORMS_AND_CHANGES/energyFormsAndChanges' );
  var EFACConstants = require( 'ENERGY_FORMS_AND_CHANGES/common/EFACConstants' );
  var EFACModelImage = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/model/EFACModelImage' );
  var EnergyChunk = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyChunk' );
  var EnergyChunkPathMover = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/model/EnergyChunkPathMover' );
  var EnergyType = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyType' );
  var EnergyUser = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/model/EnergyUser' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Random = require( 'DOT/Random' );
  var Vector2 = require( 'DOT/Vector2' );

  // Images
  var ELEMENT_BASE_BACK = require( 'image!ENERGY_FORMS_AND_CHANGES/element_base_back.png' );
  var ELEMENT_BASE_FRONT = require( 'image!ENERGY_FORMS_AND_CHANGES/element_base_front.png' );
  var WIRE_BLACK_62 = require( 'image!ENERGY_FORMS_AND_CHANGES/wire_black_62.png' );
  var WIRE_BLACK_RIGHT = require( 'image!ENERGY_FORMS_AND_CHANGES/wire_black_right.png' );

  // Constants - uncomment as needed
  var WIRE_FLAT_IMAGE = new EFACModelImage( WIRE_BLACK_62, new Vector2( -0.036, -0.04 ) );
  var WIRE_CURVE_IMAGE = new EFACModelImage( WIRE_BLACK_RIGHT, new Vector2( -0.009, -0.016 ) );
  var ELEMENT_BASE_FRONT_IMAGE = new EFACModelImage( ELEMENT_BASE_FRONT, new Vector2( 0, 0.0 ) );
  var ELEMENT_BASE_BACK_IMAGE = new EFACModelImage( ELEMENT_BASE_BACK, new Vector2( 0, 0.0 ) );

  var OFFSET_TO_LEFT_SIDE_OF_WIRE = new Vector2( -0.04, -0.04 );
  var OFFSET_TO_LEFT_SIDE_OF_WIRE_BEND = new Vector2( -0.02, -0.04 );
  var OFFSET_TO_FIRST_WIRE_CURVE_POINT = new Vector2( -0.01, -0.0375 );
  var OFFSET_TO_SECOND_WIRE_CURVE_POINT = new Vector2( -0.001, -0.025 );
  var OFFSET_TO_THIRD_WIRE_CURVE_POINT = new Vector2( -0.0005, -0.0175 );
  var OFFSET_TO_BOTTOM_OF_CONNECTOR = new Vector2( 0, -0.01 );
  var OFFSET_TO_RADIATE_POINT = new Vector2( 0, 0.066 );

  var RADIATED_ENERGY_CHUNK_MAX_DISTANCE = 0.5;
  var RAND = new Random();
  var THERMAL_ENERGY_CHUNK_TIME_ON_FILAMENT = new Range( 2, 2.5 );
  var ENERGY_TO_FULLY_LIGHT = EFACConstants.MAX_ENERGY_PRODUCTION_RATE;
  var LIGHT_CHUNK_LIT_BULB_RADIUS = 0.1; // In meters.
  var LIGHT_CHANGE_RATE = 0.5; // In proportion per second.

  /**
   * @param {Image} iconImage
   * @param {Boolean} hasFilament
   * @param {Property.<boolean>} energyChunksVisibleProperty
   * @constructor
   */
  function LightBulb( iconImage, hasFilament, energyChunksVisibleProperty ) {

    EnergyUser.call( this, iconImage );

    this.hasFilament = hasFilament;
    this.energychunksVisibleProperty = energyChunksVisibleProperty;

    // Fewer thermal energy chunks are radiated for bulbs without a filament.
    this.proportionOfThermalChunksRadiated = hasFilament ? 0.35 : 0.2;

    this.addProperty( 'litProportion', 0 );
    this.electricalEnergyChunkMovers = [];
    this.filamentEnergyChunkMovers = [];
    this.radiatedEnergyChunkMovers = [];
    this.goRightNextTime = true; // @private
  }

  energyFormsAndChanges.register( 'LightBulb', LightBulb );

  return inherit( EnergyUser, LightBulb, {

    /**
     * @param  {Number} dt - timestep
     * @param  {Energy} incomingEnergy
     * @public
     * @override
     */
    step: function( dt, incomingEnergy ) {},

    /**
     * Utility method to remove object from array.
     *
     * @param  {*[]} list
     * @param  {*} element
     * @private
     */
    removeElement: function( list, element ) {
      _.remove( list, function( e ) {
        return e === element;
      } );
    },

    /**
     *
     *
     * @param  {Number} dt - timestep
     * @private
     */
    moveRadiatedEnergyChunks: function( dt ) {
      // Iterate over a copy to mutate original without problems
      var movers = _.clone( this.radiatedEnergyChunkMovers );

      var self = this;

      movers.forEach( function( mover ) {
        mover.moveAlongPath( dt );

        // Remove the chunk and its mover.
        if ( mover.pathFullyTraversed ) {
          self.removeElement( self.energyChunkList, mover.energyChunk );
          self.removeElement( self.radiatedEnergyChunkMovers, mover );
        }
      } );
    },

    /**
     *
     *
     * @param  {Number} dt - timestep
     * @private
     */
    moveFilamentEnergyChunks: function( dt ) {
      // Iterate over a copy to mutate original without problems
      var movers = _.clone( this.filamentEnergyChunkMovers );

      var self = this;

      movers.forEach( function( mover ) {
        mover.moveAlongPath( dt );

        // Cause this energy chunk to be radiated from the bulb.
        if ( mover.pathFullyTraversed ) {
          self.removeElement( self.filamentEnergyChunkMovers, mover );
          self.radiateEnergyChunk( mover.energyChunk );
        }
      } );
    },

    /**
     *
     *
     * @param  {Number} dt - timestep
     * @private
     */
    moveElectricalEnergyChunks: function( dt ) {

      // Iterate over a copy to mutate original without problems
      var movers = _.clone( this.electricalEnergyChunkMovers );

      var self = this;

      movers.forEach( function( mover ) {
        mover.moveAlongPath( dt );

        if ( mover.pathFullyTraversed ) {
          self.removeElement( self.electricalEnergyChunkMovers, mover );

          // Turn this energy chunk into thermal energy on the filament.
          if ( self.hasFilament ) {
            mover.energyChunk.energyType.set( EnergyType.THERMAL );
            var path = self.createThermalEnergyChunkPath( mover.energyChunk.position );
            var speed = self.getTotalPathLength( mover.energyChunk.position, path ) /
              self.generateThermalChunkTimeOnFilament();

            self.filamentEnergyChunkMovers.push( new EnergyChunkPathMover( mover.energyChunk, path, speed ) );
          } else {
            // There is no filament, so just radiate the chunk.
            self.radiateEnergyChunk( mover.energyChunk );
          }
        }
      } );
    },

    /**
     * [preLoadEnergyChunks description]
     *
     * @param  {Energy} incomingEnergy
     * @public
     * @override
     */
    preLoadEnergyChunks: function( incomingEnergy ) {

      this.clearEnergyChunks();

      if ( incomingEnergy.amount < EFACConstants.MAX_ENERGY_PRODUCTION_RATE / 10 ||
        incomingEnergy.type !== EnergyType.ELECTRICAL ) {
        // No energy chunk pre-loading needed.
        return;
      }

      var dt = 1 / EFACConstants.FRAMES_PER_SECOND;
      var energySinceLastChunk = EFACConstants.ENERGY_PER_CHUNK * 0.99;

      // Simulate energy chunks moving through the system.
      var preLoadComplete = false;
      while ( !preLoadComplete ) {
        this.energySinceLastChunk += incomingEnergy.amount * dt;

        // Determine if time to add a new chunk.
        if ( this.energySinceLastChunk >= EFACConstants.ENERGY_PER_CHUNK ) {
          var newEnergyChunk = new EnergyChunk(
            EnergyType.ELECTRICAL,
            this.position.plus( OFFSET_TO_LEFT_SIDE_OF_WIRE ),
            this.energyChunksVisibleProperty );

          this.energyChunkList.push( newEnergyChunk );
          // Add a 'mover' for this energy chunk.
          // And a "mover" that will move this energy chunk through
          // the wire to the heating element.
          this.electricalEnergyChunkMovers.push( new EnergyChunkPathMover( newEnergyChunk,
            this.createElectricalEnergyChunkPath( this.position ),
            EFACConstants.ENERGY_CHUNK_VELOCITY ) );

          // Update energy since last chunk.
          energySinceLastChunk = energySinceLastChunk - EFACConstants.ENERGY_PER_CHUNK;
        }

        this.moveElectricalEnergyChunks( dt );
        this.moveFilamentEnergyChunks( dt );

        if ( this.radiatedEnergyChunkMovers.length > 1 ) {
          // A couple of chunks are radiating, which completes the pre-load.
          preLoadComplete = true;
        }
      }
    },

    /**
     * @param  {EnergyChunk} energyChunk
     * @private
     */
    radiateEnergyChunk: function( energyChunk ) {
      if ( RAND.nextDouble() > this.proportionOfThermalChunksRadiated ) {
        energyChunk.energyType.set( EnergyType.LIGHT );
      } else {
        energyChunk.energyType.set( EnergyType.THERMAL );
      }

      // Path of radiated light chunks
      var path = [];

      path.push( this.position
        .plus( OFFSET_TO_RADIATE_POINT )
        .plus( new Vector2( 0, RADIATED_ENERGY_CHUNK_MAX_DISTANCE )
          .rotated( ( RAND.nextDouble() - 0.5 ) * ( Math.PI / 2 ) ) ) );

      this.radiatedEnergyChunkMovers.push( new EnergyChunkPathMover(
        energyChunk,
        path,
        EFACConstants.ENERGY_CHUNK_VELOCITY ) );
    },

    /**
     * @param  {Vector2} startingPoint
     *
     * @return {Vector2[]}
     * @private
     */
    createThermalEnergyChunkPath: function( startingPoint ) {
      var path = [];
      var filamentWidth = 0.03;
      var x = ( 0.5 + RAND.nextDouble() / 2 ) * filamentWidth / 2 * ( this.goRightNextTime ? 1 : -1 );

      path.push( new Vector2( x, 0 ) );
      this.goRightNextTime = !this.goRightNextTime;

      return path;
    },

    /**
     * @param  {Vector2} center
     *
     * @return {Vector2[]}
     * @private
     */
    createElectricalEnergyChunkPath: function( center ) {
      var path = [];

      path.push( center.plus( OFFSET_TO_LEFT_SIDE_OF_WIRE_BEND ) );
      path.push( center.plus( OFFSET_TO_FIRST_WIRE_CURVE_POINT ) );
      path.push( center.plus( OFFSET_TO_SECOND_WIRE_CURVE_POINT ) );
      path.push( center.plus( OFFSET_TO_THIRD_WIRE_CURVE_POINT ) );
      path.push( center.plus( OFFSET_TO_BOTTOM_OF_CONNECTOR ) );
      path.push( center.plus( OFFSET_TO_RADIATE_POINT ) );

      return path;
    },

    /**
     * @return {Number} time
     * @private
     */
    generateThermalChunkTimeOnFilament: function() {
      return THERMAL_ENERGY_CHUNK_TIME_ON_FILAMENT.min +
        RAND.nextDouble() * THERMAL_ENERGY_CHUNK_TIME_ON_FILAMENT.getLength();
    },

    /**
     * @param {Vector2} startingLocation
     * @param {Vector2[]} pathPoints
     * @return {Number}
     * @private
     */
    getTotalPathLength: function( startingLocation, pathPoints ) {
      if ( pathPoints.length === 0 ) {
        return 0;
      }

      var pathLength = startingLocation.distance( pathPoints[ 0 ] );
      for ( var i = 0; i < pathPoints.length - 1; i++ ) {
        pathLength += pathPoints[ i ].distance( pathPoints[ i + 1 ] );
      }

      return pathLength;
    },

    /**
     * Deactivate the light bulb.
     * @public
     * @override
     */
    deactivate: function() {
      EnergyUser.prototype.deactivate.call( this );
      this.litProportionProperty.set( 0 );
    },

    /**
     * @public
     * @override
     */
    clearEnergyChunks: function() {
      EnergyUser.prototype.clearEnergyChunks.call( this );
      this.electricalEnergyChunkMovers.length = 0;
      this.filamentEnergyChunkMovers.length = 0;
      this.radiatedEnergyChunkMovers.length = 0;
    }

  }, {
    // Export module-scope consts for static access
    WIRE_BLACK_RIGHT: WIRE_BLACK_RIGHT,
    WIRE_BLACK_62: WIRE_BLACK_62,
    ELEMENT_BASE_FRONT: ELEMENT_BASE_FRONT,
    ELEMENT_BASE_BACK: ELEMENT_BASE_BACK,

    WIRE_FLAT_IMAGE: WIRE_FLAT_IMAGE,
    WIRE_CURVE_IMAGE: WIRE_CURVE_IMAGE,
    ELEMENT_BASE_FRONT_IMAGE: ELEMENT_BASE_FRONT_IMAGE,
    ELEMENT_BASE_BACK_IMAGE: ELEMENT_BASE_BACK_IMAGE
  } );
} );

<|endoftext|>


<commit_before>
var waiting = setInterval(function(){ //run every 1sec until we have loaded the page (dont hate me Sam)

if (typeof user != "undefined")
{

var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
        if (xhttp.readyState == 4 && xhttp.status == 200) {
            results = JSON.parse(xhttp.responseText);
var ul = document.getElementsByClassName("nav navbar-nav");

var li = document.createElement("li");
li.classList.add("dropdown");

var tonight = new Date();

tonight = new Date(tonight.getTime() + ( tonight.getTimezoneOffset() * 60000 )); //DST offset because beacon has stupid times

tonight.setHours(23,59,59,0);

var thismorning = new Date();
thismorning.setDate(thismorning.getDate()); //then

thismorning = new Date(thismorning.getTime() + ( thismorning.getTimezoneOffset() * 60000 )); //DST offset because beacon has stupid times

thismorning.setHours(0,0,0,0);

var vars = "?host="+location.hostname+"&hq="+user.headquartersId+"&start="+encodeURIComponent(thismorning.toISOString())+"&end="+encodeURIComponent(tonight.toISOString());

var jobsummaryUrl = lighthouseUrl+"lighthouse/summary.html"+vars;
var jobstatsUrl = lighthouseUrl+"lighthouse/stats.html"+vars;
var jobexportUrl = lighthouseUrl+"lighthouse/advexport.html"+vars;

var teamsummaryUrl = lighthouseUrl+"lighthouse/teamsummary.html"+vars;

var aboutURL = "https://github.com/OSPFNeighbour/Lighthouse/blob/master/README.md"//chrome.extension.getURL("lighthouse/about.html");

li.innerHTML =     "Lighthouse

Jobs

Job Summary ("+results.Code+" Today)

Job Statistics ("+results.Code+" Today)

Job Export ("+results.Code+" Today)

Teams

Team Summary ("+results.Code+" Today)

About

About Lighthouse
";

ul[0].appendChild(li);

clearInterval(waiting);

        }
    }

    xhttp.open("GET", "https://"+location.hostname+"/Api/v1/Entities/" + user.headquartersId, true);
    xhttp.send();

}

}, 1000)<commit_msg>fixed multiple icon bug<commit_after>
var waiting = setInterval(function(){ //run every 1sec until we have loaded the page (dont hate me Sam)

if (typeof user != "undefined")
{
clearInterval(waiting); //stop timer
var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
        if (xhttp.readyState == 4 && xhttp.status == 200) {
            results = JSON.parse(xhttp.responseText);
var ul = document.getElementsByClassName("nav navbar-nav");

var li = document.createElement("li");
li.classList.add("dropdown");

var tonight = new Date();

tonight = new Date(tonight.getTime() + ( tonight.getTimezoneOffset() * 60000 )); //DST offset because beacon has stupid times

tonight.setHours(23,59,59,0);

var thismorning = new Date();
thismorning.setDate(thismorning.getDate()); //then

thismorning = new Date(thismorning.getTime() + ( thismorning.getTimezoneOffset() * 60000 )); //DST offset because beacon has stupid times

thismorning.setHours(0,0,0,0);

var vars = "?host="+location.hostname+"&hq="+user.headquartersId+"&start="+encodeURIComponent(thismorning.toISOString())+"&end="+encodeURIComponent(tonight.toISOString());

var jobsummaryUrl = lighthouseUrl+"lighthouse/summary.html"+vars;
var jobstatsUrl = lighthouseUrl+"lighthouse/stats.html"+vars;
var jobexportUrl = lighthouseUrl+"lighthouse/advexport.html"+vars;

var teamsummaryUrl = lighthouseUrl+"lighthouse/teamsummary.html"+vars;

var aboutURL = "https://github.com/OSPFNeighbour/Lighthouse/blob/master/README.md"//chrome.extension.getURL("lighthouse/about.html");

li.innerHTML =     "Lighthouse

Jobs

Job Summary ("+results.Code+" Today)

Job Statistics ("+results.Code+" Today)

Job Export ("+results.Code+" Today)

Teams

Team Summary ("+results.Code+" Today)

About

About Lighthouse
";

ul[0].appendChild(li);

        }
    }

    xhttp.open("GET", "https://"+location.hostname+"/Api/v1/Entities/" + user.headquartersId, true);
    xhttp.send();

}

}, 1000)<|endoftext|>


<commit_before>// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
'use strict';

var widget = require('./widget');
var utils = require('./utils');
var _ = require('underscore');

var BoxModel = widget.DOMWidgetModel.extend({
    defaults: _.extend({}, widget.DOMWidgetModel.prototype.defaults, {
        _view_name: 'BoxView',
        _model_name: 'BoxModel',
        children: [],
        box_style: '',
        overflow_x: '',
        overflow_y: ''
    })
}, {
    serializers: _.extend({
        children: {deserialize: widget.unpack_models},
    }, widget.DOMWidgetModel.serializers)
});

var ProxyModel = widget.DOMWidgetModel.extend({
    defaults: _.extend({}, widget.DOMWidgetModel.prototype.defaults, {
        _view_name: 'ProxyView',
        _model_name: 'ProxyModel',
        child: null
    })
}, {
    serializers: _.extend({
        child: {deserialize: widget.unpack_models}
    }, widget.DOMWidgetModel.serializers)
});

var ProxyView = widget.DOMWidgetView.extend({
    initialize: function() {
        // Public constructor
        ProxyView.__super__.initialize.apply(this, arguments);
        this.el.classList.add('jupyter-widgets');
        this.el.classList.add('widget-container');
        this.box = this.el;
        this.child_promise = Promise.resolve();
    },

    render: function() {
        var that = this;
        var child_view = this.set_child(this.model.get('child'));
        this.listenTo(this.model, 'change:child', function(model, value) {
            this.set_child(value);
        });
        return child_view;
    },

    remove: function() {
        ProxyView.__super__.remove.apply(this, arguments);
        var that = this;
        this.child_promise.then(function() {
            if (that.child) {
                that.child.remove();
            }
        });
    },

    set_child: function(value) {
        if (this.child) {
            this.child.remove();
        }
        if (value) {
            var that = this;
            this.child_promise = this.child_promise.then(function() {
                return that.create_child_view(value).then(function(view) {
                    if (!that.box) {
                        console.error('Widget place holder does not exist');
                        return;
                    }
                    while (that.box.firstChild) {
                        that.box.removeChild(that.box.firstChild);
                    }
                    that.box.appendChild(view.el);

                    // Trigger the displayed event of the child view.
                    that.displayed.then(function() {
                        view.trigger('displayed', that);
                    });
                    that.child = view;
                    that.trigger('child:created');
                }).catch(utils.reject('Couldn\'t add child view to proxy', true));
            });
        }
        return this.child_promise;
    },

    /**
     * Set a CSS attr of the view
     * @param  {string} name
     * @param  {object} value
     */
    update_attr: function(name, value) { // TODO: Deprecated in 5.0
        this.box.style[name] = value;
    }
});

var PlaceProxyModel = ProxyModel.extend({
    defaults: _.extend({}, ProxyModel.prototype.defaults, {
        _view_name: 'PlaceProxyView',
        _model_name: 'PlaceProxyModel',
        selector: ''
    })
});

var PlaceProxyView = ProxyView.extend({
    initialize: function() {
        PlaceProxyView.__super__.initialize.apply(this, arguments);
        this.update_selector(this.model, this.model.get('selector'));
        this.listenTo(this.model, 'change:selector', this.update_selector);
    },

    update_selector: function(model, selector) {
        this.box = selector && document.querySelector(selector) || this.el;
        this.set_child(this.model.get('child'));
    }
});

var BoxView = widget.DOMWidgetView.extend({
    initialize: function() {
        /**
         * Public constructor
         */
        BoxView.__super__.initialize.apply(this, arguments);
        this.children_views = new widget.ViewList(this.add_child_model, null, this);
        this.listenTo(this.model, 'change:children', function(model, value) {
            this.children_views.update(value);
        }, this);
        this.listenTo(this.model, 'change:overflow_x', this.update_overflow_x, this);
        this.listenTo(this.model, 'change:overflow_y', this.update_overflow_y, this);
        this.listenTo(this.model, 'change:box_style', this.update_box_style, this);
    },

    update_attr: function(name, value) { // TODO: Deprecated in 5.0
        /**
         * Set a css attr of the widget view.
         */
        this.box.style[name] = value;
    },

    render: function() {
        /**
         * Called when view is rendered.
         */
        this.el.classList.add('jupyter-widgets');
        this.el.classList.add('widget-container');
        this.el.classList.add('widget-box');
        this.box = this.el;

        this.children_views.update(this.model.get('children'));
        this.update_overflow_x();
        this.update_overflow_y();
        this.update_box_style();
    },

    update_overflow_x: function() {
        /**
         * Called when the x-axis overflow setting is changed.
         */
        this.box.style.overflowX = this.model.get('overflow_x');
    },

    update_overflow_y: function() {
        /**
         * Called when the y-axis overflow setting is changed.
         */
        this.box.style.overflowY = this.model.get('overflow_y');
    },

    update_box_style: function() {
        var class_map = {
            success: ['alert', 'alert-success'],
            info: ['alert', 'alert-info'],
            warning: ['alert', 'alert-warning'],
            danger: ['alert', 'alert-danger']
        };
        this.update_mapped_classes(class_map, 'box_style', this.box);
    },

    add_child_model: function(model) {
        /**
         * Called when a model is added to the children list.
         */
        var that = this;
        // we insert a dummy element so the order is preserved when we add
        // the rendered content later.
        var dummy = document.createElement('div');
        that.box.appendChild(dummy);

        return this.create_child_view(model).then(function(view) {
            that.box.replaceChild(view.el, dummy);

            // Trigger the displayed event of the child view.
            that.displayed.then(function() {
                view.trigger('displayed', that);
            });
            return view;
        }).catch(utils.reject('Couldn\'t add child view to box', true));
    },

    remove: function() {
        /**
         * We remove this widget before removing the children as an optimization
         * we want to remove the entire container from the DOM first before
         * removing each individual child separately.
         */
        BoxView.__super__.remove.apply(this, arguments);
        this.children_views.remove();
    }
});

var FlexBoxModel = BoxModel.extend({ // TODO: Deprecated in 5.0 (entire model)
    defaults: _.extend({}, BoxModel.prototype.defaults, {
        _view_name: 'FlexBoxView',
        _model_name: 'FlexBoxModel',
        orientation: 'vertical',
        pack: 'start',
        alignt: 'start'
    })
});

var FlexBoxView = BoxView.extend({ // TODO: Deprecated in 5.0 (entire view)
    render: function() {
        FlexBoxView.__super__.render.apply(this);
        this.listenTo(this.model, 'change:orientation', this.update_orientation, this);
        this.listenTo(this.model, 'change:flex', this._flex_changed, this);
        this.listenTo(this.model, 'change:pack', this._pack_changed, this);
        this.listenTo(this.model, 'change:align', this._align_changed, this);
        this._flex_changed();
        this._pack_changed();
        this._align_changed();
        this.update_orientation();
    },

    update_orientation: function() {
        var orientation = this.model.get('orientation');
        if (orientation == 'vertical') {
            this.box.classList.remove('hbox');
            this.box.classList.add(vbox);
        } else {
            this.box.classList.remove('vbox');
            this.box.classList.add('hbox');
        }
    },

    _flex_changed: function() {
        if (this.model.previous('flex')) {
            this.box.classList.remove('box-flex' + this.model.previous('flex'));
        }
        this.box.classList.add('box-flex' + this.model.get('flex'));
    },

    _pack_changed: function() {
        if (this.model.previous('pack')) {
            this.box.classList.remove(this.model.previous('pack'));
        }
        this.box.classList.add(this.model.get('pack'));
    },

    _align_changed: function() {
        if (this.model.previous('align')) {
            this.box.classList.remove('align-' + this.model.previous('align'));
        }
        this.box.classList.add('align-' + this.model.get('align'));
    }
});

module.exports = {
    BoxModel: BoxModel,
    BoxView: BoxView,
    FlexBoxModel: FlexBoxModel, // TODO: Deprecated in 5.0
    FlexBoxView: FlexBoxView, // TODO: Deprecated in 5.0
    ProxyModel: ProxyModel,
    ProxyView: ProxyView,
    PlaceProxyModel: PlaceProxyModel,
    PlaceProxyView: PlaceProxyView
};
<commit_msg>Class vbox should be wrapped in quotes.<commit_after>// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
'use strict';

var widget = require('./widget');
var utils = require('./utils');
var _ = require('underscore');

var BoxModel = widget.DOMWidgetModel.extend({
    defaults: _.extend({}, widget.DOMWidgetModel.prototype.defaults, {
        _view_name: 'BoxView',
        _model_name: 'BoxModel',
        children: [],
        box_style: '',
        overflow_x: '',
        overflow_y: ''
    })
}, {
    serializers: _.extend({
        children: {deserialize: widget.unpack_models},
    }, widget.DOMWidgetModel.serializers)
});

var ProxyModel = widget.DOMWidgetModel.extend({
    defaults: _.extend({}, widget.DOMWidgetModel.prototype.defaults, {
        _view_name: 'ProxyView',
        _model_name: 'ProxyModel',
        child: null
    })
}, {
    serializers: _.extend({
        child: {deserialize: widget.unpack_models}
    }, widget.DOMWidgetModel.serializers)
});

var ProxyView = widget.DOMWidgetView.extend({
    initialize: function() {
        // Public constructor
        ProxyView.__super__.initialize.apply(this, arguments);
        this.el.classList.add('jupyter-widgets');
        this.el.classList.add('widget-container');
        this.box = this.el;
        this.child_promise = Promise.resolve();
    },

    render: function() {
        var that = this;
        var child_view = this.set_child(this.model.get('child'));
        this.listenTo(this.model, 'change:child', function(model, value) {
            this.set_child(value);
        });
        return child_view;
    },

    remove: function() {
        ProxyView.__super__.remove.apply(this, arguments);
        var that = this;
        this.child_promise.then(function() {
            if (that.child) {
                that.child.remove();
            }
        });
    },

    set_child: function(value) {
        if (this.child) {
            this.child.remove();
        }
        if (value) {
            var that = this;
            this.child_promise = this.child_promise.then(function() {
                return that.create_child_view(value).then(function(view) {
                    if (!that.box) {
                        console.error('Widget place holder does not exist');
                        return;
                    }
                    while (that.box.firstChild) {
                        that.box.removeChild(that.box.firstChild);
                    }
                    that.box.appendChild(view.el);

                    // Trigger the displayed event of the child view.
                    that.displayed.then(function() {
                        view.trigger('displayed', that);
                    });
                    that.child = view;
                    that.trigger('child:created');
                }).catch(utils.reject('Couldn\'t add child view to proxy', true));
            });
        }
        return this.child_promise;
    },

    /**
     * Set a CSS attr of the view
     * @param  {string} name
     * @param  {object} value
     */
    update_attr: function(name, value) { // TODO: Deprecated in 5.0
        this.box.style[name] = value;
    }
});

var PlaceProxyModel = ProxyModel.extend({
    defaults: _.extend({}, ProxyModel.prototype.defaults, {
        _view_name: 'PlaceProxyView',
        _model_name: 'PlaceProxyModel',
        selector: ''
    })
});

var PlaceProxyView = ProxyView.extend({
    initialize: function() {
        PlaceProxyView.__super__.initialize.apply(this, arguments);
        this.update_selector(this.model, this.model.get('selector'));
        this.listenTo(this.model, 'change:selector', this.update_selector);
    },

    update_selector: function(model, selector) {
        this.box = selector && document.querySelector(selector) || this.el;
        this.set_child(this.model.get('child'));
    }
});

var BoxView = widget.DOMWidgetView.extend({
    initialize: function() {
        /**
         * Public constructor
         */
        BoxView.__super__.initialize.apply(this, arguments);
        this.children_views = new widget.ViewList(this.add_child_model, null, this);
        this.listenTo(this.model, 'change:children', function(model, value) {
            this.children_views.update(value);
        }, this);
        this.listenTo(this.model, 'change:overflow_x', this.update_overflow_x, this);
        this.listenTo(this.model, 'change:overflow_y', this.update_overflow_y, this);
        this.listenTo(this.model, 'change:box_style', this.update_box_style, this);
    },

    update_attr: function(name, value) { // TODO: Deprecated in 5.0
        /**
         * Set a css attr of the widget view.
         */
        this.box.style[name] = value;
    },

    render: function() {
        /**
         * Called when view is rendered.
         */
        this.el.classList.add('jupyter-widgets');
        this.el.classList.add('widget-container');
        this.el.classList.add('widget-box');
        this.box = this.el;

        this.children_views.update(this.model.get('children'));
        this.update_overflow_x();
        this.update_overflow_y();
        this.update_box_style();
    },

    update_overflow_x: function() {
        /**
         * Called when the x-axis overflow setting is changed.
         */
        this.box.style.overflowX = this.model.get('overflow_x');
    },

    update_overflow_y: function() {
        /**
         * Called when the y-axis overflow setting is changed.
         */
        this.box.style.overflowY = this.model.get('overflow_y');
    },

    update_box_style: function() {
        var class_map = {
            success: ['alert', 'alert-success'],
            info: ['alert', 'alert-info'],
            warning: ['alert', 'alert-warning'],
            danger: ['alert', 'alert-danger']
        };
        this.update_mapped_classes(class_map, 'box_style', this.box);
    },

    add_child_model: function(model) {
        /**
         * Called when a model is added to the children list.
         */
        var that = this;
        // we insert a dummy element so the order is preserved when we add
        // the rendered content later.
        var dummy = document.createElement('div');
        that.box.appendChild(dummy);

        return this.create_child_view(model).then(function(view) {
            that.box.replaceChild(view.el, dummy);

            // Trigger the displayed event of the child view.
            that.displayed.then(function() {
                view.trigger('displayed', that);
            });
            return view;
        }).catch(utils.reject('Couldn\'t add child view to box', true));
    },

    remove: function() {
        /**
         * We remove this widget before removing the children as an optimization
         * we want to remove the entire container from the DOM first before
         * removing each individual child separately.
         */
        BoxView.__super__.remove.apply(this, arguments);
        this.children_views.remove();
    }
});

var FlexBoxModel = BoxModel.extend({ // TODO: Deprecated in 5.0 (entire model)
    defaults: _.extend({}, BoxModel.prototype.defaults, {
        _view_name: 'FlexBoxView',
        _model_name: 'FlexBoxModel',
        orientation: 'vertical',
        pack: 'start',
        alignt: 'start'
    })
});

var FlexBoxView = BoxView.extend({ // TODO: Deprecated in 5.0 (entire view)
    render: function() {
        FlexBoxView.__super__.render.apply(this);
        this.listenTo(this.model, 'change:orientation', this.update_orientation, this);
        this.listenTo(this.model, 'change:flex', this._flex_changed, this);
        this.listenTo(this.model, 'change:pack', this._pack_changed, this);
        this.listenTo(this.model, 'change:align', this._align_changed, this);
        this._flex_changed();
        this._pack_changed();
        this._align_changed();
        this.update_orientation();
    },

    update_orientation: function() {
        var orientation = this.model.get('orientation');
        if (orientation == 'vertical') {
            this.box.classList.remove('hbox');
            this.box.classList.add('vbox');
        } else {
            this.box.classList.remove('vbox');
            this.box.classList.add('hbox');
        }
    },

    _flex_changed: function() {
        if (this.model.previous('flex')) {
            this.box.classList.remove('box-flex' + this.model.previous('flex'));
        }
        this.box.classList.add('box-flex' + this.model.get('flex'));
    },

    _pack_changed: function() {
        if (this.model.previous('pack')) {
            this.box.classList.remove(this.model.previous('pack'));
        }
        this.box.classList.add(this.model.get('pack'));
    },

    _align_changed: function() {
        if (this.model.previous('align')) {
            this.box.classList.remove('align-' + this.model.previous('align'));
        }
        this.box.classList.add('align-' + this.model.get('align'));
    }
});

module.exports = {
    BoxModel: BoxModel,
    BoxView: BoxView,
    FlexBoxModel: FlexBoxModel, // TODO: Deprecated in 5.0
    FlexBoxView: FlexBoxView, // TODO: Deprecated in 5.0
    ProxyModel: ProxyModel,
    ProxyView: ProxyView,
    PlaceProxyModel: PlaceProxyModel,
    PlaceProxyView: PlaceProxyView
};
<|endoftext|>