<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: 'UnstyledTableView',
  extends: 'foam.u2.Element',

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

  requires: [
    'foam.core.SimpleSlot',
    'foam.dao.ProxyDAO',
    'foam.u2.md.CheckBox',
    'foam.u2.md.OverlayDropdown',
    'foam.u2.view.OverlayActionListView',
    'foam.u2.view.EditColumnsView',
    'foam.u2.view.ColumnConfig',
    'foam.u2.view.ColumnVisibility',
    'foam.u2.tag.Image'
  ],

  exports: [
    'columns',
    'selection',
    'hoverSelection'
  ],

  imports: [
    'ctrl',
    'dblclick?',
    'editRecord?',
    'filteredTableColumns?',
    'selection? as importSelection',
    'stack?'
  ],

  properties: [
    {
      class: 'Class',
      name: 'of'
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'data',
      postSet: function(_, data) {
        if ( ! this.of && data ) this.of = data.of;
      }
    },
    {
      name: 'order'
    },
    {
      name: 'columns_',
      expression: function(columns, of) {
        var of = this.of;
        if ( ! of ) return [];

        return columns.map(function(p) {
          var c = typeof p == 'string' ?
            of.getAxiomByName(p) :
            p;

           if ( ! c ) {
             console.error('Unknown table column: ', p);
           }

          return c;
        });
      },
    },
    {
      class: 'StringArray',
      name: 'allColumns',
      factory: function() {
        return [].concat(
          of.getAxiomsByClass(foam.core.Property)
            .filter(p => p.tableCellFormatter && ! p.hidden)
            .map(p => p.name),
          of.getAxiomsByClass(foam.core.Action)
            .map(a => a.name)
        );
      }
    },
    {
      name: 'columns',
      expression: function(of, editColumnsEnabled, allColumns) {
        if ( ! of ) return [];

        var tableColumns = of.getAxiomByName('tableColumns');
        if ( tableColumns ) {
          // When tableColumns are present, we want to prioritize the ordering
          // that they provide.
          tableColumns = tableColumns.columns;
          allColumns = tableColumns.concat(allColumns);
          allColumns = allColumns.filter((c, i) => allColumns.indexOf(c) == i);
        } else {
          tableColumns = allColumns;
        }

        if ( ! editColumnsEnabled ) return tableColumns;

        // Put together the columns to show by using the user's preferences for
        // columns to always show or hide.
        var columnsToShow = tableColumns
          .reduce((map, c) => {
            map[c] = true;
            return map;
          }, {});
        allColumns
          .map(c => this.ColumnConfig.create({
            name: c,
            visibility: this.ColumnVisibility[localStorage.getItem(of.id + '.' + c)] || 'DEFAULT'
          }))
          .forEach(o => {
            if ( o.visibility == this.ColumnVisibility.ALWAYS_HIDE ) {
              columnsToShow[o.name] = false;
            } else if ( o.visibility == this.ColumnVisibility.ALWAYS_SHOW ) {
              columnsToShow[o.name] = true;
            }
          });

        return allColumns.filter(c => columnsToShow[c]);
      },
    },
    {
      class: 'FObjectArray',
      of: 'foam.core.Action',
      name: 'contextMenuActions',
      documentation: `
        Each table row has a context menu that contains actions you can perform
        on the object in that row. The actions used to populate that menu come
        from two different sources. The first source is this property. If you
        want a context menu action to do something in the view, then you should
        write the code for that action in the view model and pass it to the
        table view via this property. The second source of actions is from the
        model of the object being shown in the table.
      `
    },
    {
      class: 'Boolean',
      name: 'editColumnsEnabled',
      value: true,
      documentation: 'Set this to true to let the user select columns.'
    },
    {
      class: 'Boolean',
      name: 'disableUserSelection',
      value: false,
      documentation: 'Ignores selection by user.'
    },
    {
      name: 'restingIcon',
      documentation: 'Image for grayed out double arrow when table header is not sorting',
      value: 'images/resting-arrow.svg'
    },
    {
      name: 'ascIcon',
      documentation: 'Image for table header ascending sorting arrow',
      value: 'images/up-arrow.svg'
    },
    {
      name: 'descIcon',
      documentation: 'Image for table header descending sorting arrow',
      value: 'images/down-arrow.svg'
    },
    {
      name: 'selection',
      expression: function(importSelection) { return importSelection || null; },
    },
    'hoverSelection',
    'dropdownOrigin',
    'overlayOrigin',
    {
      type: 'Boolean',
      name: 'showHeader',
      value: true,
      documentation: 'Set to false to not render the header.'
    },
    {
      class: 'Boolean',
      name: 'multiSelectEnabled',
      documentation: 'Set to true to support selecting multiple table rows.'
    },
    {
      class: 'Map',
      name: 'selectedObjects',
      documentation: `
        The objects selected by the user when multi-select support is enabled.
        It's a map where the key is the object id and the value is the object.
      `
    },
    {
      name: 'idsOfObjectsTheUserHasInteractedWith_',
      factory: function() {
        return {};
      }
    },
    {
      name: 'checkboxes_',
      documentation: 'The checkbox elements when multi-select support is enabled. Used internally to implement the select all feature.',
      factory: function() {
        return {};
      }
    },
    {
      class: 'Boolean',
      name: 'togglingCheckBoxes_',
      documentation: 'Used internally to improve performance when toggling all checkboxes on or off.'
    },
    {
      class: 'Boolean',
      name: 'allCheckBoxesEnabled_',
      documentation: 'Used internally to denote when the user has pressed the checkbox in the header to enable all checkboxes.'
    }
  ],

  methods: [
    function sortBy(column) {
      this.order = this.order === column ?
        this.DESC(column) :
        column;
    },

    function initE() {
      var view = this;
      var columnSelectionE;

      if ( this.filteredTableColumns$ ) {
        this.onDetach(this.filteredTableColumns$.follow(
          this.columns_$.map((cols) => cols.map((a) => a.name))));
      }

      this.
        addClass(this.myClass()).
        addClass(this.myClass(this.of.id.replace(/\./g, '-'))).
        start().
          addClass(this.myClass('thead')).
          show(this.showHeader$).
          add(this.slot(function(columns_) {
            return this.E().
              addClass(view.myClass('tr')).

              // If multi-select is enabled, then we show a checkbox in the
              // header that allows you to select all or select none.
              callIf(view.multiSelectEnabled, function() {
                var slot = view.SimpleSlot.create();
                this.start().
                  addClass(view.myClass('th')).
                  tag(view.CheckBox, {}, slot).
                  style({ width: '42px' }).
                end();

                // Set up a listener so we can update the existing CheckBox
                // views when a user wants to select all or select none.
                view.onDetach(slot.value.dot('data').sub(function(_, __, ___, newValueSlot) {
                  var checked = newValueSlot.get();
                  view.allCheckBoxesEnabled_ = checked;

                  if ( checked ) {
                    view.selectedObjects = {};
                    view.data.select(function(obj) {
                      view.selectedObjects[obj.id] = obj;
                    });
                  } else {
                    view.selectedObjects = {};
                  }

                  // Update the existing CheckBox views.
                  view.togglingCheckBoxes_ = true;
                  Object.keys(view.checkboxes_).forEach(function(key) {
                    view.checkboxes_[key].data = checked;
                  });
                  view.togglingCheckBoxes_ = false;
                }));
              }).

              // Render the table headers for the property columns.
              forEach(columns_, function(column) {
                this.start().
                  addClass(view.myClass('th')).
                  addClass(view.myClass('th-' + column.name)).
                  call(function() {
                    if ( column.tableWidth ) {
                      this.style({ flex: `0 0 ${column.tableWidth}px` });
                    } else {
                      this.style({ flex: '1 0 0' });
                    }
                  }).
                  on('click', function(e) {
                    view.sortBy(column);
                  }).
                  call(column.tableHeaderFormatter, [column]).
                  callIf(column.label != '', function() {
                    this.start('img').attr('src', this.slot(function(order) {
                      return column === order ? view.ascIcon :
                          (view.Desc.isInstance(order) && order.arg1 === column)
                          ? view.descIcon : view.restingIcon;
                    }, view.order$)).end();
                  }).
                end();
              }).

              // Render a th at the end for the column that contains the context
              // menu. If the column-editing feature is enabled, add that to the
              // th we create here.
              call(function() {
                this.start().
                  addClass(view.myClass('th')).
                  style({ flex: '0 0 60px' }).
                  callIf(view.editColumnsEnabled, function() {
                    this.addClass(view.myClass('th-editColumns')).
                    on('click', function(e) {
                      if ( ! view.stack ) return;
                      view.stack.push({
                        class: 'foam.u2.DetailView',
                        data: view.EditColumnsView.create({
                          of: view.of,
                          allColumns: view.allColumns
                        })
                      });
                    }).
                    tag(view.Image, { data: '/images/Icon_More_Resting.svg' }).
                    addClass(view.myClass('vertDots')).
                    addClass(view.myClass('noselect'));
                  }).
                  tag('div', null, view.dropdownOrigin$).
                end();
              });
          })).
        end().
        add(this.rowsFrom(this.data$proxy));
    },
    {
      name: 'rowsFrom',
      code: function(dao) {
        /**
         * Given a DAO, add a tbody containing the data from the DAO to the
         * table and return a reference to the tbody.
         *
         * NOTE: This exists so that ScrollTableView can create and manage
         * several different tbody elements inside the TableView it uses. It
         * needs to manage several tbody elements so it can provide performant
         * infinite scroll on tables of any size. So this method exists solely
         * as an implementation detail of ScrollTableView at the time of
         * writing.
         */
        var view = this;
        return this.slot(function(columns_) {
          // Make sure the DAO set here responds to ordering when a user clicks
          // on a table column header to sort by that column.
          if ( this.order ) dao = dao.orderBy(this.order);
          var proxy = view.ProxyDAO.create({ delegate: dao });
          view.sub('propertyChange', 'order', function(_, __, ___, s) {
            proxy.delegate = dao.orderBy(s.get());
          });

          var modelActions = view.of.getAxiomsByClass(foam.core.Action);
          var actions = Array.isArray(view.contextMenuActions)
            ? view.contextMenuActions.concat(modelActions)
            : modelActions;

          return this.
            E().
            addClass(this.myClass('tbody')).
            select(proxy, function(obj) {
              return this.E().
                addClass(view.myClass('tr')).
                on('mouseover', function() { view.hoverSelection = obj; }).
                callIf(view.dblclick && ! view.disableUserSelection, function() {
                  this.on('dblclick', function() {
                    view.dblclick && view.dblclick(obj);
                  });
                }).
                callIf( ! view.disableUserSelection, function() {
                  this.on('click', function(evt) {
                    // If we're clicking somewhere to close the context menu,
                    // don't do anything.
                    if (
                      evt.target.nodeName === 'DROPDOWN-OVERLAY' ||
                      evt.target.classList.contains(view.myClass('vertDots'))
                    ) return;

                    view.selection = obj;
                    if ( view.importSelection$ ) view.importSelection = obj;
                    if ( view.editRecord$ ) view.editRecord(obj);
                  });
                }).
                addClass(view.slot(function(selection) {
                  return selection && foam.util.equals(obj.id, selection.id) ?
                      view.myClass('selected') : '';
                })).
                addClass(view.myClass('row')).

                // If the multi-select feature is enabled, then we render a
                // Checkbox in the first cell of each row.
                callIf(view.multiSelectEnabled, function() {
                  var slot = view.SimpleSlot.create();
                  this
                    .start()
                      .addClass(view.myClass('td'))
                      .tag(view.CheckBox, { data: view.idsOfObjectsTheUserHasInteractedWith_[obj.id] ? !!view.selectedObjects[obj.id] : view.allCheckBoxesEnabled_ }, slot)
                    .end();

                  // Set up a listener so that when the user checks or unchecks
                  // a box, we update the `selectedObjects` property.
                  view.onDetach(slot.value$.dot('data').sub(function(_, __, ___, newValueSlot) {
                    // If the user is checking or unchecking all boxes at once,
                    // we only want to publish one propertyChange event, so we
                    // trigger it from the listener in the table header instead
                    // of here. This way we prevent a propertyChange being fired
                    // for every single CheckBox's data changing.
                    if ( view.togglingCheckBoxes_ ) return;

                    // Remember that the user has interacted with this checkbox
                    // directly. We need this because the ScrollTableView loads
                    // tbody's in and out while the user scrolls, so we need to
                    // handle the case when a user selects all, then unselects
                    // a particular row, then scrolls far enough that the tbody
                    // the selection was in unloads, then scrolls back into the
                    // range where it reloads. We need to know if they've set
                    // it to something already and we can't simply look at the
                    // value on `selectedObjects` because then we won't know if
                    // `selectedObjects[obj.id] === undefined` means they
                    // haven't interacted with that checkbox or if it means they
                    // explicitly set it to false. We could keep the key but set
                    // the value to null, but that clutters up `selectedObjects`
                    // because some values are objects and some are null. If we
                    // use a separate set to remember which checkboxes the user
                    // has interacted with, then we don't need to clutter up
                    // `selectedObjects`.
                    view.idsOfObjectsTheUserHasInteractedWith_[obj.id] = true;

                    var checked = newValueSlot.get();

                    if ( checked ) {
                      var modification = {};
                      modification[obj.id] = checked ? obj : null;
                      view.selectedObjects = Object.assign({}, view.selectedObjects, modification);
                    } else {
                      var temp = Object.assign({}, view.selectedObjects);
                      delete temp[obj.id];
                      view.selectedObjects = temp;
                    }
                  }));

                  // Store each CheckBox Element in a map so we have a reference
                  // to them so we can set the `data` property of them when the
                  // user checks the box to enable or disable all checkboxes.
                  var checkbox = slot.get();
                  view.checkboxes_[obj.id] = checkbox;
                  checkbox.onDetach(function() {
                    delete view.checkboxes_[obj.id];
                  });
                }).

                forEach(columns_, function(column) {
                  this.
                    start().
                      addClass(view.myClass('td')).
                      callOn(column.tableCellFormatter, 'format', [
                        column.f ? column.f(obj) : null, obj, column
                      ]).
                      callIf(column.f, function() {
                        try {
                          var value = column.f(obj);
                          if ( foam.util.isPrimitive(value) ) {
                            this.attr('title', value);
                          }
                        } catch (err) {}
                      }).
                      call(function() {
                        if ( column.tableWidth ) {
                          this.style({ flex: `0 0 ${column.tableWidth}px` });
                        } else {
                          this.style({ flex: '1 0 0' });
                        }
                      }).
                    end();
                }).
                start().
                  addClass(view.myClass('td')).
                  attrs({ name: 'contextMenuCell' }).
                  style({ flex: '0 0 60px' }).
                  tag(view.OverlayActionListView, {
                    data: actions,
                    obj: obj
                  }).
                end();
            });
        });
      }
    }
  ],

});
<commit_msg>Fix up factory<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: 'UnstyledTableView',
  extends: 'foam.u2.Element',

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

  requires: [
    'foam.core.SimpleSlot',
    'foam.dao.ProxyDAO',
    'foam.u2.md.CheckBox',
    'foam.u2.md.OverlayDropdown',
    'foam.u2.view.OverlayActionListView',
    'foam.u2.view.EditColumnsView',
    'foam.u2.view.ColumnConfig',
    'foam.u2.view.ColumnVisibility',
    'foam.u2.tag.Image'
  ],

  exports: [
    'columns',
    'selection',
    'hoverSelection'
  ],

  imports: [
    'ctrl',
    'dblclick?',
    'editRecord?',
    'filteredTableColumns?',
    'selection? as importSelection',
    'stack?'
  ],

  properties: [
    {
      class: 'Class',
      name: 'of'
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'data',
      postSet: function(_, data) {
        if ( ! this.of && data ) this.of = data.of;
      }
    },
    {
      name: 'order'
    },
    {
      name: 'columns_',
      expression: function(columns, of) {
        var of = this.of;
        if ( ! of ) return [];

        return columns.map(function(p) {
          var c = typeof p == 'string' ?
            of.getAxiomByName(p) :
            p;

           if ( ! c ) {
             console.error('Unknown table column: ', p);
           }

          return c;
        });
      },
    },
    {
      class: 'StringArray',
      name: 'allColumns',
      factory: null,
      expression: function(of) {
        return [].concat(
          of.getAxiomsByClass(foam.core.Property)
            .filter(p => p.tableCellFormatter && ! p.hidden)
            .map(p => p.name),
          of.getAxiomsByClass(foam.core.Action)
            .map(a => a.name)
        );
      }
    },
    {
      name: 'columns',
      expression: function(of, editColumnsEnabled, allColumns) {
        if ( ! of ) return [];

        var tableColumns = of.getAxiomByName('tableColumns');
        if ( tableColumns ) {
          // When tableColumns are present, we want to prioritize the ordering
          // that they provide.
          tableColumns = tableColumns.columns;
          allColumns = tableColumns.concat(allColumns);
          allColumns = allColumns.filter((c, i) => allColumns.indexOf(c) == i);
        } else {
          tableColumns = allColumns;
        }

        if ( ! editColumnsEnabled ) return tableColumns;

        // Put together the columns to show by using the user's preferences for
        // columns to always show or hide.
        var columnsToShow = tableColumns
          .reduce((map, c) => {
            map[c] = true;
            return map;
          }, {});
        allColumns
          .map(c => this.ColumnConfig.create({
            name: c,
            visibility: this.ColumnVisibility[localStorage.getItem(of.id + '.' + c)] || 'DEFAULT'
          }))
          .forEach(o => {
            if ( o.visibility == this.ColumnVisibility.ALWAYS_HIDE ) {
              columnsToShow[o.name] = false;
            } else if ( o.visibility == this.ColumnVisibility.ALWAYS_SHOW ) {
              columnsToShow[o.name] = true;
            }
          });

        return allColumns.filter(c => columnsToShow[c]);
      },
    },
    {
      class: 'FObjectArray',
      of: 'foam.core.Action',
      name: 'contextMenuActions',
      documentation: `
        Each table row has a context menu that contains actions you can perform
        on the object in that row. The actions used to populate that menu come
        from two different sources. The first source is this property. If you
        want a context menu action to do something in the view, then you should
        write the code for that action in the view model and pass it to the
        table view via this property. The second source of actions is from the
        model of the object being shown in the table.
      `
    },
    {
      class: 'Boolean',
      name: 'editColumnsEnabled',
      value: true,
      documentation: 'Set this to true to let the user select columns.'
    },
    {
      class: 'Boolean',
      name: 'disableUserSelection',
      value: false,
      documentation: 'Ignores selection by user.'
    },
    {
      name: 'restingIcon',
      documentation: 'Image for grayed out double arrow when table header is not sorting',
      value: 'images/resting-arrow.svg'
    },
    {
      name: 'ascIcon',
      documentation: 'Image for table header ascending sorting arrow',
      value: 'images/up-arrow.svg'
    },
    {
      name: 'descIcon',
      documentation: 'Image for table header descending sorting arrow',
      value: 'images/down-arrow.svg'
    },
    {
      name: 'selection',
      expression: function(importSelection) { return importSelection || null; },
    },
    'hoverSelection',
    'dropdownOrigin',
    'overlayOrigin',
    {
      type: 'Boolean',
      name: 'showHeader',
      value: true,
      documentation: 'Set to false to not render the header.'
    },
    {
      class: 'Boolean',
      name: 'multiSelectEnabled',
      documentation: 'Set to true to support selecting multiple table rows.'
    },
    {
      class: 'Map',
      name: 'selectedObjects',
      documentation: `
        The objects selected by the user when multi-select support is enabled.
        It's a map where the key is the object id and the value is the object.
      `
    },
    {
      name: 'idsOfObjectsTheUserHasInteractedWith_',
      factory: function() {
        return {};
      }
    },
    {
      name: 'checkboxes_',
      documentation: 'The checkbox elements when multi-select support is enabled. Used internally to implement the select all feature.',
      factory: function() {
        return {};
      }
    },
    {
      class: 'Boolean',
      name: 'togglingCheckBoxes_',
      documentation: 'Used internally to improve performance when toggling all checkboxes on or off.'
    },
    {
      class: 'Boolean',
      name: 'allCheckBoxesEnabled_',
      documentation: 'Used internally to denote when the user has pressed the checkbox in the header to enable all checkboxes.'
    }
  ],

  methods: [
    function sortBy(column) {
      this.order = this.order === column ?
        this.DESC(column) :
        column;
    },

    function initE() {
      var view = this;
      var columnSelectionE;

      if ( this.filteredTableColumns$ ) {
        this.onDetach(this.filteredTableColumns$.follow(
          this.columns_$.map((cols) => cols.map((a) => a.name))));
      }

      this.
        addClass(this.myClass()).
        addClass(this.myClass(this.of.id.replace(/\./g, '-'))).
        start().
          addClass(this.myClass('thead')).
          show(this.showHeader$).
          add(this.slot(function(columns_) {
            return this.E().
              addClass(view.myClass('tr')).

              // If multi-select is enabled, then we show a checkbox in the
              // header that allows you to select all or select none.
              callIf(view.multiSelectEnabled, function() {
                var slot = view.SimpleSlot.create();
                this.start().
                  addClass(view.myClass('th')).
                  tag(view.CheckBox, {}, slot).
                  style({ width: '42px' }).
                end();

                // Set up a listener so we can update the existing CheckBox
                // views when a user wants to select all or select none.
                view.onDetach(slot.value.dot('data').sub(function(_, __, ___, newValueSlot) {
                  var checked = newValueSlot.get();
                  view.allCheckBoxesEnabled_ = checked;

                  if ( checked ) {
                    view.selectedObjects = {};
                    view.data.select(function(obj) {
                      view.selectedObjects[obj.id] = obj;
                    });
                  } else {
                    view.selectedObjects = {};
                  }

                  // Update the existing CheckBox views.
                  view.togglingCheckBoxes_ = true;
                  Object.keys(view.checkboxes_).forEach(function(key) {
                    view.checkboxes_[key].data = checked;
                  });
                  view.togglingCheckBoxes_ = false;
                }));
              }).

              // Render the table headers for the property columns.
              forEach(columns_, function(column) {
                this.start().
                  addClass(view.myClass('th')).
                  addClass(view.myClass('th-' + column.name)).
                  call(function() {
                    if ( column.tableWidth ) {
                      this.style({ flex: `0 0 ${column.tableWidth}px` });
                    } else {
                      this.style({ flex: '1 0 0' });
                    }
                  }).
                  on('click', function(e) {
                    view.sortBy(column);
                  }).
                  call(column.tableHeaderFormatter, [column]).
                  callIf(column.label != '', function() {
                    this.start('img').attr('src', this.slot(function(order) {
                      return column === order ? view.ascIcon :
                          (view.Desc.isInstance(order) && order.arg1 === column)
                          ? view.descIcon : view.restingIcon;
                    }, view.order$)).end();
                  }).
                end();
              }).

              // Render a th at the end for the column that contains the context
              // menu. If the column-editing feature is enabled, add that to the
              // th we create here.
              call(function() {
                this.start().
                  addClass(view.myClass('th')).
                  style({ flex: '0 0 60px' }).
                  callIf(view.editColumnsEnabled, function() {
                    this.addClass(view.myClass('th-editColumns')).
                    on('click', function(e) {
                      if ( ! view.stack ) return;
                      view.stack.push({
                        class: 'foam.u2.DetailView',
                        data: view.EditColumnsView.create({
                          of: view.of,
                          allColumns: view.allColumns
                        })
                      });
                    }).
                    tag(view.Image, { data: '/images/Icon_More_Resting.svg' }).
                    addClass(view.myClass('vertDots')).
                    addClass(view.myClass('noselect'));
                  }).
                  tag('div', null, view.dropdownOrigin$).
                end();
              });
          })).
        end().
        add(this.rowsFrom(this.data$proxy));
    },
    {
      name: 'rowsFrom',
      code: function(dao) {
        /**
         * Given a DAO, add a tbody containing the data from the DAO to the
         * table and return a reference to the tbody.
         *
         * NOTE: This exists so that ScrollTableView can create and manage
         * several different tbody elements inside the TableView it uses. It
         * needs to manage several tbody elements so it can provide performant
         * infinite scroll on tables of any size. So this method exists solely
         * as an implementation detail of ScrollTableView at the time of
         * writing.
         */
        var view = this;
        return this.slot(function(columns_) {
          // Make sure the DAO set here responds to ordering when a user clicks
          // on a table column header to sort by that column.
          if ( this.order ) dao = dao.orderBy(this.order);
          var proxy = view.ProxyDAO.create({ delegate: dao });
          view.sub('propertyChange', 'order', function(_, __, ___, s) {
            proxy.delegate = dao.orderBy(s.get());
          });

          var modelActions = view.of.getAxiomsByClass(foam.core.Action);
          var actions = Array.isArray(view.contextMenuActions)
            ? view.contextMenuActions.concat(modelActions)
            : modelActions;

          return this.
            E().
            addClass(this.myClass('tbody')).
            select(proxy, function(obj) {
              return this.E().
                addClass(view.myClass('tr')).
                on('mouseover', function() { view.hoverSelection = obj; }).
                callIf(view.dblclick && ! view.disableUserSelection, function() {
                  this.on('dblclick', function() {
                    view.dblclick && view.dblclick(obj);
                  });
                }).
                callIf( ! view.disableUserSelection, function() {
                  this.on('click', function(evt) {
                    // If we're clicking somewhere to close the context menu,
                    // don't do anything.
                    if (
                      evt.target.nodeName === 'DROPDOWN-OVERLAY' ||
                      evt.target.classList.contains(view.myClass('vertDots'))
                    ) return;

                    view.selection = obj;
                    if ( view.importSelection$ ) view.importSelection = obj;
                    if ( view.editRecord$ ) view.editRecord(obj);
                  });
                }).
                addClass(view.slot(function(selection) {
                  return selection && foam.util.equals(obj.id, selection.id) ?
                      view.myClass('selected') : '';
                })).
                addClass(view.myClass('row')).

                // If the multi-select feature is enabled, then we render a
                // Checkbox in the first cell of each row.
                callIf(view.multiSelectEnabled, function() {
                  var slot = view.SimpleSlot.create();
                  this
                    .start()
                      .addClass(view.myClass('td'))
                      .tag(view.CheckBox, { data: view.idsOfObjectsTheUserHasInteractedWith_[obj.id] ? !!view.selectedObjects[obj.id] : view.allCheckBoxesEnabled_ }, slot)
                    .end();

                  // Set up a listener so that when the user checks or unchecks
                  // a box, we update the `selectedObjects` property.
                  view.onDetach(slot.value$.dot('data').sub(function(_, __, ___, newValueSlot) {
                    // If the user is checking or unchecking all boxes at once,
                    // we only want to publish one propertyChange event, so we
                    // trigger it from the listener in the table header instead
                    // of here. This way we prevent a propertyChange being fired
                    // for every single CheckBox's data changing.
                    if ( view.togglingCheckBoxes_ ) return;

                    // Remember that the user has interacted with this checkbox
                    // directly. We need this because the ScrollTableView loads
                    // tbody's in and out while the user scrolls, so we need to
                    // handle the case when a user selects all, then unselects
                    // a particular row, then scrolls far enough that the tbody
                    // the selection was in unloads, then scrolls back into the
                    // range where it reloads. We need to know if they've set
                    // it to something already and we can't simply look at the
                    // value on `selectedObjects` because then we won't know if
                    // `selectedObjects[obj.id] === undefined` means they
                    // haven't interacted with that checkbox or if it means they
                    // explicitly set it to false. We could keep the key but set
                    // the value to null, but that clutters up `selectedObjects`
                    // because some values are objects and some are null. If we
                    // use a separate set to remember which checkboxes the user
                    // has interacted with, then we don't need to clutter up
                    // `selectedObjects`.
                    view.idsOfObjectsTheUserHasInteractedWith_[obj.id] = true;

                    var checked = newValueSlot.get();

                    if ( checked ) {
                      var modification = {};
                      modification[obj.id] = checked ? obj : null;
                      view.selectedObjects = Object.assign({}, view.selectedObjects, modification);
                    } else {
                      var temp = Object.assign({}, view.selectedObjects);
                      delete temp[obj.id];
                      view.selectedObjects = temp;
                    }
                  }));

                  // Store each CheckBox Element in a map so we have a reference
                  // to them so we can set the `data` property of them when the
                  // user checks the box to enable or disable all checkboxes.
                  var checkbox = slot.get();
                  view.checkboxes_[obj.id] = checkbox;
                  checkbox.onDetach(function() {
                    delete view.checkboxes_[obj.id];
                  });
                }).

                forEach(columns_, function(column) {
                  this.
                    start().
                      addClass(view.myClass('td')).
                      callOn(column.tableCellFormatter, 'format', [
                        column.f ? column.f(obj) : null, obj, column
                      ]).
                      callIf(column.f, function() {
                        try {
                          var value = column.f(obj);
                          if ( foam.util.isPrimitive(value) ) {
                            this.attr('title', value);
                          }
                        } catch (err) {}
                      }).
                      call(function() {
                        if ( column.tableWidth ) {
                          this.style({ flex: `0 0 ${column.tableWidth}px` });
                        } else {
                          this.style({ flex: '1 0 0' });
                        }
                      }).
                    end();
                }).
                start().
                  addClass(view.myClass('td')).
                  attrs({ name: 'contextMenuCell' }).
                  style({ flex: '0 0 60px' }).
                  tag(view.OverlayActionListView, {
                    data: actions,
                    obj: obj
                  }).
                end();
            });
        });
      }
    }
  ],

});
<|endoftext|>


<commit_before>/**
 * Copyright 2013, 2015 IBM Corp.
 * Copyright 2016 Dean Cording
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

module.exports = function(RED) {
    "use strict";
    var operators = {
        'eq': function(a, b) { return a == b; },
        'neq': function(a, b) { return a != b; },
        'lt': function(a, b) { return a < b; },
        'lte': function(a, b) { return a <= b; },
        'gt': function(a, b) { return a > b; },
        'gte': function(a, b) { return a >= b; },
        'btwn': function(a, b, c) { return a >= b && a <= c; },
        'cont': function(a, b) { return (a + "").indexOf(b) != -1; },
        'regex': function(a, b, c, d) { return (a + "").match(new RegExp(b,d?'i':'')); },
        'true': function(a) { return a === true; },
        'false': function(a) { return a === false; },
        'null': function(a) { return (typeof a == "undefined" || a === null); },
        'nnull': function(a) { return (typeof a != "undefined" && a !== null); }
    };

    var operatorsDesc = {
        'eq': function(a, b) { return "" + a +  "== " + b; },
        'neq': function(a, b) { return "" + a + "!=" + b; },
        'lt': function(a, b) { return "" + a + "<" + b; },
        'lte': function(a, b) { return "" + a + "<=" + b; },
        'gt': function(a, b) { return "" + a + ">" + b; },
        'gte': function(a, b) { return "" + a + ">=" + b; },
        'btwn': function(a, b, c) { return "" + a + ">=" + b + " && " + a + "<=" + c; },
        'cont': function(a, b) { return "" + a + " contains " + b; },
        'regex': function(a, b, c, d) { return "" + a + " " + b + "case insensitive: " + d; },
        'true': function(a) { return "" + a + "is true"; },
        'false': function(a) { return "" + a + "is false"; },
        'null': function(a) { return "" + a + "is null"; },
        'nnull': function(a) { return "" + a + "is not null"; }
    };

    function AssertNode(n) {
        RED.nodes.createNode(this, n);
        this.rules = n.rules || [];
        this.property = n.property;
        this.propertyType = n.propertyType || "msg";
        this.previousValue = null;
        var node = this;
        for (var i=0; i<this.rules.length; i+=1) {
            var rule = this.rules[i];
            if (!rule.vt) {
                if (!isNaN(Number(rule.v))) {
                    rule.vt = 'num';
                } else {
                    rule.vt = 'str';
                }
            }
            if (rule.vt === 'num') {
                if (!isNaN(Number(rule.v))) {
                    rule.v = Number(rule.v);
                }
            }
            if (typeof rule.v2 !== 'undefined') {
                if (!rule.v2t) {
                    if (!isNaN(Number(rule.v2))) {
                        rule.v2t = 'num';
                    } else {
                        rule.v2t = 'str';
                    }
                }
                if (rule.v2t === 'num') {
                    rule.v2 = Number(rule.v2);
                }
            }
        }

        this.on('input', function (msg) {
            try {
                var prop = RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg);
                for (var i=0; i<node.rules.length; i+=1) {
                    var rule = node.rules[i];
                    var test = prop;
                    var v1,v2;
                    if (rule.vt === 'prev') {
                        v1 = node.previousValue;
                    } else {
                        v1 = RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg);
                    }
                    v2 = rule.v2;
                    if (rule.v2t === 'prev') {
                        v2 = node.previousValue;
                    } else if (typeof v2 !== 'undefined') {
                        v2 = RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg);
                    }
                    node.previousValue = prop;
                    if (!operators[rule.t](test,v1,v2,rule.case)) {
                        this.status({fill:"red",shape:"dot",text:"Assertion " + i + " failed"});
                        throw new Error("Assertion failed: " + node.property + "- " + operatorsDesc[rule.t](test,v1,v2,rule.case));
                    }
                }
                this.status({fill:"green",shape:"dot",text:"ok"});
                this.send(msg);
            } catch(err) {
                node.warn(err);
            }
        });
    }
    RED.nodes.registerType("assert", AssertNode);
}
<commit_msg>Improved error reporting<commit_after>/**
 * Copyright 2013, 2015 IBM Corp.
 * Copyright 2016 Dean Cording
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

module.exports = function(RED) {
    "use strict";
    var operators = {
        'eq': function(a, b) { return a == b; },
        'neq': function(a, b) { return a != b; },
        'lt': function(a, b) { return a < b; },
        'lte': function(a, b) { return a <= b; },
        'gt': function(a, b) { return a > b; },
        'gte': function(a, b) { return a >= b; },
        'btwn': function(a, b, c) { return a >= b && a <= c; },
        'cont': function(a, b) { return (a + "").indexOf(b) != -1; },
        'regex': function(a, b, c, d) { return (a + "").match(new RegExp(b,d?'i':'')); },
        'true': function(a) { return a === true; },
        'false': function(a) { return a === false; },
        'null': function(a) { return (typeof a == "undefined" || a === null); },
        'nnull': function(a) { return (typeof a != "undefined" && a !== null); }
    };

    var operatorsDesc = {
        'eq': function(a, b) { return "" + a +  "==" + b; },
        'neq': function(a, b) { return "" + a + "!=" + b; },
        'lt': function(a, b) { return "" + a + "<" + b; },
        'lte': function(a, b) { return "" + a + "<=" + b; },
        'gt': function(a, b) { return "" + a + ">" + b; },
        'gte': function(a, b) { return "" + a + ">=" + b; },
        'btwn': function(a, b, c) { return "" + a + ">=" + b + " && " + a + "<=" + c; },
        'cont': function(a, b) { return "" + a + " contains " + b; },
        'regex': function(a, b, c, d) { return "" + a + " " + b + "case insensitive: " + d; },
        'true': function(a) { return "" + a + "is true"; },
        'false': function(a) { return "" + a + "is false"; },
        'null': function(a) { return "" + a + "is null"; },
        'nnull': function(a) { return "" + a + "is not null"; }
    };

    function AssertNode(n) {
        RED.nodes.createNode(this, n);
        this.rules = n.rules || [];
        this.property = n.property;
        this.propertyType = n.propertyType || "msg";
        this.previousValue = null;
        var node = this;
        for (var i=0; i<this.rules.length; i+=1) {
            var rule = this.rules[i];
            if (!rule.vt) {
                if (!isNaN(Number(rule.v))) {
                    rule.vt = 'num';
                } else {
                    rule.vt = 'str';
                }
            }
            if (rule.vt === 'num') {
                if (!isNaN(Number(rule.v))) {
                    rule.v = Number(rule.v);
                }
            }
            if (typeof rule.v2 !== 'undefined') {
                if (!rule.v2t) {
                    if (!isNaN(Number(rule.v2))) {
                        rule.v2t = 'num';
                    } else {
                        rule.v2t = 'str';
                    }
                }
                if (rule.v2t === 'num') {
                    rule.v2 = Number(rule.v2);
                }
            }
        }

        this.on('input', function (msg) {
            try {
                var prop = RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg);
                for (var i=0; i<node.rules.length; i+=1) {
                    var rule = node.rules[i];
                    var test = prop;
                    var v1,v2;
                    if (rule.vt === 'prev') {
                        v1 = node.previousValue;
                    } else {
                        v1 = RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg);
                    }
                    v2 = rule.v2;
                    if (rule.v2t === 'prev') {
                        v2 = node.previousValue;
                    } else if (typeof v2 !== 'undefined') {
                        v2 = RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg);
                    }
                    node.previousValue = prop;
                    if (!operators[rule.t](test,v1,v2,rule.case)) {
                        this.status({fill:"red",shape:"dot",text:"Assertion " + (i+1) + " failed"});
                        throw new Error("Assertion " + (i+1) + " failed: " + node.propertyType + ":" + node.property + ": " + operatorsDesc[rule.t](test,v1,v2,rule.case));
                    }
                }
                this.status({fill:"green",shape:"dot",text:"ok"});
                this.send(msg);
            } catch(err) {
                node.warn(err);
            }
        });
    }
    RED.nodes.registerType("assert", AssertNode);
}
<|endoftext|>


<commit_before>/**
 * This plugin is used to fix elements to the top of the page, if the element
 * would have been scrolled out of view, vertically; however, it does allow the
 * element to continue to move left or right with the horizontal scroll.
 *
 * Given an option marginTop, the element will stop moving vertically upward
 * once the vertical scroll has reached the target position; but, the element
 * will still move horizontally as the page is scrolled left or right. Once the
 * page has been scrolled back down passed the target position, the element will
 * be restored to its original position on the page.
 *
 * This plugin has been tested in Firefox 3/4, Google Chrome 10/11, Safari 5,
 * and Internet Explorer 8/9.
 */
(function($) {
    $.ScrollToFixed = function(el, options) {
        // To avoid scope issues, use 'base' instead of 'this' to reference this
        // class from internal events and functions.
        var base = this;

        // Access to jQuery and DOM versions of element.
        base.$el = $(el);
        base.el = el;

        // Add a reverse reference to the DOM object.
        base.$el.data("ScrollToFixed", base);

        // A flag so we know if the scroll has been reset.
        var isReset = false;

        // The element that was given to us to fix if scrolled above the top of
        // the page.
        var target = base.$el;

        // The offset top of the element when resetScroll was called. This is
        // used to determine if we have scrolled past the top of the element.
        var offsetTop = 0;

        // The offset left of the element when resetScroll was called. This is
        // used to move the element left or right relative to the horizontal
        // scroll.
        var offsetLeft = 0;

        // This last offset used to move the element horizontally. This is used
        // to determine if we need to move the element because we would not want
        // to do that for no reason.
        var lastOffsetLeft = -1;

        // This is the element used to fill the void left by the target element
        // when it goes fixed; otherwise, everything below it moves up the page.
        var spacer = null;

        // Capture the original offsets for the target element. This needs to be
        // called whenever the page size changes or when the page is first
        // scrolled. For some reason, calling this before the page is first
        // scrolled causes the element to become fixed too late.
        function resetScroll() {
            // Set the element to it original positioning.
            setUnfixed();

            // Reset the last offset used to determine if the page has moved
            // horizontally.
            lastOffsetLeft = -1;

            // Capture the offset top of the target element.
            offsetTop = target.offset().top;

            // Capture the offset left of the target element.
            offsetLeft = target.offset().left;

            // Set that this has been called at least once.
            isReset = true;

            if (base.options.bottom != -1) {
                setFixed();
            }
        }

        // Returns whether the target element is fixed or not.
        function isFixed() {
            return target.css('position') == 'fixed';
        }

        // Sets the target element to fixed. Also, sets the spacer to fill the
        // void left by the target element.
        function setFixed() {
            // Only fix the target element and the spacer if we need to.
            if (!isFixed()) {
                // Set the spacer to fill the height and width of the target
                // element, then display it.
                spacer.css({
                    'display' : target.css('display'),
                    'width' : target.outerWidth(true),
                    'height' : target.outerHeight(true),
                    'float' : target.css('float')
                });

                // Set the target element to fixed and set its width so it does
                // not fill the rest of the page horizontally. Also, set its top
                // to the margin top specified in the options.
                target.css({
                    'width' : target.width(),
                    'position' : 'fixed',
                    'top' : base.options.bottom == -1?base.options.marginTop:'',
                    'bottom' : base.options.bottom == -1?'':base.options.bottom
                });
            }
        }

        // Sets the target element back to unfixed. Also, hides the spacer.
        function setUnfixed() {
            // Only unfix the target element and the spacer if we need to.
            if (isFixed()) {
                lastOffsetLeft = -1;

                // Hide the spacer now that the target element will fill the
                // space.
                spacer.css('display', 'none');

                // Remove the style attributes that were added to the target.
                // This will reverse the target back to the its original style.
                target.css({
                    'width' : '',
                    'position' : '',
                    'left' : '',
                    'top' : ''
                });
            }
        }

        // Moves the target element left or right relative to the horizontal
        // scroll position.
        function setLeft(x) {
            // Only if the scroll is not what it was last time we did this.
            if (x != lastOffsetLeft) {
                // Move the target element horizontally relative to its original
                // horizontal position.
                target.css('left', offsetLeft - x);

                // Hold the last horizontal position set.
                lastOffsetLeft = x;
            }
        }

        // Checks to see if we need to do something based on new scroll position
        // of the page.
        function checkScroll() {
            // If resetScroll has not yet been called, call it. This only
            // happens once.
            if (!isReset) {
                resetScroll();
            }

            // Grab the current horizontal scroll position.
            var x = $(window).scrollLeft();

            // Grab the current vertical scroll position.
            var y = $(window).scrollTop();

            // If the vertical scroll position, plus the optional margin, would
            // put the target element at the specified limit, set the target
            // element to absolute.
            if (base.options.bottom == -1) {
                // If the vertical scroll position, plus the optional margin, would
                // put the target element at the specified limit, set the target
                // element to absolute.
                if (base.options.limit > 0 && y >= base.options.limit - base.options.marginTop) {
                    target.css('position', 'absolute');
                    target.css('top', base.options.limit);

                // If the vertical scroll position, plus the optional margin, would
                // put the target element above the top of the page, set the target
                // element to fixed.
                } else if (y >= offsetTop - base.options.marginTop) {
                    // Set the target element to fixed.
                    setFixed();

                    // If the page has been scrolled horizontally as well, move the
                    // target element accordingly.
                    setLeft(x);

                } else {
                    // Set the target element to unfixed, placing it where it was
                    // before.
                    setUnfixed();
                }
            } else {
                if (base.options.limit > 0) {
                    if (y + $(window).height() - target.outerHeight() >= base.options.limit - base.options.marginTop) {
                        setUnfixed();
                    } else {
                        setFixed();
                        setLeft(x);
                    }
                } else {
                    setLeft(x);
                }
            }
        }

        // Initializes this plugin. Captures the options passed in, turns this
        // off for iOS, adds the spacer, and binds to the window scroll and
        // resize events.
        base.init = function() {
            // Capture the options for this plugin.
            base.options = $
                    .extend({}, $.ScrollToFixed.defaultOptions, options);

            // Turn off this functionality for iOS devices until we figure out
            // what to do with them, or until iOS5 comes out which is supposed
            // to support position:fixed.
            if (navigator.platform == 'iPad' || navigator.platform == 'iPhone'
                    || navigator.platform == "iPod") {

                return;
            }

            // Put the target element on top of everything that could be below
            // it. This reduces flicker when the target element is transitioning
            // to fixed.
            base.$el.css('z-index', 1000);

            // Create a spacer element to fill the void left by the target
            // element when it goes fixed.
            spacer = $('<div/>');

            // Place the spacer right after the target element.
            base.$el.after(spacer);

            // Reset the target element offsets when the window is resized, then
            // check to see if we need to fix or unfix the target element.
            $(window).bind('resize', function(event) {
                resetScroll();
                checkScroll();
            });

            // When the window scrolls, check to see if we need to fix or unfix
            // the target element.
            $(window).bind('scroll', function(event) {
                checkScroll();
            });

            if (base.options.bottom != -1) {
                setFixed();
            }
        };

        // Initialize the plugin.
        base.init();
    };

    // Sets the option defaults.
    $.ScrollToFixed.defaultOptions = {
        marginTop : 0,
        limit : 0,
        bottom : -1
    };

    // Returns enhanced elements that will fix to the top of the page when the
    // page is scrolled.
    $.fn.scrollToFixed = function(options) {
        return this.each(function() {
            (new $.ScrollToFixed(this, options));
        });
    };
})(jQuery);<commit_msg>See #2: The cart now moves horizontally with resize and scroll when absolute; however, the size of the changes.  Need to fix that but at least it is moving properly now.<commit_after>/**
 * This plugin is used to fix elements to the top of the page, if the element
 * would have been scrolled out of view, vertically; however, it does allow the
 * element to continue to move left or right with the horizontal scroll.
 *
 * Given an option marginTop, the element will stop moving vertically upward
 * once the vertical scroll has reached the target position; but, the element
 * will still move horizontally as the page is scrolled left or right. Once the
 * page has been scrolled back down passed the target position, the element will
 * be restored to its original position on the page.
 *
 * This plugin has been tested in Firefox 3/4, Google Chrome 10/11, Safari 5,
 * and Internet Explorer 8/9.
 */
(function($) {
    $.ScrollToFixed = function(el, options) {
        // To avoid scope issues, use 'base' instead of 'this' to reference this
        // class from internal events and functions.
        var base = this;

        // Access to jQuery and DOM versions of element.
        base.$el = $(el);
        base.el = el;

        // Add a reverse reference to the DOM object.
        base.$el.data("ScrollToFixed", base);

        // A flag so we know if the scroll has been reset.
        var isReset = false;

        // The element that was given to us to fix if scrolled above the top of
        // the page.
        var target = base.$el;

        // The offset top of the element when resetScroll was called. This is
        // used to determine if we have scrolled past the top of the element.
        var offsetTop = 0;

        // The offset left of the element when resetScroll was called. This is
        // used to move the element left or right relative to the horizontal
        // scroll.
        var offsetLeft = 0;
        var originalOffsetLeft = -1;

        // This last offset used to move the element horizontally. This is used
        // to determine if we need to move the element because we would not want
        // to do that for no reason.
        var lastOffsetLeft = -1;

        // This is the element used to fill the void left by the target element
        // when it goes fixed; otherwise, everything below it moves up the page.
        var spacer = null;

        // Capture the original offsets for the target element. This needs to be
        // called whenever the page size changes or when the page is first
        // scrolled. For some reason, calling this before the page is first
        // scrolled causes the element to become fixed too late.
        function resetScroll() {
            // Set the element to it original positioning.
            setUnfixed();

            // Reset the last offset used to determine if the page has moved
            // horizontally.
            lastOffsetLeft = -1;

            // Capture the offset top of the target element.
            offsetTop = target.offset().top;

            // Capture the offset left of the target element.
            offsetLeft = target.offset().left;
            if (originalOffsetLeft == -1) {
                orginalOffsetLeft = offsetLeft;
            }

            // Set that this has been called at least once.
            isReset = true;

            if (base.options.bottom != -1) {
                setFixed();
            }
        }

        // Returns whether the target element is fixed or not.
        function isFixed() {
            return target.css('position') == 'fixed';
        }

        // Returns whether the target element is absolute or not.
        function isAbsolute() {
            return target.css('position') == 'absolute';
        }

        // Sets the target element to fixed. Also, sets the spacer to fill the
        // void left by the target element.
        function setFixed() {
            // Only fix the target element and the spacer if we need to.
            if (!isFixed()) {
                // Set the spacer to fill the height and width of the target
                // element, then display it.
                spacer.css({
                    'display' : target.css('display'),
                    'width' : target.outerWidth(true),
                    'height' : target.outerHeight(true),
                    'float' : target.css('float')
                });

                // Set the target element to fixed and set its width so it does
                // not fill the rest of the page horizontally. Also, set its top
                // to the margin top specified in the options.
                target.css({
                    'width' : target.width(),
                    'position' : 'fixed',
                    'top' : base.options.bottom == -1?base.options.marginTop:'',
                    'bottom' : base.options.bottom == -1?'':base.options.bottom,
                });
            }
        }

        // Sets the target element back to unfixed. Also, hides the spacer.
        function setUnfixed() {
            // Only unfix the target element and the spacer if we need to.
            if (isFixed() || isAbsolute()) {
                lastOffsetLeft = -1;

                // Hide the spacer now that the target element will fill the
                // space.
                spacer.css('display', 'none');

                // Remove the style attributes that were added to the target.
                // This will reverse the target back to the its original style.
                target.css({
                    'width' : '',
                    'position' : '',
                    'left' : '',
                    'top' : ''
                });
            }
        }

        // Moves the target element left or right relative to the horizontal
        // scroll position.
        function setLeft(x) {
            // Only if the scroll is not what it was last time we did this.
            if (x != lastOffsetLeft) {
                // Move the target element horizontally relative to its original
                // horizontal position.
                target.css('left', offsetLeft - x);

                // Hold the last horizontal position set.
                lastOffsetLeft = x;
            }
        }

        // Checks to see if we need to do something based on new scroll position
        // of the page.
        function checkScroll() {
            // If resetScroll has not yet been called, call it. This only
            // happens once.
            if (!isReset) {
                resetScroll();
            }

            // Grab the current horizontal scroll position.
            var x = $(window).scrollLeft();

            // Grab the current vertical scroll position.
            var y = $(window).scrollTop();

            // If the vertical scroll position, plus the optional margin, would
            // put the target element at the specified limit, set the target
            // element to absolute.
            if (base.options.bottom == -1) {
                // If the vertical scroll position, plus the optional margin, would
                // put the target element at the specified limit, set the target
                // element to absolute.
                if (base.options.limit > 0 && y >= base.options.limit - base.options.marginTop) {
                    console.log('absolute, ' + x + ', ' + offsetLeft + ', ' + target.offset().left);
                    target.css('position', 'absolute');
                    target.css('top', base.options.limit);
                    target.css('left', offsetLeft);

                // If the vertical scroll position, plus the optional margin, would
                // put the target element above the top of the page, set the target
                // element to fixed.
                } else if (y >= offsetTop - base.options.marginTop) {
                    console.log('fixed');

                    // Set the target element to fixed.
                    setFixed();

                    // Reset the last offset left because we just went fixed.
                    lastOffsetLeft = -1;

                    // If the page has been scrolled horizontally as well, move the
                    // target element accordingly.
                    setLeft(x);

                } else {
                    console.log('unfixed');
                    // Set the target element to unfixed, placing it where it was
                    // before.
                    setUnfixed();
                }
            } else {
                if (base.options.limit > 0) {
                    if (y + $(window).height() - target.outerHeight() >= base.options.limit - base.options.marginTop) {
                        setUnfixed();
                    } else {
                        setFixed();
                        setLeft(x);
                    }
                } else {
                    setLeft(x);
                }
            }
        }

        // Initializes this plugin. Captures the options passed in, turns this
        // off for iOS, adds the spacer, and binds to the window scroll and
        // resize events.
        base.init = function() {
            // Capture the options for this plugin.
            base.options = $
                    .extend({}, $.ScrollToFixed.defaultOptions, options);

            // Turn off this functionality for iOS devices until we figure out
            // what to do with them, or until iOS5 comes out which is supposed
            // to support position:fixed.
            if (navigator.platform == 'iPad' || navigator.platform == 'iPhone'
                    || navigator.platform == "iPod") {

                return;
            }

            // Put the target element on top of everything that could be below
            // it. This reduces flicker when the target element is transitioning
            // to fixed.
            base.$el.css('z-index', 1000);

            // Create a spacer element to fill the void left by the target
            // element when it goes fixed.
            spacer = $('<div/>');

            // Place the spacer right after the target element.
            base.$el.after(spacer);

            // Reset the target element offsets when the window is resized, then
            // check to see if we need to fix or unfix the target element.
            $(window).bind('resize', function(event) {
                resetScroll();
                checkScroll();
            });

            // When the window scrolls, check to see if we need to fix or unfix
            // the target element.
            $(window).bind('scroll', function(event) {
                checkScroll();
            });

            if (base.options.bottom != -1) {
                setFixed();
            }
        };

        // Initialize the plugin.
        base.init();
    };

    // Sets the option defaults.
    $.ScrollToFixed.defaultOptions = {
        marginTop : 0,
        limit : 0,
        bottom : -1
    };

    // Returns enhanced elements that will fix to the top of the page when the
    // page is scrolled.
    $.fn.scrollToFixed = function(options) {
        return this.each(function() {
            (new $.ScrollToFixed(this, options));
        });
    };
})(jQuery);<|endoftext|>


<commit_before>/*
	Originally by Osvaldas Valutis, www.osvaldas.info
	Available for use under the MIT License
*/

;(function( $, window, document, undefined )
{
	$.fn.doubleTapToGo = function( params )
	{
		if( !( 'ontouchstart' in window ) &&
			!navigator.msMaxTouchPoints &&
			!navigator.userAgent.toLowerCase().match( /windows phone os 7/i ) ) return false;

		this.each( function()
		{
			var curItem = false;

			$( this ).on( 'click', function( e )
			{
				var item = $( this );
				if( item[ 0 ] != curItem[ 0 ] )
				{
					e.preventDefault();
					curItem = item;
				}
			});

			$( document ).on( 'click touchstart MSPointerDown', function( e )
			{
				var resetItem = true,
					parents	  = $( e.target ).parents();

				for( var i = 0; i < parents.length; i++ )
					if( parents[ i ] == curItem[ 0 ] )
						resetItem = false;

				if( resetItem )
					curItem = false;
			});
		});
		return this;
	};
})( jQuery, window, document );<commit_msg>Cleanup spacing.<commit_after>/*
	Originally by Osvaldas Valutis, www.osvaldas.info
	Available for use under the MIT License
*/

;(function($, window, document, undefined) {
	$.fn.doubleTapToGo = function(params) {
		if (!('ontouchstart' in window) &&
			!navigator.msMaxTouchPoints &&
			!navigator.userAgent.toLowerCase().match( /windows phone os 7/i )) return false;

		this.each(function() {
			var curItem = false;

			$(this).on('click', function(e) {
				var item = $(this);
				if (item[0] != curItem[0]) {
					e.preventDefault();
					curItem = item;
				}
			});

			$(document).on('click touchstart MSPointerDown', function(e) {
				var resetItem = true,
					parents = $(e.target).parents();

				for (var i = 0; i < parents.length; i++)
					if (parents[i] == curItem[0])
						resetItem = false;

				if(resetItem)
					curItem = false;
			});
		});
		return this;
	};
})(jQuery, window, document);
<|endoftext|>


<commit_before>import {Entity} from 'aframe-react';
import React from 'react';
import 'aframe-bmfont-text-component';
import $ from 'jquery';

const Height = 1.5;
const Width = 3;

export default props => {
  const extraProps = AFRAME.utils.extend({}, props);
  delete extraProps.color;
  delete extraProps.text;

  let adjustCoordinates = (position, width) => {
    let coordinates = position.split(" ").map((element) => Number(element));
    let xCoordinate = coordinates[0];
    let yCoordinate = coordinates[1];
    let zCoordinate = coordinates[2];
    if (zCoordinate < 0) {
      zCoordinate = (zCoordinate + 1.5).toString();
    } else {
      zCoordinate = (zCoordinate - 1.5).toString();
    }
    xCoordinate = (xCoordinate).toString();
    //temporary
    yCoordinate = yCoordinate.toString();
    return (`${xCoordinate} ${yCoordinate} ${zCoordinate}`);
  }
//
  let adjustEntityCoordinates = (position) => {
    let coordinates = position.split(" ").map((element) => Number(element));
    let xCoordinate = coordinates[0];
    let yCoordinate = coordinates[1];
    return (`${xCoordinate} ${yCoordinate} -3`)
  }

  let adjustEntityRotation = (rotation) => {
    let rotations = rotation.split(" ").map((element) => Number(element));
    let xRotation = rotations[1];
    return (`0 ${xRotation} 0`)
  }

  let adjustIconCoordinates = (position, width, height) => {
    let coordinates = position.split(" ").map((element) => Number(element));
    let xCoordinate = coordinates[0];
    let yCoordinate = coordinates[1];
    let zCoordinate = coordinates[2];
    if (zCoordinate < 0) {
      zCoordinate = (zCoordinate + 0.1).toString();
    } else {
      zCoordinate = (zCoordinate - 0.1).toString();
    }
    xCoordinate = (xCoordinate + (width/2.5)).toString(); //bigger modifier moves left
    //temporary
    // console.log('adjustIconCoordinates height:', height)
    yCoordinate = (yCoordinate + (height/2.75)).toString(); //bigger modifier moves down
    return (`${xCoordinate} ${yCoordinate} ${zCoordinate}`);
  }

  //make sure main text doesn't overflow beyond component parameters
 let reduceMainTextSize = (text) => {
   let output = null;
   // console.log('reduceMainTextSize text:', text)
   if (text.length >= 500) {
     text = text.slice(0, text.length -1);
     let finalPeriod = text.lastIndexOf('. ')
     output = (text.slice(0, finalPeriod) + '.').replace(/\s+/g, ' ').trim()
   } else {
     output = text.replace(/\s+/g, ' ').trim();
   }

   if (output.length >= 500) {
      return reduceMainTextSize(output);
   } else {
    return output;
   }
 }

  let greenAngleCalc = (position, commentID) => {
    // console.log('Position in greenAngleCalc:', position, 'type of position:', typeof position);
    // console.log('Comment ID in TextPlane equals:', commentID)
    position = cardCoordCalc(position);
    let coordinates = position.split(" ").map((element) => Number(element));
    let x = coordinates[0];
    let z = coordinates[2];
    if (z <= 0 && x <= 0){
      return `0 ${90 - Math.abs(Math.atan(z/x) * (180/Math.PI))} 0`
    }
    if (z >= 0 && x >= 0) {
      return `0 ${-90 - Math.abs(Math.atan(z/x) * (180/Math.PI))} 0`
    }
    if (z >= 0 && x <= 0){
      return `0 ${90 + Math.abs(Math.atan(z/x) * (180/Math.PI))} 0`
    }
    if (z <= 0 && x >= 0) {
      return `0 ${Math.abs(Math.atan(z/x) * (180/Math.PI)) - 90} 0`
    }
  }

  let cardCoordCalc = (position) => {  //y=tx, x=tz
    let coordinates = position.split(" ").map((element) => Number(element));
    let tx = coordinates[0];
    let tz = coordinates[2];
    let s = Math.abs(tx/ tz); // s = slope
    let z = 3 / (Math.sqrt(Math.pow(s, 2) + 1));
    let x = s*z;
    x = Math.sign(tx) * x;
    z = Math.sign(tz) * z
    // let gAngle = greenAngleCalc(`${x} 0.5 ${z}`);
    // return {'x': x, 'z': z, 'gAngle': gAngle};
    return `${x.toString()} 0.5 ${z.toString()}`
  }

let addBookmark = (title) => {
  $.get({
    url: '/addBookmark?exactWikiTitle=' + title,
    success: (data) => {
      // console.log(data);
    },
    error: (error) => {
      console.error('error in fetch paragraph', error);
      $('.error').show();
    }
  });
};

let adjustTextPosition = (text, textAdjust) => {
  text = reduceMainTextSize(text);
  if (text.split(" ").length > 7) {
    return (textAdjust - 0.2).toString();
  } else {
    return textAdjust;
  }
}

let formatTime = (string) => {
  // // console.log('BLAHHDEEBLAH formatTime string:' + string);
  let splitArray = string.split(':');
  return Number(splitArray[0]) > 12 ? `${Number(splitArray[0]) -12}:${splitArray[1]}pm` : `${Number(splitArray[0])}:${splitArray[1]}am`
}

let formatDate = (string) => {
  // console.log('BLAHHDEEBLEEE formatDate string:' + string);
  let splitArray = string.split(' ');
  // console.log('HA LA LA splitArray equals:', splitArray)
  return `${splitArray[1]} ${splitArray[2]} ${splitArray[3]} at ${formatTime(splitArray[4])}`;
}

let adjustTimestampPosition = (textAdjust) => {
  return (textAdjust - 0.5).toString();
}

  return (
      <Entity
        animation__rot={{property: 'rotation', dir: 'normal', dur: 500, loop: false, from: '0 0 0', to: greenAngleCalc(props.position, props.commentID)}}
        animation__scale={{property: 'scale', dir: 'normal', dur: 500, loop: false, from: '.1 .1 .1', to: '1 1 1'}}
        id="TextPlane"
        onClick={(data) => {
          // console.log('Within TextPlane.js, event data is:', data.detail.target.id);
          props.hidePlane();
        }}
        position={cardCoordCalc(props.position)}
        rotation={greenAngleCalc(props.position, props.commentID)}
        // material={{opacity: 0.5, transparent: true}}
        // easing='easeInOutQuad'
      >

        {/* Background Plane */}
        <Entity
          geometry={`primitive: plane; width: ${Width}; height: ${Height}`}
          // position='0 0 0'
          rotation='0 0 0'
          material={{color: '#436095', shader: 'flat', opacity: 0.75, transparent: true}}
        />

        {/* Main Image */}
        <Entity geometry={`primitive: circle; radius: 0.33`}
          onClick={() => props.planeClick()}
          material={{side: 'double', src: props.source, opacity: 1, shader: 'flat'}}
          // position={adjustImageCoordinates(props.position, Width, (Height/3)*2)}
          position={`-1.1 0.6 0.18`}
          rotation='0 0 0'
        />

        {/* Header Text */}
        <Entity bmfont-text={{align: 'left', width: '750', color: 'yellow', text: props.header}}
          // position={adjustHeaderTextCoordinates(props.position, Width, Height)}
          position={`${props.headerAdjust} 0.40 0.1`}
          rotation='0 0 0'
          scale='1.85 1.85 0'
        />

        {/* Main Body Text */}
        <Entity bmfont-text={{align: 'left', width: '450', color: 'white', text: reduceMainTextSize(props.text)}} // Max character length = 500
          // position={adjustMainTextCoordinates(props.position, Width, Height)}
          position={`-1 ${adjustTextPosition(props.text, props.textAdjust)} 0.24`}
          rotation='0 0 0'
          scale='1 1 0'
        />

      {/* Timestamp */}
        <Entity bmfont-text={{align: 'left', width: '450', color: 'white', text: formatDate(props.createdAt.toString())}} // Max character length = 500
          // position={adjustMainTextCoordinates(props.position, Width, Height)}
          position={`-0.25 ${adjustTimestampPosition(props.textAdjust)} 0.24`}
          rotation='0 0 0'
          scale='0.75 0.75 0'
        />

      {/*Collapse Icon*/}
      <Entity
            commentID={props.commentID}
            onClick={(data) => {
              // console.log('Within TextPlane.js, event data is:', data.detail.target.id)
              props.hidePlane();
            }}
            geometry={`primitive: plane; width: 0.25; height: 0.25`}
            material={{side: 'double', src: 'url(http://i.imgur.com/xgNToss.png)', opacity: 0.99, shader: 'flat'}}
            position={`1.175 0.475 0.3`}
            rotation='0 0 0'
            scale='1 1 0'
      />

      {/*Bookmark Button
      <Entity
            onClick={() => addBookmark(props.wikiName)}
            geometry={`primitive: plane; width: ${Width/18}; height: ${Height/9}`}
            material={{side: 'double', src: '#bookmark', opacity: 0.99}}
            position={`-2.65 1.2 0.1`}
            rotation='0 0 0'
            scale='1 1 0'
      >
      </Entity>
      */}

    </Entity>
  );
};
<commit_msg>Update textPlane for double-sided viewing during in-image navigation<commit_after>import {Entity} from 'aframe-react';
import React from 'react';
import 'aframe-bmfont-text-component';
import $ from 'jquery';

const Height = 1.5;
const Width = 3;

export default props => {
  const extraProps = AFRAME.utils.extend({}, props);
  delete extraProps.color;
  delete extraProps.text;

  let adjustCoordinates = (position, width) => {
    let coordinates = position.split(" ").map((element) => Number(element));
    let xCoordinate = coordinates[0];
    let yCoordinate = coordinates[1];
    let zCoordinate = coordinates[2];
    if (zCoordinate < 0) {
      zCoordinate = (zCoordinate + 1.5).toString();
    } else {
      zCoordinate = (zCoordinate - 1.5).toString();
    }
    xCoordinate = (xCoordinate).toString();
    //temporary
    yCoordinate = yCoordinate.toString();
    return (`${xCoordinate} ${yCoordinate} ${zCoordinate}`);
  }
//
  let adjustEntityCoordinates = (position) => {
    let coordinates = position.split(" ").map((element) => Number(element));
    let xCoordinate = coordinates[0];
    let yCoordinate = coordinates[1];
    return (`${xCoordinate} ${yCoordinate} -3`)
  }

  let adjustEntityRotation = (rotation) => {
    let rotations = rotation.split(" ").map((element) => Number(element));
    let xRotation = rotations[1];
    return (`0 ${xRotation} 0`)
  }

  let adjustIconCoordinates = (position, width, height) => {
    let coordinates = position.split(" ").map((element) => Number(element));
    let xCoordinate = coordinates[0];
    let yCoordinate = coordinates[1];
    let zCoordinate = coordinates[2];
    if (zCoordinate < 0) {
      zCoordinate = (zCoordinate + 0.1).toString();
    } else {
      zCoordinate = (zCoordinate - 0.1).toString();
    }
    xCoordinate = (xCoordinate + (width/2.5)).toString(); //bigger modifier moves left
    //temporary
    // console.log('adjustIconCoordinates height:', height)
    yCoordinate = (yCoordinate + (height/2.75)).toString(); //bigger modifier moves down
    return (`${xCoordinate} ${yCoordinate} ${zCoordinate}`);
  }

  //make sure main text doesn't overflow beyond component parameters
 let reduceMainTextSize = (text) => {
   let output = null;
   // console.log('reduceMainTextSize text:', text)
   if (text.length >= 500) {
     text = text.slice(0, text.length -1);
     let finalPeriod = text.lastIndexOf('. ')
     output = (text.slice(0, finalPeriod) + '.').replace(/\s+/g, ' ').trim()
   } else {
     output = text.replace(/\s+/g, ' ').trim();
   }

   if (output.length >= 500) {
      return reduceMainTextSize(output);
   } else {
    return output;
   }
 }

  let greenAngleCalc = (position, commentID) => {
    // console.log('Position in greenAngleCalc:', position, 'type of position:', typeof position);
    // console.log('Comment ID in TextPlane equals:', commentID)
    position = cardCoordCalc(position);
    let coordinates = position.split(" ").map((element) => Number(element));
    let x = coordinates[0];
    let z = coordinates[2];
    if (z <= 0 && x <= 0){
      return `0 ${90 - Math.abs(Math.atan(z/x) * (180/Math.PI))} 0`
    }
    if (z >= 0 && x >= 0) {
      return `0 ${-90 - Math.abs(Math.atan(z/x) * (180/Math.PI))} 0`
    }
    if (z >= 0 && x <= 0){
      return `0 ${90 + Math.abs(Math.atan(z/x) * (180/Math.PI))} 0`
    }
    if (z <= 0 && x >= 0) {
      return `0 ${Math.abs(Math.atan(z/x) * (180/Math.PI)) - 90} 0`
    }
  }

  let cardCoordCalc = (position) => {  //y=tx, x=tz
    let coordinates = position.split(" ").map((element) => Number(element));
    let tx = coordinates[0];
    let tz = coordinates[2];
    let s = Math.abs(tx/ tz); // s = slope
    let z = 3 / (Math.sqrt(Math.pow(s, 2) + 1));
    let x = s*z;
    x = Math.sign(tx) * x;
    z = Math.sign(tz) * z
    // let gAngle = greenAngleCalc(`${x} 0.5 ${z}`);
    // return {'x': x, 'z': z, 'gAngle': gAngle};
    return `${x.toString()} 0.5 ${z.toString()}`
  }

let addBookmark = (title) => {
  $.get({
    url: '/addBookmark?exactWikiTitle=' + title,
    success: (data) => {
      // console.log(data);
    },
    error: (error) => {
      console.error('error in fetch paragraph', error);
      $('.error').show();
    }
  });
};

let adjustTextPosition = (text, textAdjust) => {
  text = reduceMainTextSize(text);
  if (text.split(" ").length > 7) {
    return (textAdjust - 0.2).toString();
  } else {
    return textAdjust;
  }
}

let formatTime = (string) => {
  // // console.log('BLAHHDEEBLAH formatTime string:' + string);
  let splitArray = string.split(':');
  return Number(splitArray[0]) > 12 ? `${Number(splitArray[0]) -12}:${splitArray[1]}pm` : `${Number(splitArray[0])}:${splitArray[1]}am`
}

let formatDate = (string) => {
  // console.log('BLAHHDEEBLEEE formatDate string:' + string);
  let splitArray = string.split(' ');
  // console.log('HA LA LA splitArray equals:', splitArray)
  return `${splitArray[1]} ${splitArray[2]} ${splitArray[3]} at ${formatTime(splitArray[4])}`;
}

let adjustTimestampPosition = (textAdjust) => {
  return (textAdjust - 0.5).toString();
}

  return (
      <Entity
        animation__rot={{property: 'rotation', dir: 'normal', dur: 500, loop: false, from: '0 0 0', to: greenAngleCalc(props.position, props.commentID)}}
        animation__scale={{property: 'scale', dir: 'normal', dur: 500, loop: false, from: '.1 .1 .1', to: '1 1 1'}}
        id="TextPlane"
        onClick={(data) => {
          // console.log('Within TextPlane.js, event data is:', data.detail.target.id);
          props.hidePlane();
        }}
        position={cardCoordCalc(props.position)}
        rotation={greenAngleCalc(props.position, props.commentID)}
        // material={{opacity: 0.5, transparent: true}}
        // easing='easeInOutQuad'
      >

        {/* Background Plane */}
        <Entity
          geometry={`primitive: plane; width: ${Width}; height: ${Height}`}
          // position='0 0 0'
          rotation='0 0 0'
          material={{color: '#436095', shader: 'flat', opacity: 0.75, transparent: true, side: 'double'}}
        />

        {/* Main Image */}
        <Entity geometry={`primitive: circle; radius: 0.33`}
          onClick={() => props.planeClick()}
          material={{side: 'double', src: props.source, opacity: 1, shader: 'flat'}}
          // position={adjustImageCoordinates(props.position, Width, (Height/3)*2)}
          position={`-1.1 0.6 0.18`}
          rotation='0 0 0'
        />

        {/* Header Text */}
        <Entity bmfont-text={{align: 'left', width: '750', color: 'yellow', text: props.header}}
          // position={adjustHeaderTextCoordinates(props.position, Width, Height)}
          position={`${props.headerAdjust} 0.40 0.1`}
          rotation='0 0 0'
          scale='1.85 1.85 0'
        />

        {/* Main Body Text */}
        <Entity bmfont-text={{align: 'left', width: '450', color: 'white', text: reduceMainTextSize(props.text)}} // Max character length = 500
          // position={adjustMainTextCoordinates(props.position, Width, Height)}
          position={`-1 ${adjustTextPosition(props.text, props.textAdjust)} 0.24`}
          rotation='0 0 0'
          scale='1 1 0'
        />

      {/* Timestamp */}
        <Entity bmfont-text={{align: 'left', width: '450', color: 'white', text: formatDate(props.createdAt.toString())}} // Max character length = 500
          // position={adjustMainTextCoordinates(props.position, Width, Height)}
          position={`-0.25 ${adjustTimestampPosition(props.textAdjust)} 0.24`}
          rotation='0 0 0'
          scale='0.75 0.75 0'
        />

      {/*Collapse Icon*/}
      <Entity
            commentID={props.commentID}
            onClick={(data) => {
              // console.log('Within TextPlane.js, event data is:', data.detail.target.id)
              props.hidePlane();
            }}
            geometry={`primitive: plane; width: 0.25; height: 0.25`}
            material={{side: 'double', src: 'url(http://i.imgur.com/xgNToss.png)', opacity: 0.99, shader: 'flat'}}
            position={`1.175 0.475 0.3`}
            rotation='0 0 0'
            scale='1 1 0'
      />

      {/*Bookmark Button
      <Entity
            onClick={() => addBookmark(props.wikiName)}
            geometry={`primitive: plane; width: ${Width/18}; height: ${Height/9}`}
            material={{side: 'double', src: '#bookmark', opacity: 0.99}}
            position={`-2.65 1.2 0.1`}
            rotation='0 0 0'
            scale='1 1 0'
      >
      </Entity>
      */}

    </Entity>
  );
};
<|endoftext|>


<commit_before>import getGraphSpecificInfo from './graphSepcific/graphSpecificInfo.js';
import scene from './scene.js';

export default getBaseNodeViewModel;

function getBaseNodeViewModel(nodeId) {
  var graphName = scene.getGraphName();
  var graphSpecificInfo = getGraphSpecificInfo(graphName);
  var nodeInfo = scene.getNodeInfo(nodeId)

  return {
    name: nodeInfo.name,
    id: nodeInfo.id,

    inDegree: nodeInfo.in,
    inDegreeLabel: graphSpecificInfo.getInDegreeLabel(nodeInfo.in),

    outDegree: nodeInfo.out,
    outDegreeLabel: graphSpecificInfo.getOutDegreeLabel(nodeInfo.out)
  };
}
<commit_msg>Added formatting for deps count<commit_after>import getGraphSpecificInfo from './graphSepcific/graphSpecificInfo.js';
import scene from './scene.js';
import formatNumber from '../utils/formatNumber.js';

export default getBaseNodeViewModel;

function getBaseNodeViewModel(nodeId) {
  var graphName = scene.getGraphName();
  var graphSpecificInfo = getGraphSpecificInfo(graphName);
  var nodeInfo = scene.getNodeInfo(nodeId);

  return {
    name: nodeInfo.name,
    id: nodeInfo.id,

    inDegree: formatNumber(nodeInfo.in),
    inDegreeLabel: graphSpecificInfo.getInDegreeLabel(nodeInfo.in),

    outDegree: formatNumber(nodeInfo.out),
    outDegreeLabel: graphSpecificInfo.getOutDegreeLabel(nodeInfo.out)
  };
}
<|endoftext|>


<commit_before>var sampleArray = mobx.observable(["Matt", "Kelly"]);

var ListView = mobxReact.observer(function ListView() {
       return React.createElement('ul', null,
            this.props.list.map(function (name) {
                return React.createElement('div', {key: name}, name);
            })
        );
    }
);
ListView.propTypes = {
    list: React.PropTypes.oneOfType([
        React.PropTypes.array,
        React.PropTypes.object
    ]).isRequired
};

ReactDOM.render(
    React.createElement(ListView, {list: sampleArray}),
    document.getElementById('mount')
);

<commit_msg>Updated the array proptype example to use  instanceof<commit_after>var sampleArray = mobx.observable(["Matt", "Kelly"]);

var ListView = mobxReact.observer(function ListView() {
       return React.createElement('ul', null,
            this.props.list.map(function (name) {
                return React.createElement('div', {key: name}, name);
            })
        );
    }
);
ListView.propTypes = {
    list: React.PropTypes.oneOfType([
        React.PropTypes.array,
        React.PropTypes.instanceOf(Array)
    ]).isRequired
};

ReactDOM.render(
    React.createElement(ListView, {list: sampleArray}),
    document.getElementById('mount')
);

<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { isEmpty, debounce, pick, omit } from 'underscore';

import compactObject from 'react/util/compactObject';

import Box from 'react/components/UI/Box';
import Icons from 'react/components/UI/Icons';
import { Input } from 'react/components/UI/Inputs';

const OUTER_PROPS_KEYS = ['m', 'mt', 'mr', 'mb', 'ml', 'mx', 'my'];

const Icon = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: 2.5em;
  cursor: pointer;
`;

class SearchInput extends Component {
  static propTypes = {
    query: PropTypes.string,
    onFocus: PropTypes.func,
    onBlur: PropTypes.func,
    onQueryChange: PropTypes.func,
    onDebouncedQueryChange: PropTypes.func,
    debounceWait: PropTypes.number,
    forwardRef: PropTypes.oneOfType([
      PropTypes.func,
      PropTypes.shape({ current: PropTypes.any }),
    ]),
  }

  static defaultProps = {
    query: '',
    onFocus: () => {},
    onBlur: () => {},
    onQueryChange: () => {},
    onDebouncedQueryChange: () => {},
    debounceWait: 250,
    forwardRef: null,
  }

  constructor(props) {
    super(props);

    const { debounceWait, onDebouncedQueryChange } = props;

    this.handleDebouncedQueryChange = debounce(onDebouncedQueryChange, debounceWait);

    this.state = {
      mode: 'resting',
      query: props.query,
    };
  }

  componentWillReceiveProps(nextProps) {
    if (isEmpty(nextProps.query) && !isEmpty(this.state.query)) {
      this.resetState();
    }
  }

  resetState = () => {
    this.setState({ query: '', mode: 'resting' });
    this.input.value = '';
    this.input.focus();
  }

  handleChange = ({ target: { value: query } }) => {
    const currentState = { query, mode: 'active' };

    if (isEmpty(query)) {
      currentState.mode = 'resting';
    }

    this.setState(currentState);
    this.props.onQueryChange(query);
    this.handleDebouncedQueryChange(query);
  }

  handleReset = () => {
    this.resetState();
    this.props.onQueryChange('');
    this.props.onDebouncedQueryChange('');
  }

  render() {
    const {
      query: _query,
      onFocus,
      onBlur,
      onQueryChange: _onQueryChange,
      onDebouncedQueryChange: _onDebouncedQueryChange,
      debounceWait: _debounceWait,
      forwardRef,
      ...rest
    } = this.props;

    const { mode, query } = this.state;

    const outerProps = compactObject(pick(rest, ...OUTER_PROPS_KEYS));
    const innerProps = omit(rest, ...OUTER_PROPS_KEYS);

    return (
      <Box position="relative" ref={forwardRef} {...outerProps}>
        <Icon onClick={this.handleReset}>
          <Icons
            color="gray.medium"
            name={{
              resting: 'MagnifyingGlass',
              active: 'X',
            }[mode]}
          />
        </Icon>

        <Input
          px="2.5em"
          borderColor="gray.regular"
          {...innerProps}
          ref={(input) => { this.input = input; }}
          onFocus={onFocus}
          onBlur={onBlur}
          onChange={this.handleChange}
          defaultValue={query}
        />
      </Box>
    );
  }
}

export default React.forwardRef((props, ref) => (
  <SearchInput forwardRef={ref} {...props} />));
<commit_msg>Never auto- anything in search inputs<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { isEmpty, debounce, pick, omit } from 'underscore';

import compactObject from 'react/util/compactObject';

import Box from 'react/components/UI/Box';
import Icons from 'react/components/UI/Icons';
import { Input } from 'react/components/UI/Inputs';

const OUTER_PROPS_KEYS = ['m', 'mt', 'mr', 'mb', 'ml', 'mx', 'my'];

const Icon = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: 2.5em;
  cursor: pointer;
`;

class SearchInput extends Component {
  static propTypes = {
    query: PropTypes.string,
    onFocus: PropTypes.func,
    onBlur: PropTypes.func,
    onQueryChange: PropTypes.func,
    onDebouncedQueryChange: PropTypes.func,
    debounceWait: PropTypes.number,
    forwardRef: PropTypes.oneOfType([
      PropTypes.func,
      PropTypes.shape({ current: PropTypes.any }),
    ]),
  }

  static defaultProps = {
    query: '',
    onFocus: () => {},
    onBlur: () => {},
    onQueryChange: () => {},
    onDebouncedQueryChange: () => {},
    debounceWait: 250,
    forwardRef: null,
  }

  constructor(props) {
    super(props);

    const { debounceWait, onDebouncedQueryChange } = props;

    this.handleDebouncedQueryChange = debounce(onDebouncedQueryChange, debounceWait);

    this.state = {
      mode: 'resting',
      query: props.query,
    };
  }

  componentWillReceiveProps(nextProps) {
    if (isEmpty(nextProps.query) && !isEmpty(this.state.query)) {
      this.resetState();
    }
  }

  resetState = () => {
    this.setState({ query: '', mode: 'resting' });
    this.input.value = '';
    this.input.focus();
  }

  handleChange = ({ target: { value: query } }) => {
    const currentState = { query, mode: 'active' };

    if (isEmpty(query)) {
      currentState.mode = 'resting';
    }

    this.setState(currentState);
    this.props.onQueryChange(query);
    this.handleDebouncedQueryChange(query);
  }

  handleReset = () => {
    this.resetState();
    this.props.onQueryChange('');
    this.props.onDebouncedQueryChange('');
  }

  render() {
    const {
      query: _query,
      onFocus,
      onBlur,
      onQueryChange: _onQueryChange,
      onDebouncedQueryChange: _onDebouncedQueryChange,
      debounceWait: _debounceWait,
      forwardRef,
      ...rest
    } = this.props;

    const { mode, query } = this.state;

    const outerProps = compactObject(pick(rest, ...OUTER_PROPS_KEYS));
    const innerProps = omit(rest, ...OUTER_PROPS_KEYS);

    return (
      <Box position="relative" ref={forwardRef} {...outerProps}>
        <Icon onClick={this.handleReset}>
          <Icons
            color="gray.medium"
            name={{
              resting: 'MagnifyingGlass',
              active: 'X',
            }[mode]}
          />
        </Icon>

        <Input
          px="2.5em"
          borderColor="gray.regular"
          {...innerProps}
          ref={(input) => { this.input = input; }}
          onFocus={onFocus}
          onBlur={onBlur}
          onChange={this.handleChange}
          defaultValue={query}
          autoCorrect="off"
          autoComplete="off"
          autoCapitalize="off"
          spellCheck="false"
        />
      </Box>
    );
  }
}

export default React.forwardRef((props, ref) => (
  <SearchInput forwardRef={ref} {...props} />));
<|endoftext|>