<commit_before>/**
# Data Table

Data-table is intended to make quick work of simple tables.

```
<b8r-component
  path="../components/data-table.component.js"
  data-bind="
    component(rows)=_component_.rows
    component(config)=_component_.config
  "
>
</b8r-component>
<script>
  set('config', {
    columns: [
      {
        name: 'emoji',
        path: '.chars',
        width: 60,
      },
      {
        name: 'code',
        path: '.code',
        width: 100,
      },
      {
        name: 'name',
        path: '.name',
        width: 200,
      },
      {
        name: 'category',
        path: '.category',
        visible: false,
        width: 120,
      },
      {
        name: 'sub-category',
        path: '.subcatory',
        visible: false,
        width: 120,
      },
    ]
  })
  b8r.json('https://raw.githubusercontent.com/tonioloewald/emoji-metadata/master/emoji-metadata.json').then(rows => set({rows}))
</script>
```
*/

import { trackDrag } from '../lib/track-drag.js'

export default {
  css: `
    ._component_ {
      --table-height: 400px;
      --table-width: 100%;
      --columns: 40px 80px 120px 160px 200px auto;
      --table-head-font: bold 14px sans-serif;
      --table-body-font: 14px sans-serif;
      width: var(--table-width);
      height: var(--table-height);
      display: flex;
      flex-direction: column;
      cursor: default;
    }

    ._component_ .t-head {
      flex: 0 0 auto;
      background: var(--black-10);
      font: var(--table-head-font);
    }

    ._component_ .t-body {
      flex: 1 1;
      overflow-y: scroll;
      overflow-y: overlay;
      font: var(--table-body-font);
    }

    ._component_ .t-row {
      display: grid;
      grid-template-columns: var(--columns);
      vertical-align: middle;
      line-height: 20px;
    }

    ._component_ .t-row:nth-child(4n+3),
    ._component_ .t-row:nth-child(4n) {
      background: var(--black-5);
    }

    ._component_ .t-row > * {
      padding: 2px 10px;
    }

    ._component_ .t-head > * {
      padding: 5px 10px;
      position: relative;
    }

    ._component_ .t-head > *+* {
      border-left: 1px solid var(--black-10);
    }

    ._component_ .t-head > *+*:before {
      content: ' ';
      position: absolute;
      display: block;
      width: 10px;
      top: 0;
      left: -5px;
      bottom: 0;
      cursor: col-resize;
    }

    ._component_ :not(.t-head).t-row:hover {
      background: var(--black-10);
    }`,
  html: `

`,
  initialValue: ({ b8r, get, component, on, findOne }) => {
    b8r.addDataBinding(component, 'method(_component_.renderGrid)', '_component_.config.columns')
    return {
      visibleColumns (columns) {
        return columns.filter(({ visible }) => visible !== false)
      },
      resizeColumn (evt) {
        if (evt.offsetX <= 5) {
          console.log(evt.target.textContent, b8r.elementIndex(evt.target))
          const edgeIndex = b8r.elementIndex(evt.target) - 1
          const columns = b8r.cssVar(component, '--columns').split(' ')
          columns.pop()
          const liveResize = get('rows').length <= get('config.maxRowsForLiveColumnResize')
          const widths = columns.map(x => parseInt(x, 10))
          const thead = findOne('.t-head')
          trackDrag(evt, widths[edgeIndex], 0, (w, _y, _dx, _dy, dragEnded) => {
            widths[edgeIndex] = w < 20 ? 20 : w
            const gridSpec = widths.map(w => w + 'px').join(' ') + ' auto'
            if (liveResize || dragEnded) {
              b8r.cssVar(thead, '--columns', '')
              b8r.cssVar(component, '--columns', gridSpec)
            } else {
              b8r.cssVar(thead, '--columns', gridSpec)
            }
          })
        }
      },
      renderGrid (table, columns) {
        const widths = get('visibleColumns')(columns).map(col => col.width + 'px')
        widths[widths.length - 1] = 'auto'
        b8r.cssVar(table, '--columns', widths.join(' '))
      },
      renderRow (tableBody, rowData) {
        const { config, visibleColumns } = get()
        const rowTemplate = tableBody.querySelector('[data-list]')
        rowTemplate.textContent = ''

        if (config.columns) {
          visibleColumns(config.columns)
            .forEach(async ({ path }) => {
              const span = b8r.create('span')
              span.dataset.bind = `text=${path}`
              rowTemplate.appendChild(span)
            })
        }
        b8r.bindAll(rowTemplate)
      },
      config: {
        maxRowsForLiveColumnResize: 100,
        columns: []
      },
      rows: []
    }
  }
}
<commit_msg>Update data-table.component.js<commit_after>/**
# Data Table

Data-table is intended to make quick work of simple tables.

```
<b8r-component
  path="../components/data-table.component.js"
  data-bind="
    component(rows)=_component_.rows
    component(config)=_component_.config
  "
>
</b8r-component>
<script>
  set('config', {
    columns: [
      {
        name: 'emoji',
        path: '.chars',
        width: 60,
      },
      {
        name: 'code',
        path: '.code',
        width: 100,
      },
      {
        name: 'name',
        path: '.name',
        width: 200,
      },
      {
        name: 'category',
        path: '.category',
        visible: false,
        width: 120,
      },
      {
        name: 'sub-category',
        path: '.subcatory',
        visible: false,
        width: 120,
      },
    ]
  })
  b8r.json('https://raw.githubusercontent.com/tonioloewald/emoji-metadata/master/emoji-metadata.json').then(rows => set({rows}))
</script>
```
*/

import { trackDrag } from '../lib/track-drag.js'

export default {
  css: `
    ._component_ {
      --table-height: 400px;
      --table-width: 100%;
      --columns: 40px 80px 120px 160px 200px auto;
      --table-head-font: bold 14px sans-serif;
      --table-body-font: 14px sans-serif;
      width: var(--table-width);
      height: var(--table-height);
      display: flex;
      flex-direction: column;
      cursor: default;
    }

    ._component_ .t-head {
      flex: 0 0 auto;
      background: var(--black-10);
      font: var(--table-head-font);
    }

    ._component_ .t-body {
      flex: 1 1;
      overflow-y: scroll;
      overflow-y: overlay;
      font: var(--table-body-font);
    }

    ._component_ .t-row {
      display: grid;
      grid-template-columns: var(--columns);
      vertical-align: middle;
      line-height: 20px;
    }

    ._component_ .t-row:nth-child(4n+3),
    ._component_ .t-row:nth-child(4n) {
      background: var(--black-5);
    }

    ._component_ .t-row > * {
      padding: 2px 10px;
    }

    ._component_ .t-head > * {
      padding: 5px 10px;
      position: relative;
    }

    ._component_ .t-head > *+*:before {
      content: ' ';
      position: absolute;
      display: block;
      width: 10px;
      top: 0;
      left: -5px;
      bottom: 0;
    }

    ._component_ .t-head > *+*:after {
      content: ' ';
      position: absolute;
      display: block;
      width: 5px;
      top: 0;
      left: -5px;
      height: 100vh;
      border-left: 1px solid var(--black-10);
      cursor: col-resize;
    }

    ._component_ :not(.t-head).t-row:hover {
      background: var(--black-10);
    }`,
  html: `

`,
  initialValue: ({ b8r, get, component, on, findOne }) => {
    b8r.addDataBinding(component, 'method(_component_.renderGrid)', '_component_.config.columns')
    return {
      visibleColumns (columns) {
        return columns.filter(({ visible }) => visible !== false)
      },
      resizeColumn (evt) {
        if (evt.offsetX <= 5) {
          console.log(evt.target.textContent, b8r.elementIndex(evt.target))
          const edgeIndex = b8r.elementIndex(evt.target) - 1
          const columns = b8r.cssVar(component, '--columns').split(' ')
          columns.pop()
          const liveResize = get('rows').length <= get('config.maxRowsForLiveColumnResize')
          const widths = columns.map(x => parseInt(x, 10))
          const thead = findOne('.t-head')
          trackDrag(evt, widths[edgeIndex], 0, (w, _y, _dx, _dy, dragEnded) => {
            widths[edgeIndex] = w < 20 ? 20 : w
            const gridSpec = widths.map(w => w + 'px').join(' ') + ' auto'
            if (liveResize || dragEnded) {
              b8r.cssVar(thead, '--columns', '')
              b8r.cssVar(component, '--columns', gridSpec)
            } else {
              b8r.cssVar(thead, '--columns', gridSpec)
            }
          })
        }
      },
      renderGrid (table, columns) {
        const widths = get('visibleColumns')(columns).map(col => col.width + 'px')
        widths[widths.length - 1] = 'auto'
        b8r.cssVar(table, '--columns', widths.join(' '))
      },
      renderRow (tableBody, rowData) {
        const { config, visibleColumns } = get()
        const rowTemplate = tableBody.querySelector('[data-list]')
        rowTemplate.textContent = ''

        if (config.columns) {
          visibleColumns(config.columns)
            .forEach(async ({ path }) => {
              const span = b8r.create('span')
              span.dataset.bind = `text=${path}`
              rowTemplate.appendChild(span)
            })
        }
        b8r.bindAll(rowTemplate)
      },
      config: {
        maxRowsForLiveColumnResize: 100,
        columns: []
      },
      rows: []
    }
  }
}
<|endoftext|>


<commit_before>// if (typeof require == 'function') var elation = require("utils/elation");
elation.extend("events", {
  events: {},
  cloneattrs: ['type', 'bubbles', 'cancelable', 'view', 'detail', 'screenX', 'screenY', 'clientX', 'clientY', 'ctrlKey', 'shiftKey', 'altKey', 'metaKey', 'button', 'relatedTarget', 'target', 'element', 'data', 'origin', 'timeStamp', 'returnValue', 'cancelBubble', 'keyCode', 'dataTransfer', 'deltaX', 'deltaY', 'deltaZ', 'deltaMode'],

  eventstats: {},

  fire: function(type, data, target, element, fn) {
    var ev = this.getEvent(type, data, target, element, fn);

    //console.log('fire!', ev, type, data, target, element, fn);
    return elation.events.fireEvent(ev);
  },
  getEvent: function(type, data, target, element, fn) {
    var extras = {};

    if (typeof type == 'object') {
      data = elation.utils.any(type.data, data);
      target = type.target || target;
      element = type.element || element;
      fn = type.fn || fn;
      var cloneev = type.event || {};

      for (var i = 0; i < this.cloneattrs.length; i++) {
        var attr = this.cloneattrs[i];
        if (!elation.utils.isNull(type[attr])) extras[attr] = type[attr];
        else if (!elation.utils.isNull(cloneev[attr])) extras[attr] = cloneev[attr];
      }

      if (type.event) {
        var realevent = type.event;
        // If we have a real event, we want our synthesized event to pass stopPropagation and preventDefault calls through
        if (!extras.stopPropagation) {
          extras.stopPropagation = elation.bind(extras, function() {
            realevent.stopPropagation();
            this.cancelBubble = true;
          });
        }
        if (!extras.preventDefault) extras.preventDefault = elation.bind(type.event, type.event.preventDefault);
      }

      if (!elation.utils.isNull(type.clientX)) extras.clientX = type.clientX;
      if (!elation.utils.isNull(type.clientY)) extras.clientY = type.clientY;
      if (!elation.utils.isNull(type.button)) extras.button = type.button;
      if (!elation.utils.isNull(type.keyCode)) extras.keyCode = type.keyCode;

      extras.fn = fn;

      type = type.type;
    }

    extras.data = data;
    extras.target = target;
    extras.element = element;
    extras.fn = fn;
/*
    var ev = {
      type: type,
      element: element,
      fn: fn,
      extras: extras,
      data: data
    };
*/
    return extras;
  },
  fireEvent: function(realevent, element) {
    //console.log('fireEvent:',realevent);
    var type = realevent.type,
        data = realevent.data,
        element = element || realevent.element,
        target = realevent.target,
        fn = realevent.fn;

    if (!type)
      return false;

    if (!this.eventstats[type]) this.eventstats[type] = 0;
    this.eventstats[type]++;

    var list = this.events[type],
        original_events = [],
        events = [],
        event;

    if (!list) {
      this.events[type] = [];
      return [];
    }

    // gather all the events associated with this event type
    // filter by [element] and/or [fn] if present
    for (var i=0; i<list.length; i++) {
      event = list[i];
      if (fn || element) {
        if ((fn && event.origin == fn) || (element && event.target == element) || elation.utils.isNull(event.target)) {
          original_events.push(event);
        } else {
          continue;
        }
      } else {
        original_events.push(event);
      }
    }

    // fire each event
    var extrakeys = Object.keys(realevent);

    for (var i=0; i<original_events.length; i++) {
      var eventObj = original_events[i];

          // break reference to eventObj so original doesn't get overwritten
      var event = elation.events.clone(eventObj, {
            type: type,
            target: target,
            data: data,
            timeStamp: new Date().getTime()
          });
      for (var j = 0; j < extrakeys.length; j++) {
        if (typeof realevent[extrakeys[j]] != 'undefined') {
          event[extrakeys[j]] = realevent[extrakeys[j]];
        }
      }
      if (!event.origin)
        continue;

      var cont = true;

      if (typeof event.origin == 'function') {
        cont = event.origin(event);
      } else if (typeof event.origin.handleEvent != 'undefined') {
        cont = event.origin.handleEvent(event);
      }

      events.push(event);

      if (cont === false || event.cancelBubble || realevent.cancelBubble) {
        // FIXME - I keep finding myself commenting this out, and then wondering why I've commented it out later
        //         Note to self - if you have to change this setting again, at least document why it needs to change!
        event.cancelBubble = true;
        break;
      }
    }

    // return all event objects that were fired
    return events;
  },

  register: function(types, fn, element) {
    var types = types.split(','),
        type;

    for (var i=0; i<types.length; i++) {
      type = types[i];

      if (!this.events[type]) {
        if (fn || element)
          this._register(element, type, fn);
        else
          this.events[type] = [];
      }
    }
  },

  _register: function(element, type, fn, options) {

    var event = {
      type: type,
      target: element,
      origin: fn,
      preventDefault: function() { this.returnValue = false; return; },
      stopPropagation: function() { this.cancelBubble = true; return; },
      returnValue: true,
      cancelBubble: false
    };

    if (!elation.events.events[type])
      elation.events.events[type] = [];

    elation.events.events[type].push(event);
  },
  _unregister: function(element, type, fn) {
    if (elation.events.events[type]) {
      var remaining = [];
      for (var i = 0; i < elation.events.events[type].length; i++) {
        var ev = elation.events.events[type][i];
        if (ev.type == type && ev.target == element && ev.origin == fn) {
          //elation.events.events[type].splice(i--, 1);
        } else {
          remaining.push(ev);
        }
      }
      if (elation.events.events[type].length != remaining.length) {
        elation.events.events[type] = remaining;
      }
    }
  },

  // syntax: add(element || [ elements ], "type1,type2,type3", function || object);
  add: function(elements, types, fn, options) {
    if (!types || !fn || typeof types != "string")
      return;

    var elements = elation.utils.isNull(elements)
          ? [null]
          : !elation.utils.isArray(elements) || elements == window
            ? [ elements ]
            : elements,
        types = types.split(',');

    if (typeof fn == "string") {
      fn = (function(func) { return function(ev) { eval(func); }; })(fn);
    }

    for (var e=0; e<elements.length; e++) {
      var element = elements[e];

      if (typeof element != 'object')
        continue;

      for (var i=0; i<types.length; i++) {
        var type = types[i];

        elation.events._register(element, type, fn);

        if (!element)
          continue;

        if (type.toLowerCase() == 'transitionend') {
          if ('onwebkittransitionend' in window) type = 'webkitTransitionEnd';
          else if ('onotransitionend' in element || navigator.appName == 'Opera') type = 'oTransitionEnd';
        }

        if ("addEventListener" in element) {
          //if (type == 'mousewheel' && elation.browser.type != 'safari')
          //  type = 'DOMMouseScroll';
          if (typeof fn == "object" && fn.handleEvent) {
            element[type+fn] = function(e) {
              fn.handleEvent(e);
            }
            element.addEventListener(type, element[(type + fn)], options);
          } else {
            element.addEventListener(type, fn, options);
          }
        } else if (element.attachEvent) {
          if (typeof fn == "object" && fn.handleEvent) {
            element[type+fn] = function() {
              fn.handleEvent(elation.events.fix(window.event));
            }
          } else {
            element["e" + type + fn] = fn;
            element[type + fn] = function() {
              if (typeof element["e" + type + fn] == 'function')
                element["e" + type + fn](elation.events.fix(window.event));
            }
          }

          element.attachEvent("on" + type, element[type + fn]);
        }
      }
    }

    return this;
  },

  // syntax: remove(element || [ elements ], "type1,type2,type3", reference);
  remove: function(elements, types, fn) {
    //if (!elements || !types || !fn || typeof types != "string")
    //  return;

    //var  elements = (!elation.utils.isNull(elements.nodeName) || elements == window) ? [ elements ] : elements;
    if (!elation.utils.isArray(elements)) {
      elements = [elements];
    }
    var types = types.split(',');

    for (var e=0; e<elements.length; e++) {
      var element = elements[e];

      if (typeof element != 'object')
        continue;

      for (var i=0; i<types.length; i++) {
        var type = types[i];

        elation.events._unregister(element, type, fn);

        if (element) {
          if (element.removeEventListener) {
            if (typeof fn == "object" && fn.handleEvent) {
              element.removeEventListener(type, element[type+fn], false);
              delete element[type + fn];
            } else {
              element.removeEventListener(type, fn, false);
            }
          } else if (element.detachEvent) {
            if (typeof element[type + fn] == "function")
              element.detachEvent("on" + type, element[type + fn]);

            element[type + fn] = null;
            element["e" + type + fn] = null;
          }
        }
      }
    }

    return this;
  },

  fix: function(event) {
    event.preventDefault = function() {
      this.returnValue = false;
    }

    event.stopPropagation = function() {
      this.cancelBubble = true;
    }

    //event.preventDefault = this.preventDefault;
    //event.stopPropagation = this.stopPropagation;

    return event;
  },

  getTarget: function(event) {
    return window.event ? event.srcElement : event.target;
  },

  getRelated: function(event) {
    var reltg;

    if (event.relatedTarget) {
      reltg = event.relatedTarget;
    } else {
      if (event.type == "mouseover")
        reltg = event.fromElement;
      else if (event.type == "mouseout")
        reltg = event.toElement;
      else
        reltg = document;
    }

    return reltg;
  },

  getEventTarget: function(event, parentClassName) {
    var target;

    if (!event)
      var event = window.event;

    if (event.target)
      target = event.target;
    else if (event.srcElement)
      target = event.srcElement;

    if (target.nodeType == 3)
      target = target.parentNode; // Defeat Safari bug

    if (parentClassName) {
      // Make sure we're working with the correct element
      var classUp, classDown;

      if (parentClassName.indexOf(">")) {
        var classes = parentClassName.split(">", 2);
        classDown = classes[0];
        classUp = classes[1];
      } else {
        classDown = parentClassName;
      }

      // First run DOWN the heirarchy to find the base class...
      while (!elation.html.hasclass(target,classDown) && target.parentNode) {
        target = target.parentNode;
      }

      // Now if we've specified a child to attach to, find it!
      if (classUp) {
        var elements;
        elements = elation.find("." + classUp, target);
        if (elements.length > 0) {
          target = elements[0];
        }
      }
    }

    return target;
  },
  isTransition: function(ev, parent) {
    var tg = this.getTarget(ev),
        reltg = this.getRelated(ev);
    return (elation.utils.isin(parent, tg) && !elation.utils.isin(parent, reltg));
  },

  // returns mouse or all finger touch coords
  coords: function(event) {
    if (typeof event.touches != 'undefined' && event.touches.length > 0) {
      var c = {
        x: event.touches[0].pageX,
        y: event.touches[0].pageY
      };
    } else {
      var  c = {
        x: (event.pageX || (event.clientX + document.body.scrollLeft)),
        y: (event.pageY || (event.clientY + document.body.scrollTop))
      };
    }

    return c;
  },

  clone: function(ev,  overrides) {
    //var newev = new Event(ev.type);
    var newev = {};
    for (var i = 0; i < this.cloneattrs.length; i++) {
      var foo = elation.utils.any(overrides[this.cloneattrs[i]], ev[this.cloneattrs[i]]);
      if (foo !== null) {
        newev[this.cloneattrs[i]] = foo;
      }
    }
    return elation.events.fix(newev);
    //return newev;
  },

  handleEvent: function(ev) {
    if (typeof this[ev.type] == 'function') {
      this[ev.type](ev);
    }
  },

  schedule: function(args) {
    /*
    elation.events.schedule({ev: foo, in: 2000});
    elation.events.schedule({ev: foo, every: 200});
    elation.events.schedule({ev: foo, at: Date().getTime() + 3600});
    */
  },

  getEventsByTarget: function(target) {
    var results = [];
    for (var evname in this.events) {
      for (var i = 0; i < this.events[evname].length; i++) {
        var ev = this.events[evname][i];
        if (ev.target === target) {
          results.push(ev);
        }
      }
    }
    return results;
  },
  getEventsByOrigin: function(origin) {
    var results = [];
    for (var evname in this.events) {
      for (var i = 0; i < this.events[evname].length; i++) {
        var ev = this.events[evname][i];
        if (ev.origin === origin) {
          results.push(ev);
        }
      }
    }
    return results;
  },
  hasEventListener: function(target, type) {
    var allevents = elation.events.events[type];
    if (allevents) {
      for (var i = 0; i < allevents.length; i++) {
        var ev = allevents[i];
        if (ev.target === target || ev.origin === target) {
          return true;
        }
      }
    }
    return false;
  }
});
<commit_msg>More efficient event removal for deleted objects<commit_after>// if (typeof require == 'function') var elation = require("utils/elation");
elation.extend("events", {
  events: {},
  cloneattrs: ['type', 'bubbles', 'cancelable', 'view', 'detail', 'screenX', 'screenY', 'clientX', 'clientY', 'ctrlKey', 'shiftKey', 'altKey', 'metaKey', 'button', 'relatedTarget', 'target', 'element', 'data', 'origin', 'timeStamp', 'returnValue', 'cancelBubble', 'keyCode', 'dataTransfer', 'deltaX', 'deltaY', 'deltaZ', 'deltaMode'],

  eventstats: {},

  fire: function(type, data, target, element, fn) {
    var ev = this.getEvent(type, data, target, element, fn);

    //console.log('fire!', ev, type, data, target, element, fn);
    return elation.events.fireEvent(ev);
  },
  getEvent: function(type, data, target, element, fn) {
    var extras = {};

    if (typeof type == 'object') {
      data = elation.utils.any(type.data, data);
      target = type.target || target;
      element = type.element || element;
      fn = type.fn || fn;
      var cloneev = type.event || {};

      for (var i = 0; i < this.cloneattrs.length; i++) {
        var attr = this.cloneattrs[i];
        if (!elation.utils.isNull(type[attr])) extras[attr] = type[attr];
        else if (!elation.utils.isNull(cloneev[attr])) extras[attr] = cloneev[attr];
      }

      if (type.event) {
        var realevent = type.event;
        // If we have a real event, we want our synthesized event to pass stopPropagation and preventDefault calls through
        if (!extras.stopPropagation) {
          extras.stopPropagation = elation.bind(extras, function() {
            realevent.stopPropagation();
            this.cancelBubble = true;
          });
        }
        if (!extras.preventDefault) extras.preventDefault = elation.bind(type.event, type.event.preventDefault);
      }

      if (!elation.utils.isNull(type.clientX)) extras.clientX = type.clientX;
      if (!elation.utils.isNull(type.clientY)) extras.clientY = type.clientY;
      if (!elation.utils.isNull(type.button)) extras.button = type.button;
      if (!elation.utils.isNull(type.keyCode)) extras.keyCode = type.keyCode;

      extras.fn = fn;

      type = type.type;
    }

    extras.data = data;
    extras.target = target;
    extras.element = element;
    extras.fn = fn;
/*
    var ev = {
      type: type,
      element: element,
      fn: fn,
      extras: extras,
      data: data
    };
*/
    return extras;
  },
  fireEvent: function(realevent, element) {
    //console.log('fireEvent:',realevent);
    var type = realevent.type,
        data = realevent.data,
        element = element || realevent.element,
        target = realevent.target,
        fn = realevent.fn;

    if (!type)
      return false;

    if (!this.eventstats[type]) this.eventstats[type] = 0;
    this.eventstats[type]++;

    var list = this.events[type],
        original_events = [],
        events = [],
        event;

    if (!list) {
      this.events[type] = [];
      return [];
    }

    // gather all the events associated with this event type
    // filter by [element] and/or [fn] if present
    for (var i=0; i<list.length; i++) {
      event = list[i];
      if (fn || element) {
        if ((fn && event.origin == fn) || (element && event.target == element) || elation.utils.isNull(event.target)) {
          original_events.push(event);
        } else {
          continue;
        }
      } else {
        original_events.push(event);
      }
    }

    // fire each event
    var extrakeys = Object.keys(realevent);

    for (var i=0; i<original_events.length; i++) {
      var eventObj = original_events[i];

          // break reference to eventObj so original doesn't get overwritten
      var event = elation.events.clone(eventObj, {
            type: type,
            target: target,
            data: data,
            timeStamp: new Date().getTime()
          });
      for (var j = 0; j < extrakeys.length; j++) {
        if (typeof realevent[extrakeys[j]] != 'undefined') {
          event[extrakeys[j]] = realevent[extrakeys[j]];
        }
      }
      if (!event.origin)
        continue;

      var cont = true;

      if (typeof event.origin == 'function') {
        cont = event.origin(event);
      } else if (typeof event.origin.handleEvent != 'undefined') {
        cont = event.origin.handleEvent(event);
      }

      events.push(event);

      if (cont === false || event.cancelBubble || realevent.cancelBubble) {
        // FIXME - I keep finding myself commenting this out, and then wondering why I've commented it out later
        //         Note to self - if you have to change this setting again, at least document why it needs to change!
        event.cancelBubble = true;
        break;
      }
    }

    // return all event objects that were fired
    return events;
  },

  register: function(types, fn, element) {
    var types = types.split(','),
        type;

    for (var i=0; i<types.length; i++) {
      type = types[i];

      if (!this.events[type]) {
        if (fn || element)
          this._register(element, type, fn);
        else
          this.events[type] = [];
      }
    }
  },

  _register: function(element, type, fn, options) {

    var event = {
      type: type,
      target: element,
      origin: fn,
      preventDefault: function() { this.returnValue = false; return; },
      stopPropagation: function() { this.cancelBubble = true; return; },
      returnValue: true,
      cancelBubble: false
    };

    if (!elation.events.events[type])
      elation.events.events[type] = [];

    elation.events.events[type].push(event);
  },
  _unregister: function(element, type, fn) {
    if (elation.events.events[type]) {
      var remaining = [];
      for (var i = 0; i < elation.events.events[type].length; i++) {
        var ev = elation.events.events[type][i];
        if (ev.type == type && ev.target == element && ev.origin == fn) {
          //elation.events.events[type].splice(i--, 1);
        } else {
          remaining.push(ev);
        }
      }
      if (elation.events.events[type].length != remaining.length) {
        elation.events.events[type] = remaining;
      }
    }
  },

  // syntax: add(element || [ elements ], "type1,type2,type3", function || object);
  add: function(elements, types, fn, options) {
    if (!types || !fn || typeof types != "string")
      return;

    var elements = elation.utils.isNull(elements)
          ? [null]
          : !elation.utils.isArray(elements) || elements == window
            ? [ elements ]
            : elements,
        types = types.split(',');

    if (typeof fn == "string") {
      fn = (function(func) { return function(ev) { eval(func); }; })(fn);
    }

    for (var e=0; e<elements.length; e++) {
      var element = elements[e];

      if (typeof element != 'object')
        continue;

      for (var i=0; i<types.length; i++) {
        var type = types[i];

        elation.events._register(element, type, fn);

        if (!element)
          continue;

        if (type.toLowerCase() == 'transitionend') {
          if ('onwebkittransitionend' in window) type = 'webkitTransitionEnd';
          else if ('onotransitionend' in element || navigator.appName == 'Opera') type = 'oTransitionEnd';
        }

        if ("addEventListener" in element) {
          //if (type == 'mousewheel' && elation.browser.type != 'safari')
          //  type = 'DOMMouseScroll';
          if (typeof fn == "object" && fn.handleEvent) {
            element[type+fn] = function(e) {
              fn.handleEvent(e);
            }
            element.addEventListener(type, element[(type + fn)], options);
          } else {
            element.addEventListener(type, fn, options);
          }
        } else if (element.attachEvent) {
          if (typeof fn == "object" && fn.handleEvent) {
            element[type+fn] = function() {
              fn.handleEvent(elation.events.fix(window.event));
            }
          } else {
            element["e" + type + fn] = fn;
            element[type + fn] = function() {
              if (typeof element["e" + type + fn] == 'function')
                element["e" + type + fn](elation.events.fix(window.event));
            }
          }

          element.attachEvent("on" + type, element[type + fn]);
        }
      }
    }

    return this;
  },

  // syntax: remove(element || [ elements ], "type1,type2,type3", reference);
  remove: function(elements, types, fn) {
    //if (!elements || !types || !fn || typeof types != "string")
    //  return;

    //var  elements = (!elation.utils.isNull(elements.nodeName) || elements == window) ? [ elements ] : elements;
    if (!elation.utils.isArray(elements)) {
      elements = [elements];
    }
    var types = types.split(',');

    for (var e=0; e<elements.length; e++) {
      var element = elements[e];

      if (typeof element != 'object')
        continue;

      for (var i=0; i<types.length; i++) {
        var type = types[i];

        elation.events._unregister(element, type, fn);

        if (element) {
          if (element.removeEventListener) {
            if (typeof fn == "object" && fn.handleEvent) {
              element.removeEventListener(type, element[type+fn], false);
              delete element[type + fn];
            } else {
              element.removeEventListener(type, fn, false);
            }
          } else if (element.detachEvent) {
            if (typeof element[type + fn] == "function")
              element.detachEvent("on" + type, element[type + fn]);

            element[type + fn] = null;
            element["e" + type + fn] = null;
          }
        }
      }
    }

    return this;
  },

  fix: function(event) {
    event.preventDefault = function() {
      this.returnValue = false;
    }

    event.stopPropagation = function() {
      this.cancelBubble = true;
    }

    //event.preventDefault = this.preventDefault;
    //event.stopPropagation = this.stopPropagation;

    return event;
  },

  getTarget: function(event) {
    return window.event ? event.srcElement : event.target;
  },

  getRelated: function(event) {
    var reltg;

    if (event.relatedTarget) {
      reltg = event.relatedTarget;
    } else {
      if (event.type == "mouseover")
        reltg = event.fromElement;
      else if (event.type == "mouseout")
        reltg = event.toElement;
      else
        reltg = document;
    }

    return reltg;
  },

  getEventTarget: function(event, parentClassName) {
    var target;

    if (!event)
      var event = window.event;

    if (event.target)
      target = event.target;
    else if (event.srcElement)
      target = event.srcElement;

    if (target.nodeType == 3)
      target = target.parentNode; // Defeat Safari bug

    if (parentClassName) {
      // Make sure we're working with the correct element
      var classUp, classDown;

      if (parentClassName.indexOf(">")) {
        var classes = parentClassName.split(">", 2);
        classDown = classes[0];
        classUp = classes[1];
      } else {
        classDown = parentClassName;
      }

      // First run DOWN the heirarchy to find the base class...
      while (!elation.html.hasclass(target,classDown) && target.parentNode) {
        target = target.parentNode;
      }

      // Now if we've specified a child to attach to, find it!
      if (classUp) {
        var elements;
        elements = elation.find("." + classUp, target);
        if (elements.length > 0) {
          target = elements[0];
        }
      }
    }

    return target;
  },
  isTransition: function(ev, parent) {
    var tg = this.getTarget(ev),
        reltg = this.getRelated(ev);
    return (elation.utils.isin(parent, tg) && !elation.utils.isin(parent, reltg));
  },

  // returns mouse or all finger touch coords
  coords: function(event) {
    if (typeof event.touches != 'undefined' && event.touches.length > 0) {
      var c = {
        x: event.touches[0].pageX,
        y: event.touches[0].pageY
      };
    } else {
      var  c = {
        x: (event.pageX || (event.clientX + document.body.scrollLeft)),
        y: (event.pageY || (event.clientY + document.body.scrollTop))
      };
    }

    return c;
  },

  clone: function(ev,  overrides) {
    //var newev = new Event(ev.type);
    var newev = {};
    for (var i = 0; i < this.cloneattrs.length; i++) {
      var foo = elation.utils.any(overrides[this.cloneattrs[i]], ev[this.cloneattrs[i]]);
      if (foo !== null) {
        newev[this.cloneattrs[i]] = foo;
      }
    }
    return elation.events.fix(newev);
    //return newev;
  },

  handleEvent: function(ev) {
    if (typeof this[ev.type] == 'function') {
      this[ev.type](ev);
    }
  },

  schedule: function(args) {
    /*
    elation.events.schedule({ev: foo, in: 2000});
    elation.events.schedule({ev: foo, every: 200});
    elation.events.schedule({ev: foo, at: Date().getTime() + 3600});
    */
  },

  getEventsByTarget: function(target) {
    var results = [];
    for (var evname in this.events) {
      for (var i = 0; i < this.events[evname].length; i++) {
        var ev = this.events[evname][i];
        if (ev.target === target) {
          results.push(ev);
        }
      }
    }
    return results;
  },
  getEventsByOrigin: function(origin) {
    var results = [];
    for (var evname in this.events) {
      for (var i = 0; i < this.events[evname].length; i++) {
        var ev = this.events[evname][i];
        if (ev.origin === origin) {
          results.push(ev);
        }
      }
    }
    return results;
  },
  getEventsByTargetOrOrigin: function(target) {
    var results = [];
    for (var evname in this.events) {
      for (var i = 0; i < this.events[evname].length; i++) {
        var ev = this.events[evname][i];
        if (ev.target === target || ev.origin === target) {
          results.push(ev);
        }
      }
    }
    return results;
  },
  hasEventListener: function(target, type) {
    var allevents = elation.events.events[type];
    if (allevents) {
      for (var i = 0; i < allevents.length; i++) {
        var ev = allevents[i];
        if (ev.target === target || ev.origin === target) {
          return true;
        }
      }
    }
    return false;
  }
});
<|endoftext|>


<commit_before>/**
 * @author mrdoob / http://mrdoob.com/
 */

'use strict';

THREE.OBJLoader = (function () {

	function OBJLoader( manager ) {
		this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;

		this.materials = null;
		this.group = null;
		this.loadAsArrayBuffer = true;
		this.trimFunction = null;
		this.path = null;

		this.input = null;
		this.debug = false;

		this.reInit( true );
	}

	OBJLoader.prototype.setPath = function ( value ) {
		this.path = value;
	};

	OBJLoader.prototype.setMaterials = function ( materials ) {
		this.materials = materials;
	};

	/**
	 * When this is set the ResponseType of the XHRLoader is set to arraybuffer
	 * and parseArrayBuffer is used.
	 * @param loadAsArrayBuffer
	 */
	OBJLoader.prototype.setLoadAsArrayBuffer = function ( loadAsArrayBuffer ) {
		this.loadAsArrayBuffer = loadAsArrayBuffer;
	};

	OBJLoader.prototype.reInit = function ( loadAsArrayBuffer, path ) {
		this.materials = null;
		this.container = new THREE.Group();
		this.loadAsArrayBuffer = loadAsArrayBuffer;

		// Define trim function to use once
		// Faster to just trim left side of the line. Use if available.
		var trimLeft = function ( line ) { return line.trimLeft(); };
		var trimNormal = function ( line ) { return line.trim(); };
		this.trimFunction = typeof ''.trimLeft === 'function' ?  trimLeft : trimNormal;

		this.setPath( path );
	};

	OBJLoader.prototype.load = function ( url, onLoad, onProgress, onError ) {
		var scope = this;

		var loader = new THREE.XHRLoader( scope.manager );
		loader.setPath( this.path );
		loader.setResponseType( this.loadAsArrayBuffer ? 'arraybuffer' : 'text' );
		loader.load( url, function ( loadedContent ) {

			onLoad( scope.parse( loadedContent ) );

		}, onProgress, onError );
	};

	OBJLoader.prototype.parse = function ( loadedContent ) {

		var objectStore = new InputObjectStore( this.debug, 0 );
		var count = 0;
		var scope = this;

		var objectStoreChangeCallback = function ( nextObjectObjectStore ) {
			objectStore = nextObjectObjectStore;
			if ( scope.debug ) {

				console.log(count);
				count++;

			}
		};
		objectStore.registerObjectStoreChangeCallback( objectStoreChangeCallback );

		if ( this.loadAsArrayBuffer ) {

			console.time( 'ParseAB' );
			var view = new Uint8Array( loadedContent );

			for ( var i = 0, length = view.byteLength; i < length; i++ ) {

				objectStore.processByte( view [ i ] );

			}
			console.timeEnd( 'ParseAB' );

		} else {

			console.time( 'ParseText' );
			for ( var i = 0, length = loadedContent.length; i < length; i++ ) {

				objectStore.processByte( loadedContent[ i ].charCodeAt( 0 ) );

			}
			console.timeEnd( 'ParseText' );

		}
		console.log( 'Line Count: ' + objectStore.lineCount );

		return this.container;
	};

	var InputObjectStore = (function () {

		function InputObjectStore( debug, lineCount ) {

			this.currentInput = null;
			this.comments = new CommentStore( '#' );
			this.vertices = new VertexStore( 'v' );
			this.normals = new VertexStore( 'vn' );
			this.uvs = new VertexStore( 'vt' );
			this.faces = new FaceInput();
			this.groups = new BaseStore( 'g' );
			this.smoothingGroups = new BaseStore( 's' );
			this.useMtls = new BaseStore( 'u' );

			this.afterVertex = false;
			this.lineCount = lineCount;
			this.haveV = false;

			if ( debug ) {
				this.comments.debug = debug;
				this.vertices.debug = debug;
				this.normals.debug = debug;
				this.uvs.debug = debug;
				this.faces.debug = debug;
				this.groups.debug = debug;
				this.smoothingGroups.debug = debug;
				this.useMtls.debug = debug;
			}
 		}
		InputObjectStore.prototype.registerObjectStoreChangeCallback = function ( objectStoreChangeCallback ) {
			this.objectStoreChangeCallback = objectStoreChangeCallback;
		};

		InputObjectStore.prototype.processByte = function ( code ) {

			// detect end of line
			if ( code === 10 || code === 13 ) {

				// if CR exists this.currentInput will be null afterwards
				// LF with CR will then do nothing
				// LF without CR will have this.currentInput != null or null beacause of empty line
				if ( this.currentInput !== null ) {

					this.currentInput.detectedLF();
					this.currentInput = null;

				}

				if ( code === 10 ) {
					this.lineCount ++;
				}

			} else if ( this.currentInput !== null ) {

				this.currentInput.parseObjInput( code );

			// It can be of type 'v', 'vn' and 'vt' in the end
			} else  if ( this.haveV ) {

				if ( code === 110 ) {

					this.afterVertex = true;
					this.currentInput = this.normals;

				} else if ( code === 116 ) {

					this.currentInput = this.uvs;

				} else if ( code === 32 ) {

					// when afterVertex is true a new vertex is added, then this storage has to re-init
					// and provide its data
					if ( this.afterVertex ) {

						var objectStore = new InputObjectStore( false, this.lineCount );
						objectStore.registerObjectStoreChangeCallback( this.objectStoreChangeCallback );
						this.objectStoreChangeCallback( objectStore );

					} else {

						this.currentInput = this.vertices;

					}

				} else {

					console.error( 'No space after v: ' + code + ' line: ' + this.lineCount );
				}

				if ( this.currentInput !== null ) {

					this.currentInput.resetLine();

				}
				this.haveV = false;

			} else {

				switch ( code ) {

					// #
					case 35:
						this.currentInput = this.comments;
						break;

					// v
					case 118:
						// Identify with next character
						this.haveV = true;
						break;

					// f
					case 102:
						this.currentInput = this.faces;
						break;

					// g
					case 103:
						this.currentInput = this.groups;
						break;
					// s
					case 115:
						this.currentInput = this.smoothingGroups;
						break;
					// u
					case 117:
						this.currentInput = this.useMtls;
						break;

					// SPACE at start of line : mark as not usable so far
					case 32:
					default:
						break;
				}

				if ( ! this.haveV && this.currentInput !== null ) {

					this.currentInput.resetLine();

				}
			}
		};

		return InputObjectStore;
	})();

	var BaseStore = (function () {

		function BaseStore( defaultChar ) {
			this.defaultChar = defaultChar;
			this.input = this.defaultChar ? this.defaultChar : '';

			this.buffer = [];
			this.bufferIndex = -1;

			this.debug = false;
		}

		BaseStore.prototype.parseObjInput = function ( code ) {
			this.input += String.fromCharCode( code );
		};

		BaseStore.prototype.verify = function () {
			if ( this.input.length > 0 ) {

				this.buffer.push( this.input );
				this.bufferIndex++;
				this.input = '';

			}
		};

		BaseStore.prototype.detectedLF = function () {
			this.verify();

			if ( this.debug ) {
				console.log( this.buffer[ this.bufferIndex ] );
			}
		};

		BaseStore.prototype.resetLine = function () {
			this.input = this.defaultChar ? this.defaultChar : '';
		};

		return BaseStore;
	})();

	var CommentStore = (function () {

		CommentStore.prototype = Object.create( BaseStore.prototype, {
			constructor: {
				value: CommentStore
			}
		});

		function CommentStore() {
			BaseStore.call( this, '#' );
		}

		return CommentStore;
	})();

	var VertexStore = (function () {

		VertexStore.prototype = Object.create( BaseStore.prototype, {
			constructor: {
				value: VertexStore
			}
		});

		function VertexStore( type ) {
			BaseStore.call( this );
			this.type = type;
		}

		VertexStore.prototype.parseObjInput = function ( code ) {
			// "v   1.0 2.0 3.0" or
			// "vn  1.0 2.0 3.0" or
			// "vt  1.0 2.0 3.0"

			if ( code !== 32) {

				this.input += String.fromCharCode( code );

			} else {

				this.verify();

			}
		};

		VertexStore.prototype.detectedLF = function () {
			this.verify();

			if ( this.debug ) {
				var sub = 2;
				if ( this.type === 'vt' ) {
					sub = 1;
				}
				console.log( this.type + ': ' + this.buffer.slice( this.bufferIndex - sub, this.bufferIndex ) );
			}
		};

		return VertexStore;
	})();

	var FaceInput = (function () {

		FaceInput.prototype = Object.create( BaseStore.prototype, {
			constructor: {
				value: FaceInput
			}
		});

		function FaceInput() {
			BaseStore.call( this );

			// possible types
			// 0: "f vertex/uv/normal	vertex/uv/normal	vertex/uv/normal"
			// 1: "f vertex/uv			vertex/uv			vertex/uv"
			// 2: "f vertex//normal		vertex//normal		vertex//normal"
			// 3: "f vertex				vertex				vertex"
			this.typesPerLine = [];
			this.slashes = [];
			this.slashIndex = 0;
		}

		FaceInput.prototype.parseObjInput = function ( code ) {

			if ( code !== 32 && code !== 47 ) {

				this.input += String.fromCharCode( code );

			} else {

				if ( code === 47 ) {

					this.slashes.push( this.slashIndex );

				}
				this.verify();

			}
			this.slashIndex++;
		};

		FaceInput.prototype.detectedLF = function () {
			this.verify();

			// identify type
			var slashesLength = this.slashes.length;
			var type;
			if ( slashesLength === 0 ) {

				type = 3;

			} else if ( slashesLength === 3 ) {

				type = 1;

			} else if ( slashesLength === 6 ) {

				type = this.slashes[ 1 ] - this.slashes[ 0 ] === 1 ? 2 : 0;

			}

			this.typesPerLine.push( type );

			if ( this.debug ) {
				var sub = 2;
				if ( type === 1 || type === 2 ) {
					sub = 5;
				} else if ( type === 0 ) {
					sub = 8;
				}
				console.log( type + ': ' + this.buffer.slice( this.bufferIndex - sub, this.bufferIndex ) );
			}

		};

		FaceInput.prototype.resetLine = function () {
			this.slashes = [];
			this.slashIndex = 0;
			this.input = '';
		};

		return FaceInput;
	})();

	return OBJLoader;
})();
<commit_msg>Fixed Vertex-, Uv- and FaceInput and improved face storage speed. Added InputObjectStore debug information. This helps error detection during dev.<commit_after>/**
 * @author mrdoob / http://mrdoob.com/
 */

'use strict';

THREE.OBJLoader = (function () {

	function OBJLoader( manager ) {
		this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;

		this.materials = null;
		this.group = null;
		this.loadAsArrayBuffer = true;
		this.trimFunction = null;
		this.path = null;

		this.input = null;
		this.debug = true;

		this.reInit( true );
	}

	OBJLoader.prototype.setPath = function ( value ) {
		this.path = value;
	};

	OBJLoader.prototype.setMaterials = function ( materials ) {
		this.materials = materials;
	};

	/**
	 * When this is set the ResponseType of the XHRLoader is set to arraybuffer
	 * and parseArrayBuffer is used.
	 * @param loadAsArrayBuffer
	 */
	OBJLoader.prototype.setLoadAsArrayBuffer = function ( loadAsArrayBuffer ) {
		this.loadAsArrayBuffer = loadAsArrayBuffer;
	};

	OBJLoader.prototype.reInit = function ( loadAsArrayBuffer, path ) {
		this.materials = null;
		this.container = new THREE.Group();
		this.loadAsArrayBuffer = loadAsArrayBuffer;

		// Define trim function to use once
		// Faster to just trim left side of the line. Use if available.
		var trimLeft = function ( line ) { return line.trimLeft(); };
		var trimNormal = function ( line ) { return line.trim(); };
		this.trimFunction = typeof ''.trimLeft === 'function' ?  trimLeft : trimNormal;

		this.setPath( path );
	};

	OBJLoader.prototype.load = function ( url, onLoad, onProgress, onError ) {
		var scope = this;

		var loader = new THREE.XHRLoader( scope.manager );
		loader.setPath( this.path );
		loader.setResponseType( this.loadAsArrayBuffer ? 'arraybuffer' : 'text' );
		loader.load( url, function ( loadedContent ) {

			onLoad( scope.parse( loadedContent ) );

		}, onProgress, onError );
	};

	OBJLoader.prototype.parse = function ( loadedContent ) {

		var objectStore = new InputObjectStore( false, 0 );
		var count = 0;
		var scope = this;

		var parseResults = [];
		var singleResultSet;

		var objectStoreChangeCallback = function ( nextObjectObjectStore ) {

			if ( scope.debug ) {

				if ( count < 10 ) {

					var overallFaceTypesCount = [ 0, 0, 0, 0 ];
					for ( var i = 0, type, typesLength = objectStore.faces.typesPerLine.length; i < typesLength; i ++ ) {
						type = objectStore.faces.typesPerLine[ i ];
						overallFaceTypesCount[ type ] = overallFaceTypesCount[ type ] + 1;
					}
					var overallFacesAttr = [
						overallFaceTypesCount[ 0 ] * 9,
						overallFaceTypesCount[ 1 ] * 6,
						overallFaceTypesCount[ 2 ] * 6,
						overallFaceTypesCount[ 3 ] * 3
					];

					singleResultSet = {
						index: count,
						name: objectStore.groups.buffer.length === 1 ? objectStore.groups.buffer[ 0 ] : 'noname',
						vertexCount: objectStore.vertices.buffer.length / 3,
						normalCount: objectStore.normals.buffer.length / 3,
						uvCount: objectStore.uvs.buffer.length / 2,
						overallFaceTypesCount: overallFaceTypesCount,
						overallFacesAttr: overallFacesAttr,
						groupCount: objectStore.groups.buffer.length,
						smoothingGroupCount: objectStore.smoothingGroups.buffer.length,
						useMtlCount: objectStore.useMtls.buffer.length,
						commentCount: objectStore.comments.buffer.length
					};
					parseResults.push( singleResultSet );

				}
				count ++;

			}

			objectStore = nextObjectObjectStore;
		};
		objectStore.registerObjectStoreChangeCallback( objectStoreChangeCallback );

		console.time( 'Parse' );
		if ( this.loadAsArrayBuffer ) {

			var view = new Uint8Array( loadedContent );
			for ( var i = 0, length = view.byteLength; i < length; i++ ) {

				objectStore.processByte( view [ i ] );

			}

		} else {

			for ( var i = 0, length = loadedContent.length; i < length; i++ ) {

				objectStore.processByte( loadedContent[ i ].charCodeAt( 0 ) );

			}

		}
		console.timeEnd( 'Parse' );

		if ( scope.debug ) {

			for ( var i = 0, length = parseResults.length; i < length; i ++ ) {

				singleResultSet = parseResults[ i ];

				console.log( 'Object number: ' + singleResultSet.index );
				console.log( 'Object name: ' + singleResultSet.name );
				console.log( 'Vertex count: ' + singleResultSet.vertexCount );
				console.log( 'Normal count: ' + singleResultSet.normalCount );
				console.log( 'UV count: ' + singleResultSet.uvCount );
				console.log( 'Faces Type counts: ' + singleResultSet.overallFaceTypesCount );
				console.log( 'Faces attr count per type: ' + singleResultSet.overallFacesAttr );
				console.log( 'Group count: ' + singleResultSet.groupCount );
				console.log( 'Smoothing group count: ' + singleResultSet.smoothingGroupCount );
				console.log( 'Usemtl count: ' + singleResultSet.useMtlCount );
				console.log( 'Comments count: ' + singleResultSet.commentCount );

			}
		}

		return this.container;
	};

	var InputObjectStore = (function () {

		function InputObjectStore( debug, lineCount ) {

			this.currentInput = null;
			this.comments = new CommentStore( '#' );
			this.vertices = new VertexStore( 'v' );
			this.normals = new VertexStore( 'vn' );
			this.uvs = new UvsStore( 'vt' );
			this.faces = new FaceInput();
			this.groups = new CommentStore( 'g' );
			this.smoothingGroups = new CommentStore( 's' );
			this.useMtls = new CommentStore( 'u' );

			this.afterVertex = false;
			this.lineCount = lineCount;
			this.haveV = false;

			if ( debug ) {
				this.comments.debug = debug;
				this.vertices.debug = debug;
				this.normals.debug = debug;
				this.uvs.debug = debug;
				this.faces.debug = debug;
				this.groups.debug = debug;
				this.smoothingGroups.debug = debug;
				this.useMtls.debug = debug;
			}
 		}
		InputObjectStore.prototype.registerObjectStoreChangeCallback = function ( objectStoreChangeCallback ) {
			this.objectStoreChangeCallback = objectStoreChangeCallback;
		};

		InputObjectStore.prototype.processByte = function ( code ) {

			// detect end of line
			if ( code === 10 || code === 13 ) {

				// if CR exists this.currentInput will be null afterwards
				// LF with CR will then do nothing
				// LF without CR will have this.currentInput != null or null beacause of empty line
				if ( this.currentInput !== null ) {

					this.currentInput.detectedLF();
					this.currentInput = null;

				}

				if ( code === 10 ) {
					this.lineCount ++;
				}

			} else if ( this.currentInput !== null ) {

				this.currentInput.parseObjInput( code );

			// It can be of type 'v', 'vn' and 'vt' in the end
			} else  if ( this.haveV ) {

				if ( code === 110 ) {

					this.afterVertex = true;
					this.currentInput = this.normals;

				} else if ( code === 116 ) {

					this.currentInput = this.uvs;

				} else if ( code === 32 ) {

					// when afterVertex is true a new vertex is added, then this storage has to re-init
					// and provide its data
					if ( this.afterVertex ) {

						var objectStore = new InputObjectStore( false, this.lineCount );
						objectStore.registerObjectStoreChangeCallback( this.objectStoreChangeCallback );
						this.objectStoreChangeCallback( objectStore );

					} else {

						this.currentInput = this.vertices;

					}

				} else {

					console.error( 'No space after v: ' + code + ' line: ' + this.lineCount );
				}

				if ( this.currentInput !== null ) {

					this.currentInput.resetLine();

				}
				this.haveV = false;

			} else {

				switch ( code ) {

					// #
					case 35:
						this.currentInput = this.comments;
						break;

					// v
					case 118:
						// Identify with next character
						this.haveV = true;
						break;

					// f
					case 102:
						this.currentInput = this.faces;
						break;

					// g
					case 103:
						this.currentInput = this.groups;
						break;
					// s
					case 115:
						this.currentInput = this.smoothingGroups;
						break;
					// u
					case 117:
						this.currentInput = this.useMtls;
						break;

					// SPACE at start of line : mark as not usable so far
					case 32:
					default:
						break;
				}

				if ( ! this.haveV && this.currentInput !== null ) {

					this.currentInput.resetLine();

				}
			}
		};

		return InputObjectStore;
	})();

	var BaseStore = (function () {

		function BaseStore() {
			this.input = '';
			this.buffer = [];
			this.bufferIndex = -1;

			this.debug = false;
		}

		BaseStore.prototype.parseObjInput = function ( code ) {
			this.input += String.fromCharCode( code );
		};

		BaseStore.prototype.verify = function () {
			if ( this.input.length > 0 ) {

				this.buffer.push( this.input );
				this.bufferIndex++;
				this.input = '';

			}
		};

		BaseStore.prototype.detectedLF = function () {
			this.verify();

			if ( this.debug ) {
				console.log( this.buffer[ this.bufferIndex ] );
			}
		};

		BaseStore.prototype.resetLine = function () {
			this.input = '';
		};

		return BaseStore;
	})();

	var CommentStore = (function () {

		CommentStore.prototype = Object.create( BaseStore.prototype, {
			constructor: {
				value: CommentStore
			}
		});

		function CommentStore( defaultChar ) {
			BaseStore.call( this );

			this.defaultChar = defaultChar;
			this.input = this.defaultChar ? this.defaultChar : '';
		}

		CommentStore.prototype.resetLine = function () {
			this.input = this.defaultChar ? this.defaultChar : '';
		};

		return CommentStore;
	})();

	var VertexStore = (function () {

		VertexStore.prototype = Object.create( BaseStore.prototype, {
			constructor: {
				value: VertexStore
			}
		});

		function VertexStore( type ) {
			BaseStore.call( this );
			this.type = type;
		}

		VertexStore.prototype.parseObjInput = function ( code ) {
			// "v   1.0 2.0 3.0" or
			// "vn  1.0 2.0 3.0" or

			if ( code !== 32) {

				this.input += String.fromCharCode( code );

			} else {

				this.verify();

			}
		};

		VertexStore.prototype.detectedLF = function () {
			this.verify();

			if ( this.debug ) {
				console.log( this.type + ': ' + this.buffer.slice( this.bufferIndex - 2, this.bufferIndex ) );
			}

		};

		VertexStore.prototype.resetLine = function () {
			this.input = '';
		};

		return VertexStore;
	})();

	var UvsStore = (function () {

		UvsStore.prototype = Object.create( VertexStore.prototype, {
			constructor: {
				value: UvsStore
			}
		});

		function UvsStore( type ) {
			VertexStore.call( this, type );
			this.retrievedFloatCount = 0;
		}

		UvsStore.prototype.parseObjInput = function ( code ) {
			// "vt  1.0 2.0 0.0" -> do not use "z"

			if ( this.retrievedFloatCount < 2 ) {

				if ( code !== 32 ) {

					this.input += String.fromCharCode( code );

				} else {

					this.verify();

				}
			}
		};

		UvsStore.prototype.verify = function () {
			if ( this.input.length > 0 ) {

				this.buffer.push( this.input );
				this.bufferIndex++;
				this.input = '';
				this.retrievedFloatCount++;
			}
		};

		UvsStore.prototype.detectedLF = function () {
			this.verify();

			if ( this.debug ) {
				console.log( this.type + ': ' + this.buffer.slice( this.bufferIndex - 1, this.bufferIndex ) );
			}
		};

		UvsStore.prototype.resetLine = function () {
			this.input = '';
			this.retrievedFloatCount = 0;
		};

		return UvsStore;
	})();

	var FaceInput = (function () {

		FaceInput.prototype = Object.create( BaseStore.prototype, {
			constructor: {
				value: FaceInput
			}
		});

		function FaceInput() {
			BaseStore.call( this );

			// possible types
			// 0: "f vertex/uv/normal	vertex/uv/normal	vertex/uv/normal"
			// 1: "f vertex/uv			vertex/uv			vertex/uv"
			// 2: "f vertex//normal		vertex//normal		vertex//normal"
			// 3: "f vertex				vertex				vertex"

			this.lineIndex = 0;
			this.typesPerLine = [];
			this.slashCount = 0;
			this.shortestDistance = 1000;
			this.slashLast = 0;
		}

		FaceInput.prototype.parseObjInput = function ( code ) {

			if ( code !== 32 && code !== 47 ) {

				this.input += String.fromCharCode( code );

			} else {

				if ( code === 47 ) {

					this.slashCount++;
					var distance = this.lineIndex - this.slashLast;
					if ( distance < this.shortestDistance ) {
						this.shortestDistance = distance;
					}
					this.slashLast = this.lineIndex;

				}
				this.verify();

			}
			this.lineIndex++;
		};

		FaceInput.prototype.detectedLF = function () {
			this.verify();

			// identify type
			var type = 3;
			if ( this.slashCount === 3 ) {

				type = 1;

			} else if ( this.slashCount === 6 ) {

				type = this.shortestDistance === 1 ? 2 : 0;

			}

			this.typesPerLine.push( type );

			if ( this.debug ) {
				var sub = 2;
				if ( type === 1 || type === 2 ) {
					sub = 5;
				} else if ( type === 0 ) {
					sub = 8;
				}
				console.log( type + ': ' + this.buffer.slice( this.bufferIndex - sub, this.bufferIndex ) );
			}

		};

		FaceInput.prototype.resetLine = function () {
			this.lineIndex = 0;
			this.slashCount = 0;
			this.shortestDistance = 1000;
			this.slashLast = 0;
			this.input = '';
		};

		return FaceInput;
	})();

	return OBJLoader;
})();
<|endoftext|>


<commit_before>// ==UserScript==
// @name         Scratch-Forum-UserScript
// @namespace    http://github.com/Smrman/Scratch-Forum-UserScript
// @version      2.0
// @author       Smrman
// @description  Make Scratch great again!
// @homepageURL  http://github.com/Smrman/Scratch-Forum-UserScript
// @iconURL      https://raw.githubusercontent.com/Smrman/Scratch-Forum-UserScript/master/script/assets/favicon_bleu_128.png
// @icon64URL    https://raw.githubusercontent.com/Smrman/Scratch-Forum-UserScript/master/script/assets/favicon_bleu_64.png
// @updateURL    https://github.com/Smrman/Scratch-Forum-UserScript/raw/master/script/js/Scratch-Forum-UserScript.user.js
// @downloadURL  https://github.com/Smrman/Scratch-Forum-UserScript/raw/master/script/js/Scratch-Forum-UserScript.user.js
// @supportURL   https://smrman.github.io/Scratch-Forum-UserScript/html/index.html
// @include      https://scratch.mit.edu/*
// @match        https://scratch.mit.edu/*
// @exclude
// @require      https://raw.githubusercontent.com/Smrman/Scratch-Forum-UserScript/master/script/js/Scratch-Forum-UserScript.js
// @require      https://raw.githubusercontent.com/Smrman/Scratch-Forum-UserScript/master/script/js/discuss.js
// @require      https://raw.githubusercontent.com/Smrman/Scratch-Forum-UserScript/master/script/js/search.js
// @resource
// @connect
// @run-at       document-idle
// @grant        none
// @unwrap
// ==/UserScript==
<commit_msg>space removed<commit_after>// ==UserScript==
// @name         Scratch-Forum-UserScript
// @namespace    http://github.com/Smrman/Scratch-Forum-UserScript
// @version      2.0
// @author       Smrman
// @description  Make Scratch great again!
// @homepageURL  http://github.com/Smrman/Scratch-Forum-UserScript
// @iconURL      https://raw.githubusercontent.com/Smrman/Scratch-Forum-UserScript/master/script/assets/favicon_bleu_128.png
// @icon64URL    https://raw.githubusercontent.com/Smrman/Scratch-Forum-UserScript/master/script/assets/favicon_bleu_64.png
// @updateURL    https://github.com/Smrman/Scratch-Forum-UserScript/raw/master/script/js/Scratch-Forum-UserScript.user.js
// @downloadURL  https://github.com/Smrman/Scratch-Forum-UserScript/raw/master/script/js/Scratch-Forum-UserScript.user.js
// @supportURL   https://smrman.github.io/Scratch-Forum-UserScript/html/index.html
// @include      https://scratch.mit.edu/*
// @match        https://scratch.mit.edu/*
// @exclude
// @require      https://raw.githubusercontent.com/Smrman/Scratch-Forum-UserScript/master/script/js/Scratch-Forum-UserScript.js
// @require      https://raw.githubusercontent.com/Smrman/Scratch-Forum-UserScript/master/script/js/discuss.js
// @require      https://raw.githubusercontent.com/Smrman/Scratch-Forum-UserScript/master/script/js/search.js
// @resource
// @connect
// @run-at       document-idle
// @grant        none
// @unwrap
// ==/UserScript==
<|endoftext|>


<commit_before>'use strict'

const path = require('path')
const CONFIG = require('../config')

module.exports = function (filePath) {
  return !EXCLUDED_PATHS_REGEXP.test(filePath) || INCLUDED_PATHS_REGEXP.test(filePath)
}

const EXCLUDE_REGEXPS_SOURCES = [
  escapeRegExp('.DS_Store'),
  escapeRegExp('.jshintrc'),
  escapeRegExp('.npmignore'),
  escapeRegExp('.pairs'),
  escapeRegExp('.travis.yml'),
  escapeRegExp('appveyor.yml'),
  escapeRegExp('circle.yml'),
  escapeRegExp('.idea'),
  escapeRegExp('.editorconfig'),
  escapeRegExp('.lint'),
  escapeRegExp('.lintignore'),
  escapeRegExp('.eslintrc'),
  escapeRegExp('.jshintignore'),
  escapeRegExp('coffeelint.json'),
  escapeRegExp('.coffeelintignore'),
  escapeRegExp('.gitattributes'),
  escapeRegExp('.gitkeep'),
  escapeRegExp(path.join('git-utils', 'deps')),
  escapeRegExp(path.join('oniguruma', 'deps')),
  escapeRegExp(path.join('less', 'dist')),
  escapeRegExp(path.join('npm', 'doc')),
  escapeRegExp(path.join('npm', 'html')),
  escapeRegExp(path.join('npm', 'man')),
  escapeRegExp(path.join('npm', 'node_modules', '.bin', 'beep')),
  escapeRegExp(path.join('npm', 'node_modules', '.bin', 'clear')),
  escapeRegExp(path.join('npm', 'node_modules', '.bin', 'starwars')),
  escapeRegExp(path.join('pegjs', 'examples')),
  escapeRegExp(path.join('get-parameter-names', 'node_modules', 'testla')),
  escapeRegExp(path.join('get-parameter-names', 'node_modules', '.bin', 'testla')),
  escapeRegExp(path.join('jasmine-reporters', 'ext')),
  escapeRegExp(path.join('node_modules', 'nan')),
  escapeRegExp(path.join('node_modules', 'native-mate')),
  escapeRegExp(path.join('build', 'binding.Makefile')),
  escapeRegExp(path.join('build', 'config.gypi')),
  escapeRegExp(path.join('build', 'gyp-mac-tool')),
  escapeRegExp(path.join('build', 'Makefile')),
  escapeRegExp(path.join('build', 'Release', 'obj.target')),
  escapeRegExp(path.join('build', 'Release', 'obj')),
  escapeRegExp(path.join('build', 'Release', '.deps')),
  escapeRegExp(path.join('vendor', 'apm')),

  // These are only required in dev-mode, when pegjs grammars aren't precompiled
  escapeRegExp(path.join('node_modules', 'loophole')),
  escapeRegExp(path.join('node_modules', 'pegjs')),
  escapeRegExp(path.join('node_modules', '.bin', 'pegjs')),
  escapeRegExp(path.join('node_modules', 'spellchecker', 'vendor', 'hunspell') + path.sep) + '.*',
  escapeRegExp(path.join('build', 'Release') + path.sep) + '.*\\.pdb',

  // Ignore *.cc and *.h files from native modules
  escapeRegExp(path.sep) + '.+\\.(cc|h)$',

  // Ignore build files
  escapeRegExp(path.sep) + 'binding\\.gyp$',
  escapeRegExp(path.sep) + '.+\\.target.mk$',
  escapeRegExp(path.sep) + 'linker\\.lock$',
  escapeRegExp(path.join('build', 'Release') + path.sep) + '.+\\.node\\.dSYM',

  // Ignore test and example folders
  'node_modules' + escapeRegExp(path.sep) + '.*' + escapeRegExp(path.sep) + '_*te?sts?_*' + escapeRegExp(path.sep),
  'node_modules' + escapeRegExp(path.sep) + '.*' + escapeRegExp(path.sep) + 'examples?' + escapeRegExp(path.sep)
]

// Ignore spec directories in all bundled packages
for (let packageName in CONFIG.appMetadata.packageDependencies) {
  EXCLUDE_REGEXPS_SOURCES.push('^' + escapeRegExp(path.join(CONFIG.repositoryRootPath, 'node_modules', packageName, 'spec')))
}

// Ignore Hunspell dictionaries only on macOS.
if (process.platform === 'darwin') {
  EXCLUDE_REGEXPS_SOURCES.push(escapeRegExp(path.join('spellchecker', 'vendor', 'hunspell_dictionaries')))
}

const EXCLUDED_PATHS_REGEXP = new RegExp(
  EXCLUDE_REGEXPS_SOURCES.map(path => `(${path})`).join('|')
)

const INCLUDED_PATHS_REGEXP = new RegExp(
  escapeRegExp(path.join('node_modules', 'node-gyp', 'src', 'win_delay_load_hook.cc'))
)

function escapeRegExp (string) {
  return string.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&')
}
<commit_msg>Exclude lots of native node module cruft<commit_after>'use strict'

const path = require('path')
const CONFIG = require('../config')

module.exports = function (filePath) {
  return !EXCLUDED_PATHS_REGEXP.test(filePath) || INCLUDED_PATHS_REGEXP.test(filePath)
}

const EXCLUDE_REGEXPS_SOURCES = [
  escapeRegExp('.DS_Store'),
  escapeRegExp('.jshintrc'),
  escapeRegExp('.npmignore'),
  escapeRegExp('.pairs'),
  escapeRegExp('.travis.yml'),
  escapeRegExp('appveyor.yml'),
  escapeRegExp('circle.yml'),
  escapeRegExp('.idea'),
  escapeRegExp('.editorconfig'),
  escapeRegExp('.lint'),
  escapeRegExp('.lintignore'),
  escapeRegExp('.eslintrc'),
  escapeRegExp('.jshintignore'),
  escapeRegExp('coffeelint.json'),
  escapeRegExp('.coffeelintignore'),
  escapeRegExp('.gitattributes'),
  escapeRegExp('.gitkeep'),
  escapeRegExp(path.join('git-utils', 'deps')),
  escapeRegExp(path.join('oniguruma', 'deps')),
  escapeRegExp(path.join('less', 'dist')),
  escapeRegExp(path.join('npm', 'doc')),
  escapeRegExp(path.join('npm', 'html')),
  escapeRegExp(path.join('npm', 'man')),
  escapeRegExp(path.join('npm', 'node_modules', '.bin', 'beep')),
  escapeRegExp(path.join('npm', 'node_modules', '.bin', 'clear')),
  escapeRegExp(path.join('npm', 'node_modules', '.bin', 'starwars')),
  escapeRegExp(path.join('pegjs', 'examples')),
  escapeRegExp(path.join('get-parameter-names', 'node_modules', 'testla')),
  escapeRegExp(path.join('get-parameter-names', 'node_modules', '.bin', 'testla')),
  escapeRegExp(path.join('jasmine-reporters', 'ext')),
  escapeRegExp(path.join('node_modules', 'nan')),
  escapeRegExp(path.join('node_modules', 'native-mate')),
  escapeRegExp(path.join('build', 'binding.Makefile')),
  escapeRegExp(path.join('build', 'config.gypi')),
  escapeRegExp(path.join('build', 'gyp-mac-tool')),
  escapeRegExp(path.join('build', 'Makefile')),
  escapeRegExp(path.join('build', 'Release', 'obj.target')),
  escapeRegExp(path.join('build', 'Release', 'obj')),
  escapeRegExp(path.join('build', 'Release', '.deps')),
  escapeRegExp(path.join('vendor', 'apm')),

  // These are only required in dev-mode, when pegjs grammars aren't precompiled
  escapeRegExp(path.join('node_modules', 'loophole')),
  escapeRegExp(path.join('node_modules', 'pegjs')),
  escapeRegExp(path.join('node_modules', '.bin', 'pegjs')),
  escapeRegExp(path.join('node_modules', 'spellchecker', 'vendor', 'hunspell') + path.sep) + '.*',

  // Ignore *.cc and *.h files from native modules
  escapeRegExp(path.sep) + '.+\\.(cc|h)$',

  // Ignore build files
  escapeRegExp(path.sep) + 'binding\\.gyp$',
  escapeRegExp(path.sep) + '.+\\.target.mk$',
  escapeRegExp(path.sep) + 'linker\\.lock$',
  escapeRegExp(path.join('build', 'Release') + path.sep) + '.+\\.node\\.dSYM',
  escapeRegExp(path.join('build', 'Release') + path.sep) + '.*\\.(pdb|lib|exp|map|ipdb|iobj)',

  // Ignore test and example folders
  'node_modules' + escapeRegExp(path.sep) + '.*' + escapeRegExp(path.sep) + '_*te?sts?_*' + escapeRegExp(path.sep),
  'node_modules' + escapeRegExp(path.sep) + '.*' + escapeRegExp(path.sep) + 'examples?' + escapeRegExp(path.sep)
]

// Ignore spec directories in all bundled packages
for (let packageName in CONFIG.appMetadata.packageDependencies) {
  EXCLUDE_REGEXPS_SOURCES.push('^' + escapeRegExp(path.join(CONFIG.repositoryRootPath, 'node_modules', packageName, 'spec')))
}

// Ignore Hunspell dictionaries only on macOS.
if (process.platform === 'darwin') {
  EXCLUDE_REGEXPS_SOURCES.push(escapeRegExp(path.join('spellchecker', 'vendor', 'hunspell_dictionaries')))
}

const EXCLUDED_PATHS_REGEXP = new RegExp(
  EXCLUDE_REGEXPS_SOURCES.map(path => `(${path})`).join('|')
)

const INCLUDED_PATHS_REGEXP = new RegExp(
  escapeRegExp(path.join('node_modules', 'node-gyp', 'src', 'win_delay_load_hook.cc'))
)

function escapeRegExp (string) {
  return string.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&')
}
<|endoftext|>


<commit_before>define([
  "underscore",
  "backend/draw/manage/ObjectIndex",
  "backend/generic/EventsMixin"
], function(
  _,
  ObjectIndex,
  EventsMixin
) {

  var states = null;
  var currentState = 0;

  var Selection = function() {
    selection = { objects: [], examples: [] };
    selection.checkpoint = [];
    return selection;
  };

  var clone = function(selection) {
    var copy = {};
    copy.objects = selection.objects.slice();
    copy.examples = selection.examples.slice();
    copy.checkpoint = selection.checkpoint.slice();
    return copy;
  };

  var toolSwitch = false;
  var SelectIndex = {

    create: function() {
      states = [Selection()];
      currentState = 0;

      var index = this;
      this.addListener("toolSwitch", function(tool) {
        console.log("heard tool switch at sel index");
        toolSwitch = true;
        /*
        this.dispatch("clearSelection");
        var selection = clone(states[currentState]);
        // basically make sure that a "finish" hasn't been executed already
        if (states[currentState].objects.length !== 0) {
          selection.checkpoint = selection.objects.slice();
          selection.objects = [];
          selection.examples = [];
          states = [selection];
          currentState = 0;
        }
        this.dispatch("drawSelection", selection);
        */
      });

      /*
      this.addListener("finish", function(update) {
        console.log("heard finish selection at sel index");
        this.dispatch("clearSelection");
        var selection = clone(states[currentState]);
        if (update == "none") {
          selection.checkpoint = selection.objects.slice();
        } else if (update == "remove") {
          selection.checkpoint = _.difference(selection.checkpoint, selection.objects);
        } else if (update == "union") {
          selection.checkpoint = _.union(selection.objects, selection.checkpoint);
        } else if (update == "intersect") {
          selection.checkpoint = _.intersection(selection.objects, selection.checkpoint);
        }
        selection.objects = [];
        selection.examples = [];
        states = [selection];
        currentState = 0;
        this.dispatch("drawSelection", selection);
      });
      */
    },

    reset: function(options) {
      states = [Selection()];
      currentState = 0;
    },

    undo: function() {
      console.log("select undo", currentState);
      if (currentState == 0) { return; }
      this.dispatch("clearSelection");
      currentState = currentState - 1;
      var selection = states[currentState];
      this.dispatch("drawSelection", selection);
    },

    redo: function() {
      console.log("select redo", currentState);
      if (currentState == states.length-1) { return; }
      this.dispatch("clearSelection");
      currentState = currentState + 1;
      var selection = states[currentState];
      this.dispatch("drawSelection", selection);
    },

    get: function() {
      var selection = states[currentState];
      return clone(selection);
    },

    // only for use with inferred selections
    update: function(objects, example) {
      console.log("updating selection", objects, example);
      console.log("current selection state", states[currentState]);
      this.dispatch("clearSelection");
      var selection = clone(states[currentState]);
      if (toolSwitch) {
        selection.examples = [];
        toolSwitch = false;
      }
      selection.objects = objects;
      selection.examples.push(example);
      this.dispatch("drawSelection", selection);
      states = states.slice(0, currentState+1);
      states.push(selection);
      currentState = currentState + 1;
    },

    change: function(objects, polarity) {
      console.log("changing selection", objects, polarity);
      console.log("current selection state", states[currentState]);
      this.dispatch("clearSelection");
      var selection = clone(states[currentState]);
      if (toolSwitch) {
        selection.examples = [];
        toolSwitch = false;
      }
      /*
      if (selection.examples.length > 0) { selection.examples = []; }
      if (polarity == 1) {
        selection.objects.push.apply(selection.objects, objects);
      } else {
        selection.objects = _.difference(selection.objects, objects);
      };
      */
      var intersect = _.intersection(selection.objects, objects);
      if (intersect.length == 0) {
        selection.objects.push.apply(selection.objects, objects);
      } else {
        selection.objects = _.difference(selection.objects, objects);
      }
      this.dispatch("drawSelection", selection);
      states = states.slice(0, currentState+1);
      states.push(selection);
      currentState = currentState + 1;
    }
  };
  _.extend(SelectIndex, EventsMixin);

  return SelectIndex;
});
<commit_msg>added missing file from log support commit<commit_after>define([
  "underscore",
  "backend/draw/manage/ObjectIndex",
  "backend/generic/EventsMixin",
  "backend/generic/Communicator"
], function(
  _,
  ObjectIndex,
  EventsMixin,
  Communicator
) {

  var states = null;
  var currentState = 0;

  var Selection = function() {
    selection = { objects: [], examples: [] };
    selection.checkpoint = [];
    return selection;
  };

  var clone = function(selection) {
    var copy = {};
    copy.objects = selection.objects.slice();
    copy.examples = selection.examples.slice();
    copy.checkpoint = selection.checkpoint.slice();
    return copy;
  };

  var currTool = "search";
  var toolSwitch = false;
  var SelectIndex = {

    create: function() {
      states = [Selection()];
      currentState = 0;

      var index = this;
      this.addListener("toolSwitch", function(tool) {
        console.log("heard tool switch at sel index");
        toolSwitch = true;
        currTool = tool;
      });
    },

    reset: function(options) {
      states = [Selection()];
      currentState = 0;
    },

    undo: function() {
      console.log("select undo", currentState);
      if (currentState == 0) { return; }
      this.dispatch("clearSelection");
      currentState = currentState - 1;
      var selection = states[currentState];
      this.dispatch("drawSelection", selection);
      // LOGGING
      Communicator.post("image", "log", {
        "operation": "undo",
        "objects": selection.objects,
        "tool": currTool
      });
    },

    redo: function() {
      console.log("select redo", currentState);
      if (currentState == states.length-1) { return; }
      this.dispatch("clearSelection");
      currentState = currentState + 1;
      var selection = states[currentState];
      this.dispatch("drawSelection", selection);
      Communicator.post("image", "log", {
        "operation": "redo",
        "objects": selection.objects,
        "tool": currTool
      });
    },

    get: function() {
      var selection = states[currentState];
      return clone(selection);
    },

    // only for use with inferred selections
    update: function(objects, example) {
      console.log("updating selection", objects, example);
      console.log("current selection state", states[currentState]);
      this.dispatch("clearSelection");
      var selection = clone(states[currentState]);
      if (toolSwitch) {
        selection.examples = [];
        toolSwitch = false;
      }
      selection.objects = objects;
      selection.examples.push(example);
      this.dispatch("drawSelection", selection);
      states = states.slice(0, currentState+1);
      states.push(selection);
      currentState = currentState + 1;
      // LOGGING
      Communicator.post("image", "log", {
        "operation": "change",
        "objects": selection.objects,
        "tool": currTool
      });
    },

    change: function(objects, polarity) {
      console.log("changing selection", objects, polarity);
      console.log("current selection state", states[currentState]);
      this.dispatch("clearSelection");
      var selection = clone(states[currentState]);
      if (toolSwitch) {
        selection.examples = [];
        toolSwitch = false;
      }
      var intersect = _.intersection(selection.objects, objects);
      if (intersect.length == 0) {
        selection.objects.push.apply(selection.objects, objects);
      } else {
        selection.objects = _.difference(selection.objects, objects);
      }
      this.dispatch("drawSelection", selection);
      states = states.slice(0, currentState+1);
      states.push(selection);
      currentState = currentState + 1;
      // LOGGING
      Communicator.post("image", "log", {
        "operation": "change",
        "objects": selection.objects,
        "tool": currTool
      });
    }
  };
  _.extend(SelectIndex, EventsMixin);

  return SelectIndex;
});
<|endoftext|>


<commit_before>/*
 Copyright 2012 - $Date $ by PeopleWare n.v.

 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.
 */

define(["dojo/_base/declare", "./PersistentObject", "ppwcode-util-oddsAndEnds/js", "dojo/date/stamp", "module"],
    function(declare, PersistentObject, js, dateStamp, module) {

      function stringToDate(candidate) {
        //noinspection FallthroughInSwitchStatementJS
        switch (js.typeOf(candidate)) {
          case "undefined":
          case "null":
            return null;
          case "date":
            return candidate;
          case "string":
            return dateStamp.fromISOString(candidate);
          default:
            throw "ERROR: cannot convert to date: " + candidate;
        }
      }

      function compareDate(d1, d2) {
        // summary:
        //   We use < 1000 (1s) instead of < 0, because the server stores dates only to the second.
        //   With the current server implementation, we see that if we retrieve quickly after an
        //   update or create, the retrieve lastModified at is later than the one in the response
        //   of the action. The reason is, that the data in the action response comes from RAM,
        //   and has values up to the nanosecond or so (23/6/2012 15:45:32.424242242), while the
        //   data in the retrieve comes from the DB, which only stores up to the second
        //   (23/6/2012 15:45:32). So, it seems that retrieve-date is earlier than the action
        //   response date, which is impossible. By giving our comparison a 1 second leeway,
        //   this is resolved.

        if (d1 === d2) {
          return 0;
        }
        if (!d1) {
          return -1;
        }
        if (!d2) {
          return +1;
        }
        var delta = d1.getTime() - d2.getTime();
        if (delta < -1000) {
          return -1;
        }
        if (delta > +1000) {
          return +1;
        }
        return 0;
      }

      // MUDO There is a much worse problem: a RAM-created server Date is in the local time of the server
      //      While the time in the DB has no timezone. A RAM-created server Date that is sent over JSON
      //      contains timezone information. A date that is retrieved from the DB does not contain
      //      timezone in RAM, and thus does also not contain timezone information when it is sent over
      //      JSON.
      //      When JavaScript parses a string with timezone information, it takes it into account.
      //      When JavaScript parses a string without timezone information, it assumes the local
      //      timezone.
      //      When server and client are in a different timezone, when the JSON contains timezone information,
      //      the string is interpreted in the timezone of the server. When the JSON does not contain
      //      timezone information, the string is interpreted in the timezone of the client.
      //      For webapplications, this in unacceptable.
      // MUDO for now we work around this by NOT SENDING THE DATA TO THE SERVER IN THE FIRST PLACE
      //      that was another workaround; the effect of this is that we will not see this data after update
      // MUDO but it is obviously wrong

      // MUDO Attempted workaround for PICTOPERFECT-484: send the data to the server

      var InsertAuditableObject = declare([PersistentObject], {
        // InsertAuditableObjects have a `createdAt` and `createdBy` property, which is set by the server.
        // These properties cannot be set in the UI, and are initially null. Once set, the server should always
        // return the same values, except after delete, when they all turn null together again.

        _c_invar: [
          function() {return this._c_prop_string("createdBy");},
          function() {return this._c_prop_date("createdAt");},
          /*
            createdAt must be in the past
            but we cannot test that: server time and time of this local computer are incomparable
           */
          function() {return !!this.get("persistenceId") === !!this.get("createdBy");}, // both exist together or not
          function() {return !!this.get("createdBy") === !!this.get("createdAt");} // both exist together or not
        ],

        // createdBy: String
        createdBy: null,

        // createdAt: Date
        createdAt: null,

        _createdAtSetter: function() {
          // createdAt is read-only
          throw "ERROR createdAt is read-only";
        },

        _createdBySetter: function() {
          // createdBy is read-only
          throw "ERROR createdBy is read-only";
        },

        reload: function(/*Object*/ json) {
          // created.. can change from null to an actual date and username number after create,
          this._c_pre(function() {return json;});
// MUDO PICTOPERFECT-482          this._c_pre(function() {return !!json.persistenceId === !!json.createdBy;});
          this._c_pre(function() {return !!json.createdBy === !!json.createdAt;});
          this._c_pre(function() {return this._c_prop_string(json, "createdBy");});
          /*
          this._c_pre(function() {return !this.get("persistenceId") || !json.persistenceId || (this.get("createdBy") === json.createdBy);});
          The above is true, from the outside, but we cannot test it here, with this contract framework, because persistenceId is set
          by the superclass from null to the new value (on create) before we reach this test. It was true before reload, but not
          halfway. What follows is an equivalent local version.
          */
// MUDO PICTOPERFECT-482          this._c_pre(function() {return !this.get("createdBy") || !json.persistenceId || (this.get("createdBy") === json.createdBy);});
          this._c_pre(function() {return this._c_prop_string(json, "createdAt") || this._c_prop_date(json, "createdAt");});
          this._c_pre(function() {return !this.get("createdBy") || !json.persistenceId || compareDate(this.get("createdAt"), stringToDate(json.createdAt)) === 0;});
          if (!this.get("createdBy") || !json.createdBy) {
            this._changeAttrValue("createdBy", json.createdBy);
            this._changeAttrValue("createdAt", stringToDate(json.createdAt));
          }
        },

        // it makes no senses whatsoever to send this data back to the back-end
        // MUDO Attempted workaround for PICTOPERFECT-484: send the data to the server
        _extendJsonObject:function (/*Object*/ json) {
          json.createdBy = this.get("createdBy");
          json.createdAt = this.get("createdAt");
        },

        _stateToString: function(/*String[]*/ toStrings) {
          toStrings.push("createdAt: " + this.createdAt);
          toStrings.push("createdBy: " + this.createdBy);
        }
      });

      InsertAuditableObject.mid = module.id;
      InsertAuditableObject.stringToDate = stringToDate;
      InsertAuditableObject.compareDate = compareDate;
      return InsertAuditableObject;
    }
);
<commit_msg>disable workaround for short confirmation<commit_after>/*
 Copyright 2012 - $Date $ by PeopleWare n.v.

 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.
 */

define(["dojo/_base/declare", "./PersistentObject", "ppwcode-util-oddsAndEnds/js", "dojo/date/stamp", "module"],
    function(declare, PersistentObject, js, dateStamp, module) {

      function stringToDate(candidate) {
        //noinspection FallthroughInSwitchStatementJS
        switch (js.typeOf(candidate)) {
          case "undefined":
          case "null":
            return null;
          case "date":
            return candidate;
          case "string":
            return dateStamp.fromISOString(candidate);
          default:
            throw "ERROR: cannot convert to date: " + candidate;
        }
      }

      function compareDate(d1, d2) {
        // summary:
        //   We use < 1000 (1s) instead of < 0, because the server stores dates only to the second.
        //   With the current server implementation, we see that if we retrieve quickly after an
        //   update or create, the retrieve lastModified at is later than the one in the response
        //   of the action. The reason is, that the data in the action response comes from RAM,
        //   and has values up to the nanosecond or so (23/6/2012 15:45:32.424242242), while the
        //   data in the retrieve comes from the DB, which only stores up to the second
        //   (23/6/2012 15:45:32). So, it seems that retrieve-date is earlier than the action
        //   response date, which is impossible. By giving our comparison a 1 second leeway,
        //   this is resolved.

        if (d1 === d2) {
          return 0;
        }
        if (!d1) {
          return -1;
        }
        if (!d2) {
          return +1;
        }
        var delta = d1.getTime() - d2.getTime();
        if (delta < -1000) {
          return -1;
        }
        if (delta > +1000) {
          return +1;
        }
        return 0;
      }

      // MUDO There is a much worse problem: a RAM-created server Date is in the local time of the server
      //      While the time in the DB has no timezone. A RAM-created server Date that is sent over JSON
      //      contains timezone information. A date that is retrieved from the DB does not contain
      //      timezone in RAM, and thus does also not contain timezone information when it is sent over
      //      JSON.
      //      When JavaScript parses a string with timezone information, it takes it into account.
      //      When JavaScript parses a string without timezone information, it assumes the local
      //      timezone.
      //      When server and client are in a different timezone, when the JSON contains timezone information,
      //      the string is interpreted in the timezone of the server. When the JSON does not contain
      //      timezone information, the string is interpreted in the timezone of the client.
      //      For webapplications, this in unacceptable.
      // MUDO for now we work around this by NOT SENDING THE DATA TO THE SERVER IN THE FIRST PLACE
      //      that was another workaround; the effect of this is that we will not see this data after update
      // MUDO but it is obviously wrong

      // MUDO Attempted workaround for PICTOPERFECT-484: send the data to the server

      var InsertAuditableObject = declare([PersistentObject], {
        // InsertAuditableObjects have a `createdAt` and `createdBy` property, which is set by the server.
        // These properties cannot be set in the UI, and are initially null. Once set, the server should always
        // return the same values, except after delete, when they all turn null together again.

        _c_invar: [
          function() {return this._c_prop_string("createdBy");},
          function() {return this._c_prop_date("createdAt");},
          /*
            createdAt must be in the past
            but we cannot test that: server time and time of this local computer are incomparable
           */
          function() {return !!this.get("persistenceId") === !!this.get("createdBy");}, // both exist together or not
          function() {return !!this.get("createdBy") === !!this.get("createdAt");} // both exist together or not
        ],

        // createdBy: String
        createdBy: null,

        // createdAt: Date
        createdAt: null,

        _createdAtSetter: function() {
          // createdAt is read-only
          throw "ERROR createdAt is read-only";
        },

        _createdBySetter: function() {
          // createdBy is read-only
          throw "ERROR createdBy is read-only";
        },

        reload: function(/*Object*/ json) {
          // created.. can change from null to an actual date and username number after create,
          this._c_pre(function() {return json;});
// MUDO PICTOPERFECT-482          this._c_pre(function() {return !!json.persistenceId === !!json.createdBy;});
          this._c_pre(function() {return !!json.createdBy === !!json.createdAt;});
          this._c_pre(function() {return this._c_prop_string(json, "createdBy");});
          /*
          this._c_pre(function() {return !this.get("persistenceId") || !json.persistenceId || (this.get("createdBy") === json.createdBy);});
          The above is true, from the outside, but we cannot test it here, with this contract framework, because persistenceId is set
          by the superclass from null to the new value (on create) before we reach this test. It was true before reload, but not
          halfway. What follows is an equivalent local version.
          */
// MUDO PICTOPERFECT-482          this._c_pre(function() {return !this.get("createdBy") || !json.persistenceId || (this.get("createdBy") === json.createdBy);});
          this._c_pre(function() {return this._c_prop_string(json, "createdAt") || this._c_prop_date(json, "createdAt");});
          this._c_pre(function() {return !this.get("createdBy") || !json.persistenceId || compareDate(this.get("createdAt"), stringToDate(json.createdAt)) === 0;});
          if (!this.get("createdBy") || !json.createdBy) {
            this._changeAttrValue("createdBy", json.createdBy);
            this._changeAttrValue("createdAt", stringToDate(json.createdAt));
          }
        },

        // it makes no senses whatsoever to send this data back to the back-end
        // MUDO Attempted workaround for PICTOPERFECT-484: send the data to the server

        // MUDO this workaround works; disabled for a short time to confirm
//        _extendJsonObject:function (/*Object*/ json) {
//          json.createdBy = this.get("createdBy");
//          json.createdAt = this.get("createdAt");
//        },

        _stateToString: function(/*String[]*/ toStrings) {
          toStrings.push("createdAt: " + this.createdAt);
          toStrings.push("createdBy: " + this.createdBy);
        }
      });

      InsertAuditableObject.mid = module.id;
      InsertAuditableObject.stringToDate = stringToDate;
      InsertAuditableObject.compareDate = compareDate;
      return InsertAuditableObject;
    }
);
<|endoftext|>