<commit_before>import {inject} from 'aurelia-dependency-injection';
import {touchy} from './touchy';
import {GLOBAL_OPTIONS, Options} from './options';
import {Util} from './util';
import {Emitter} from './emitter';
import * as classes from './classes';

const MIN_TIME_BETWEEN_REDRAWS_MS = 20;

@inject(GLOBAL_OPTIONS)
export class Dragula {

  constructor(options) {
    let len = arguments.length;
    this.options = options || new Options();
    this.emitter = new Emitter();
    this.dragging = false;

    if (this.options.removeOnSpill === true) {
      this.emitter.on('over', ::this.spillOver);
      this.emitter.on('out', ::this.spillOut);
    }

    this._events();

    this._mirror; // mirror image
    this._source; // source container
    this._item; // item being dragged
    this._offsetX; // reference x
    this._offsetY; // reference y
    this._moveX; // reference move x
    this._moveY; // reference move y
    this._initialSibling; // reference sibling when grabbed
    this._currentSibling; // reference sibling now
    this._copy; // item used for copying
    this._lastRenderTime = null; // last time we rendered the mirror
    this._lastDropTarget = null; // last container item was over
    this._grabbed; // holds mousedown context until first mousemove
  }

  on(eventName, callback) {
    this.emitter.on(eventName, callback);
  }

  get containers() {
    return this.options.containers;
  }

  set containers(value) {
    this.options.containers = value;
  }

  isContainer(el) {
    return this.options.containers.indexOf(el) !== -1 || this.options.isContainer(el);
  }

  _events(remove) {
    let op = remove ? 'removeEventListener' : 'addEventListener';
    touchy(document.documentElement, op, 'mousedown', ::this._grab);
    touchy(document.documentElement, op, 'mouseup', ::this._release);
  }

  _eventualMovements(remove) {
    let op = remove ? 'removeEventListener' : 'addEventListener';
    touchy(document.documentElement, op, 'mousemove', ::this._startBecauseMouseMoved);
  }

  _movements(remove) {
    let op = remove ? 'removeEventListener' : 'addEventListener';
    document.documentElement[op]('click', this._preventGrabbed);
  }

  destroy() {
    this._events(true);
    this._release({});
  }

  _preventGrabbed(e) {
    if (this._grabbed) {
      e.preventDefault();
    }
  }

  _grab(e) {
    this._moveX = e.clientX;
    this._moveY = e.clientY;

    let ignore = Util.whichMouseButton(e) !== 1 || e.metaKey || e.ctrlKey;
    if (ignore) {
      return; // we only care about honest-to-god left clicks and touch events
    }
    let item = e.target;
    let context = this._canStart(item);
    if (!context) {
      return;
    }
    this._grabbed = context;
    this._eventualMovements();
    if (e.type === 'mousedown') {
      if (Util.isInput(item)) { // see also: https://github.com/bevacqua/dragula/issues/208
        item.focus(); // fixes https://github.com/bevacqua/dragula/issues/176
      } else {
        e.preventDefault(); // fixes https://github.com/bevacqua/dragula/issues/155
      }
    }
  }

  _startBecauseMouseMoved (e) {
    if (!this._grabbed) {
      return;
    }
    if (Util.whichMouseButton(e) === 0) {
      this._release({});
      return; // when text is selected on an input and then dragged, mouseup doesn't fire. this is our only hope
    }
    // truthy check fixes #239, equality fixes #207
    if (e.clientX !== void 0 && e.clientX === this._moveX && e.clientY !== void 0 && e.clientY === this._moveY) {
      return;
    }
    if (this.options.ignoreInputTextSelection) {
      let clientX = Util.getCoord('clientX', e);
      let clientY = Util.getCoord('clientY', e);
      let elementBehindCursor = document.elementFromPoint(clientX, clientY);
      if (Util.isInput(elementBehindCursor)) {
        return;
      }
    }

    let grabbed = this._grabbed; // call to end() unsets _grabbed
    this._eventualMovements(true);
    this._movements();
    this.end();
    this.start(grabbed);

    let offset = Util.getOffset(this._item);
    this._offsetX = Util.getCoord('pageX', e) - offset.left;
    this._offsetY = Util.getCoord('pageY', e) - offset.top;

    classes.add(this._copy || this._item, 'gu-transit');
    this.renderMirrorImage();
    this.drag(e);
  }

  _canStart(item) {
    if (this.dragging && this._mirror) {
      return;
    }
    if (this.isContainer(item)) {
      return; // don't drag container itself
    }
    let handle = item;
    while (Util.getParent(item) && this.isContainer(Util.getParent(item)) === false) {
      if (this.options.invalid(item, handle)) {
        return;
      }
      item = Util.getParent(item); // drag target should be a top element
      if (!item) {
        return;
      }
    }
    let source = Util.getParent(item);
    if (!source) {
      return;
    }
    if (this.options.invalid(item, handle)) {
      return;
    }

    let movable = this.options.moves(item, source, handle, Util.nextEl(item));
    if (!movable) {
      return;
    }

    return {
      item: item,
      source: source
    };
  }

  manualStart(item) {
    let context = this._canStart(item);
    if (context) {
      this.start(context);
    }
  }

  start(context) {
    if (this._isCopy(context.item, context.source)) {
      this._copy = context.item.cloneNode(true);
      this.emitter.emit('cloned', this._copy, context.item, 'copy');
    }

    this._source = context.source;
    this._item = context.item;
    this._initialSibling = context.item.nextSibling;
    this._currentSibling = Util.nextEl(context.item);

    this._timeSinceLastMove = Date.now() + MIN_TIME_BETWEEN_REDRAWS_MS; //ensure that the first frame draws...

    this.dragging = true;
    this.emitter.emit('drag', this._item, this._source);
  }

  end() {
    if (!this.dragging) {
      return;
    }
    let item = this._copy || this._item;
    this.drop(item, Util.getParent(item));
  }

  _ungrab() {
    this._grabbed = false;
    this._eventualMovements(true);
    this._movements(true);
  }

  _release(e) {
    this._ungrab();

    if (!this.dragging) {
      return;
    }
    let item = this._copy || this._item;
    let clientX = Util.getCoord('clientX', e);
    let clientY = Util.getCoord('clientY', e);
    let elementBehindCursor = Util.getElementBehindPoint(this._mirror, clientX, clientY);
    let dropTarget = this._findDropTarget(elementBehindCursor, clientX, clientY);
    if (dropTarget && ((this._copy && this.options.copySortSource) || (!this._copy || dropTarget !== this._source))) {
      this.drop(item, dropTarget);
    } else if (this.options.removeOnSpill) {
      this.remove();
    } else {
      this.cancel();
    }
  }

  drop(item, target) {
    let parent = Util.getParent(item);
    if (this._copy && this.options.copySortSource && target === this._source) {
      parent.removeChild(_item);
    }
    if (this._isInitialPlacement(target)) {
      this.emitter.emit('cancel', item, this._source, this._source);
    } else {
      this.emitter.emit('drop', item, target, this._source, this._currentSibling);
    }
    this._cleanup();
  }

  remove() {
    if (!this.dragging) {
      return;
    }
    let item = this._copy || this._item;
    let parent = Util.getParent(item);
    if (parent) {
      parent.removeChild(item);
    }
    this.emitter.emit(this._copy ? 'cancel' : 'remove', item, parent, this._source);
    this._cleanup();
  }

  cancel(revert) {
    if (!this.dragging) {
      return;
    }
    let reverts = arguments.length > 0 ? revert : this.options.revertOnSpill;
    let item = this._copy || this._item;
    let parent = Util.getParent(item);
    if (parent === this._source && this._copy) {
      parent.removeChild(this._copy);
    }
    let initial = this._isInitialPlacement(parent);
    if (initial === false && !this._copy && reverts) {
      this._source.insertBefore(item, this._initialSibling);
    }
    if (initial || reverts) {
      this.emitter.emit('cancel', item, this._source, this._source);
    } else {
      this.emitter.emit('drop', item, parent, this._source, this._currentSibling);
    }
    this._cleanup();
  }

  _cleanup () {
    let item = this._copy || this._item;
    this._ungrab();
    this.removeMirrorImage();
    if (item) {
      classes.rm(item, 'gu-transit');
    }
    this.dragging = false;
    if (this._lastDropTarget) {
      this.emitter.emit('out', item, this._lastDropTarget, this._source);
    }
    this.emitter.emit('dragend', item);
    this._source = this._item = this._copy = this._initialSibling = this._currentSibling = this._lastRenderTime = this._lastDropTarget = null;
  }

  _isInitialPlacement(target, s) {
    let sibling;
    if (s !== void 0) {
      sibling = s;
    } else if (this._mirror) {
      sibling = this._currentSibling;
    } else {
      sibling = (this._copy || this._item).nextSibling;
    }
    return target === this._source && sibling === this._initialSibling;
  }

  _findDropTarget(elementBehindCursor, clientX, clientY) {
    let accepted = () => {
      let droppable = this.isContainer(target);
      if (droppable === false) {
        return false;
      }

      let immediate = Util.getImmediateChild(target, elementBehindCursor);
      let reference = this.getReference(target, immediate, clientX, clientY);
      let initial = this._isInitialPlacement(target, reference);
      if (initial) {
        return true; // should always be able to drop it right back where it was
      }
      return this.options.accepts(this._item, target, this._source, reference);
    }

    let target = elementBehindCursor;
    while (target && !accepted()) {
      target = Util.getParent(target);
    }
    return target;
  }

  drag(e) {
    if (!this._mirror) {
      return;
    }

    if (this._lastRenderTime !== null && Date.now() - this._lastRenderTime < MIN_TIME_BETWEEN_REDRAWS_MS) {
      return;
    }
    this._lastRenderTime = Date.now();
    e.preventDefault();

    let moved = (type) => { this.emitter.emit(type, item, this._lastDropTarget, this._source); }
    let over = () => { if (changed) { moved('over'); } }
    let out = () => { if (this._lastDropTarget) { moved('out'); } }

    let clientX = Util.getCoord('clientX', e);
    let clientY = Util.getCoord('clientY', e);
    let x = clientX - this._offsetX;
    let y = clientY - this._offsetY;

    this._mirror.style.left = x + 'px';
    this._mirror.style.top = y + 'px';

    let item = this._copy || this._item;
    let elementBehindCursor = Util.getElementBehindPoint(this._mirror, clientX, clientY);
    let dropTarget = this._findDropTarget(elementBehindCursor, clientX, clientY);
    let changed = dropTarget !== null && dropTarget !== this._lastDropTarget;
    if (changed || dropTarget === null) {
      out();
      this._lastDropTarget = dropTarget;
      over();
    }
    let parent = Util.getParent(item);
    if (dropTarget === this._source && this._copy && !this.options.copySortSource) {
      if (parent) {
        parent.removeChild(item);
      }
      return;
    }
    let reference;
    let immediate = Util.getImmediateChild(dropTarget, elementBehindCursor);
    if (immediate !== null) {
      reference = this.getReference(dropTarget, immediate, clientX, clientY);
    } else if (this.options.revertOnSpill === true && !this._copy) {
      reference = this._initialSibling;
      dropTarget = this._source;
    } else {
      if (this._copy && parent) {
        parent.removeChild(item);
      }
      return;
    }
    if (
      (reference === null && changed) ||
      reference !== item &&
      reference !== Util.nextEl(item)
    ) {
      this._currentSibling = reference;
      dropTarget.insertBefore(item, reference);
      this.emitter.emit('shadow', item, dropTarget, this._source);
    }
  }

  spillOver(el) {
    classes.rm(el, 'gu-hide');
  }

  spillOut(el) {
    if (this.dragging) { classes.add(el, 'gu-hide'); }
  }

  renderMirrorImage() {
    if (this._mirror) {
      return;
    }
    let rect = this._item.getBoundingClientRect();
    this._mirror = this._item.cloneNode(true);
    this._mirror.style.width = Util.getRectWidth(rect) + 'px';
    this._mirror.style.height = Util.getRectHeight(rect) + 'px';
    classes.rm(this._mirror, 'gu-transit');
    classes.add(this._mirror, 'gu-mirror');
    this.options.mirrorContainer.appendChild(this._mirror);
    touchy(document.documentElement, 'addEventListener', 'mousemove', ::this.drag);
    classes.add(this.options.mirrorContainer, 'gu-unselectable');
    this.emitter.emit('cloned', this._mirror, this._item, 'mirror');
  }

  removeMirrorImage() {
    if (this._mirror) {
      classes.rm(this.options.mirrorContainer, 'gu-unselectable');
      touchy(document.documentElement, 'removeEventListener', 'mousemove', ::this.drag);
      Util.getParent(this._mirror).removeChild(this._mirror);
      this._mirror = null;
    }
  }

  getReference(dropTarget, target, x, y) {
    let outside = () => { // slower, but able to figure out any position
      let len = dropTarget.children.length;
      let i;
      let el;
      let rect;
      for (i = 0; i < len; i++) {
        el = dropTarget.children[i];
        rect = el.getBoundingClientRect();
        if (horizontal && (rect.left + rect.width / 2) > x) { return el; }
        if (!horizontal && (rect.top + rect.height / 2) > y) { return el; }
      }
      return null;
    }

    let resolve = (after) => {
      return after ? Util.nextEl(target) : target;
    }

    let inside = () => { // faster, but only available if dropped inside a child element
      let rect = target.getBoundingClientRect();
      if (horizontal) {
        return resolve(x > rect.left + Util.getRectWidth(rect) / 2);
      }
      return resolve(y > rect.top + Util.getRectHeight(rect) / 2);
    }

    let horizontal = this.options.direction === 'horizontal';
    let reference = target !== dropTarget ? inside() : outside();
    return reference;
  }

  _isCopy(item, container) {
    return typeof this.options.copy === 'boolean' ? this.options.copy : this.options.copy(item, container);
  }

}<commit_msg>Not important that this is removed, but useful, nonetheless<commit_after>import {inject} from 'aurelia-dependency-injection';
import {touchy} from './touchy';
import {GLOBAL_OPTIONS, Options} from './options';
import {Util} from './util';
import {Emitter} from './emitter';
import * as classes from './classes';

const MIN_TIME_BETWEEN_REDRAWS_MS = 20;

@inject(GLOBAL_OPTIONS)
export class Dragula {

  constructor(options) {
    let len = arguments.length;
    this.options = options || new Options();
    this.emitter = new Emitter();
    this.dragging = false;

    if (this.options.removeOnSpill === true) {
      this.emitter.on('over', ::this.spillOver);
      this.emitter.on('out', ::this.spillOut);
    }

    this._events();

    this._mirror; // mirror image
    this._source; // source container
    this._item; // item being dragged
    this._offsetX; // reference x
    this._offsetY; // reference y
    this._moveX; // reference move x
    this._moveY; // reference move y
    this._initialSibling; // reference sibling when grabbed
    this._currentSibling; // reference sibling now
    this._copy; // item used for copying
    this._lastRenderTime = null; // last time we rendered the mirror
    this._lastDropTarget = null; // last container item was over
    this._grabbed; // holds mousedown context until first mousemove
  }

  on(eventName, callback) {
    this.emitter.on(eventName, callback);
  }

  get containers() {
    return this.options.containers;
  }

  set containers(value) {
    this.options.containers = value;
  }

  isContainer(el) {
    return this.options.containers.indexOf(el) !== -1 || this.options.isContainer(el);
  }

  _events(remove) {
    let op = remove ? 'removeEventListener' : 'addEventListener';
    touchy(document.documentElement, op, 'mousedown', ::this._grab);
    touchy(document.documentElement, op, 'mouseup', ::this._release);
  }

  _eventualMovements(remove) {
    let op = remove ? 'removeEventListener' : 'addEventListener';
    touchy(document.documentElement, op, 'mousemove', ::this._startBecauseMouseMoved);
  }

  _movements(remove) {
    let op = remove ? 'removeEventListener' : 'addEventListener';
    document.documentElement[op]('click', this._preventGrabbed);
  }

  destroy() {
    this._events(true);
    this._release({});
  }

  _preventGrabbed(e) {
    if (this._grabbed) {
      e.preventDefault();
    }
  }

  _grab(e) {
    this._moveX = e.clientX;
    this._moveY = e.clientY;

    let ignore = Util.whichMouseButton(e) !== 1 || e.metaKey || e.ctrlKey;
    if (ignore) {
      return; // we only care about honest-to-god left clicks and touch events
    }
    let item = e.target;
    let context = this._canStart(item);
    if (!context) {
      return;
    }
    this._grabbed = context;
    this._eventualMovements();
    if (e.type === 'mousedown') {
      if (Util.isInput(item)) { // see also: https://github.com/bevacqua/dragula/issues/208
        item.focus(); // fixes https://github.com/bevacqua/dragula/issues/176
      } else {
        e.preventDefault(); // fixes https://github.com/bevacqua/dragula/issues/155
      }
    }
  }

  _startBecauseMouseMoved (e) {
    if (!this._grabbed) {
      return;
    }
    if (Util.whichMouseButton(e) === 0) {
      this._release({});
      return; // when text is selected on an input and then dragged, mouseup doesn't fire. this is our only hope
    }
    // truthy check fixes #239, equality fixes #207
    if (e.clientX !== void 0 && e.clientX === this._moveX && e.clientY !== void 0 && e.clientY === this._moveY) {
      return;
    }
    if (this.options.ignoreInputTextSelection) {
      let clientX = Util.getCoord('clientX', e);
      let clientY = Util.getCoord('clientY', e);
      let elementBehindCursor = document.elementFromPoint(clientX, clientY);
      if (Util.isInput(elementBehindCursor)) {
        return;
      }
    }

    let grabbed = this._grabbed; // call to end() unsets _grabbed
    this._eventualMovements(true);
    this._movements();
    this.end();
    this.start(grabbed);

    let offset = Util.getOffset(this._item);
    this._offsetX = Util.getCoord('pageX', e) - offset.left;
    this._offsetY = Util.getCoord('pageY', e) - offset.top;

    classes.add(this._copy || this._item, 'gu-transit');
    this.renderMirrorImage();
    this.drag(e);
  }

  _canStart(item) {
    if (this.dragging && this._mirror) {
      return;
    }
    if (this.isContainer(item)) {
      return; // don't drag container itself
    }
    let handle = item;
    while (Util.getParent(item) && this.isContainer(Util.getParent(item)) === false) {
      if (this.options.invalid(item, handle)) {
        return;
      }
      item = Util.getParent(item); // drag target should be a top element
      if (!item) {
        return;
      }
    }
    let source = Util.getParent(item);
    if (!source) {
      return;
    }
    if (this.options.invalid(item, handle)) {
      return;
    }

    let movable = this.options.moves(item, source, handle, Util.nextEl(item));
    if (!movable) {
      return;
    }

    return {
      item: item,
      source: source
    };
  }

  manualStart(item) {
    let context = this._canStart(item);
    if (context) {
      this.start(context);
    }
  }

  start(context) {
    if (this._isCopy(context.item, context.source)) {
      this._copy = context.item.cloneNode(true);
      this.emitter.emit('cloned', this._copy, context.item, 'copy');
    }

    this._source = context.source;
    this._item = context.item;
    this._initialSibling = context.item.nextSibling;
    this._currentSibling = Util.nextEl(context.item);

    this.dragging = true;
    this.emitter.emit('drag', this._item, this._source);
  }

  end() {
    if (!this.dragging) {
      return;
    }
    let item = this._copy || this._item;
    this.drop(item, Util.getParent(item));
  }

  _ungrab() {
    this._grabbed = false;
    this._eventualMovements(true);
    this._movements(true);
  }

  _release(e) {
    this._ungrab();

    if (!this.dragging) {
      return;
    }
    let item = this._copy || this._item;
    let clientX = Util.getCoord('clientX', e);
    let clientY = Util.getCoord('clientY', e);
    let elementBehindCursor = Util.getElementBehindPoint(this._mirror, clientX, clientY);
    let dropTarget = this._findDropTarget(elementBehindCursor, clientX, clientY);
    if (dropTarget && ((this._copy && this.options.copySortSource) || (!this._copy || dropTarget !== this._source))) {
      this.drop(item, dropTarget);
    } else if (this.options.removeOnSpill) {
      this.remove();
    } else {
      this.cancel();
    }
  }

  drop(item, target) {
    let parent = Util.getParent(item);
    if (this._copy && this.options.copySortSource && target === this._source) {
      parent.removeChild(_item);
    }
    if (this._isInitialPlacement(target)) {
      this.emitter.emit('cancel', item, this._source, this._source);
    } else {
      this.emitter.emit('drop', item, target, this._source, this._currentSibling);
    }
    this._cleanup();
  }

  remove() {
    if (!this.dragging) {
      return;
    }
    let item = this._copy || this._item;
    let parent = Util.getParent(item);
    if (parent) {
      parent.removeChild(item);
    }
    this.emitter.emit(this._copy ? 'cancel' : 'remove', item, parent, this._source);
    this._cleanup();
  }

  cancel(revert) {
    if (!this.dragging) {
      return;
    }
    let reverts = arguments.length > 0 ? revert : this.options.revertOnSpill;
    let item = this._copy || this._item;
    let parent = Util.getParent(item);
    if (parent === this._source && this._copy) {
      parent.removeChild(this._copy);
    }
    let initial = this._isInitialPlacement(parent);
    if (initial === false && !this._copy && reverts) {
      this._source.insertBefore(item, this._initialSibling);
    }
    if (initial || reverts) {
      this.emitter.emit('cancel', item, this._source, this._source);
    } else {
      this.emitter.emit('drop', item, parent, this._source, this._currentSibling);
    }
    this._cleanup();
  }

  _cleanup () {
    let item = this._copy || this._item;
    this._ungrab();
    this.removeMirrorImage();
    if (item) {
      classes.rm(item, 'gu-transit');
    }
    this.dragging = false;
    if (this._lastDropTarget) {
      this.emitter.emit('out', item, this._lastDropTarget, this._source);
    }
    this.emitter.emit('dragend', item);
    this._source = this._item = this._copy = this._initialSibling = this._currentSibling = this._lastRenderTime = this._lastDropTarget = null;
  }

  _isInitialPlacement(target, s) {
    let sibling;
    if (s !== void 0) {
      sibling = s;
    } else if (this._mirror) {
      sibling = this._currentSibling;
    } else {
      sibling = (this._copy || this._item).nextSibling;
    }
    return target === this._source && sibling === this._initialSibling;
  }

  _findDropTarget(elementBehindCursor, clientX, clientY) {
    let accepted = () => {
      let droppable = this.isContainer(target);
      if (droppable === false) {
        return false;
      }

      let immediate = Util.getImmediateChild(target, elementBehindCursor);
      let reference = this.getReference(target, immediate, clientX, clientY);
      let initial = this._isInitialPlacement(target, reference);
      if (initial) {
        return true; // should always be able to drop it right back where it was
      }
      return this.options.accepts(this._item, target, this._source, reference);
    }

    let target = elementBehindCursor;
    while (target && !accepted()) {
      target = Util.getParent(target);
    }
    return target;
  }

  drag(e) {
    if (!this._mirror) {
      return;
    }

    if (this._lastRenderTime !== null && Date.now() - this._lastRenderTime < MIN_TIME_BETWEEN_REDRAWS_MS) {
      return;
    }
    this._lastRenderTime = Date.now();
    e.preventDefault();

    let moved = (type) => { this.emitter.emit(type, item, this._lastDropTarget, this._source); }
    let over = () => { if (changed) { moved('over'); } }
    let out = () => { if (this._lastDropTarget) { moved('out'); } }

    let clientX = Util.getCoord('clientX', e);
    let clientY = Util.getCoord('clientY', e);
    let x = clientX - this._offsetX;
    let y = clientY - this._offsetY;

    this._mirror.style.left = x + 'px';
    this._mirror.style.top = y + 'px';

    let item = this._copy || this._item;
    let elementBehindCursor = Util.getElementBehindPoint(this._mirror, clientX, clientY);
    let dropTarget = this._findDropTarget(elementBehindCursor, clientX, clientY);
    let changed = dropTarget !== null && dropTarget !== this._lastDropTarget;
    if (changed || dropTarget === null) {
      out();
      this._lastDropTarget = dropTarget;
      over();
    }
    let parent = Util.getParent(item);
    if (dropTarget === this._source && this._copy && !this.options.copySortSource) {
      if (parent) {
        parent.removeChild(item);
      }
      return;
    }
    let reference;
    let immediate = Util.getImmediateChild(dropTarget, elementBehindCursor);
    if (immediate !== null) {
      reference = this.getReference(dropTarget, immediate, clientX, clientY);
    } else if (this.options.revertOnSpill === true && !this._copy) {
      reference = this._initialSibling;
      dropTarget = this._source;
    } else {
      if (this._copy && parent) {
        parent.removeChild(item);
      }
      return;
    }
    if (
      (reference === null && changed) ||
      reference !== item &&
      reference !== Util.nextEl(item)
    ) {
      this._currentSibling = reference;
      dropTarget.insertBefore(item, reference);
      this.emitter.emit('shadow', item, dropTarget, this._source);
    }
  }

  spillOver(el) {
    classes.rm(el, 'gu-hide');
  }

  spillOut(el) {
    if (this.dragging) { classes.add(el, 'gu-hide'); }
  }

  renderMirrorImage() {
    if (this._mirror) {
      return;
    }
    let rect = this._item.getBoundingClientRect();
    this._mirror = this._item.cloneNode(true);
    this._mirror.style.width = Util.getRectWidth(rect) + 'px';
    this._mirror.style.height = Util.getRectHeight(rect) + 'px';
    classes.rm(this._mirror, 'gu-transit');
    classes.add(this._mirror, 'gu-mirror');
    this.options.mirrorContainer.appendChild(this._mirror);
    touchy(document.documentElement, 'addEventListener', 'mousemove', ::this.drag);
    classes.add(this.options.mirrorContainer, 'gu-unselectable');
    this.emitter.emit('cloned', this._mirror, this._item, 'mirror');
  }

  removeMirrorImage() {
    if (this._mirror) {
      classes.rm(this.options.mirrorContainer, 'gu-unselectable');
      touchy(document.documentElement, 'removeEventListener', 'mousemove', ::this.drag);
      Util.getParent(this._mirror).removeChild(this._mirror);
      this._mirror = null;
    }
  }

  getReference(dropTarget, target, x, y) {
    let outside = () => { // slower, but able to figure out any position
      let len = dropTarget.children.length;
      let i;
      let el;
      let rect;
      for (i = 0; i < len; i++) {
        el = dropTarget.children[i];
        rect = el.getBoundingClientRect();
        if (horizontal && (rect.left + rect.width / 2) > x) { return el; }
        if (!horizontal && (rect.top + rect.height / 2) > y) { return el; }
      }
      return null;
    }

    let resolve = (after) => {
      return after ? Util.nextEl(target) : target;
    }

    let inside = () => { // faster, but only available if dropped inside a child element
      let rect = target.getBoundingClientRect();
      if (horizontal) {
        return resolve(x > rect.left + Util.getRectWidth(rect) / 2);
      }
      return resolve(y > rect.top + Util.getRectHeight(rect) / 2);
    }

    let horizontal = this.options.direction === 'horizontal';
    let reference = target !== dropTarget ? inside() : outside();
    return reference;
  }

  _isCopy(item, container) {
    return typeof this.options.copy === 'boolean' ? this.options.copy : this.options.copy(item, container);
  }

}<|endoftext|>


<commit_before>var Tokenizer = require("./Tokenizer.js");

/*
	Options:

	xmlMode: Special behavior for script/style tags (true by default)
	lowerCaseAttributeNames: call .toLowerCase for each attribute name (true if xmlMode is `false`)
	lowerCaseTags: call .toLowerCase for each tag name (true if xmlMode is `false`)
*/

/*
	Callbacks:

	oncdataend,
	oncdatastart,
	onclosetag,
	oncomment,
	oncommentend,
	onerror,
	onopentag,
	onprocessinginstruction,
	onreset,
	ontext
*/

var formTags = {
    input: true,
    option: true,
    optgroup: true,
    select: true,
    button: true,
    datalist: true,
    textarea: true
};

var openImpliesClose = {
    tr: { tr: true, th: true, td: true },
    th: { th: true },
    td: { thead: true, td: true },
    body: { head: true, link: true, script: true },
    li: { li: true },
    p: { p: true },
    select: formTags,
    input: formTags,
    output: formTags,
    button: formTags,
    datalist: formTags,
    textarea: formTags,
    option: { option: true },
    optgroup: { optgroup: true }
};

var voidElements = {
    __proto__: null,
    area: true,
    base: true,
    basefont: true,
    br: true,
    col: true,
    command: true,
    embed: true,
    frame: true,
    hr: true,
    img: true,
    input: true,
    isindex: true,
    keygen: true,
    link: true,
    meta: true,
    param: true,
    source: true,
    track: true,
    wbr: true
};

var re_nameEnd = /\s|\//;

function Parser(cbs, options) {
    this._options = options || {};
    this._cbs = cbs || {};

    this._tagname = "";
    this._attribname = "";
    this._attribs = null;
    this._stack = [];
    this._done = false;

    this.startIndex = 0;
    this.endIndex = null;

    this._tokenizer = new Tokenizer(options, this);
}

require("util").inherits(Parser, require("events").EventEmitter);

Parser.prototype._updatePosition = function(initialOffset) {
    if (this.endIndex === null) {
        this.startIndex =
            this._tokenizer._sectionStart <= initialOffset
                ? 0
                : this._tokenizer._sectionStart - initialOffset;
    }
    this.startIndex = this.endIndex + 1;
    this.endIndex = this._tokenizer._index;
};

//Tokenizer event handlers
Parser.prototype.ontext = function(data) {
    this._updatePosition(1);
    this.endIndex--;

    if (this._cbs.ontext) this._cbs.ontext(data);
};

Parser.prototype.onopentagname = function(name) {
    if (
        !(this._options.xmlMode || "lowerCaseTags" in this._options) ||
        this._options.lowerCaseTags
    ) {
        name = name.toLowerCase();
    }

    this._tagname = name;

    if (!this._options.xmlMode && name in openImpliesClose) {
        for (
            var el;
            (el = this._stack[this._stack.length - 1]) in
            openImpliesClose[name];
            this.onclosetag(el)
        );
    }

    if (this._options.xmlMode || !(name in voidElements)) {
        this._stack.push(name);
    }

    if (this._cbs.onopentagname) this._cbs.onopentagname(name);
    if (this._cbs.onopentag) this._attribs = {};
};

Parser.prototype.onopentagend = function() {
    this._updatePosition(1);

    if (this._attribname !== "") this.onattribvalue("");

    if (this._attribs) {
        if (this._cbs.onopentag)
            this._cbs.onopentag(this._tagname, this._attribs);
        this._attribs = null;
    }

    if (
        !this._options.xmlMode &&
        this._cbs.onclosetag &&
        this._tagname in voidElements
    ) {
        this._cbs.onclosetag(this._tagname);
    }

    this._tagname = "";
};

Parser.prototype.onclosetag = function(name) {
    this._updatePosition(1);

    if (
        !(this._options.xmlMode || "lowerCaseTags" in this._options) ||
        this._options.lowerCaseTags
    ) {
        name = name.toLowerCase();
    }
    if (
        this._stack.length &&
        (!(name in voidElements) || this._options.xmlMode)
    ) {
        var pos = this._stack.lastIndexOf(name);
        if (pos !== -1) {
            if (this._cbs.onclosetag) {
                pos = this._stack.length - pos;
                while (pos--) this._cbs.onclosetag(this._stack.pop());
            } else this._stack.splice(pos);
        } else if (name === "p" && !this._options.xmlMode) {
            this.onopentagname(name);
            this.onselfclosingtag();
        }
    } else if (!this._options.xmlMode && (name === "br" || name === "p")) {
        this.onopentagname(name);
        this.onselfclosingtag();
    }
};

Parser.prototype.onselfclosingtag = function() {
    var name = this._tagname;

    this.onopentagend();

    //self-closing tags will be on the top of the stack
    //(cheaper check than in onclosetag)
    if (this._stack[this._stack.length - 1] === name) {
        if (this._cbs.onclosetag) {
            this._cbs.onclosetag(name);
        }
        this._stack.pop();
    }
};

Parser.prototype.onattribname = function(name) {
    if (this._attribname !== "") this.onattribvalue("");
    if (
        !(
            this._options.xmlMode || "lowerCaseAttributeNames" in this._options
        ) ||
        this._options.lowerCaseAttributeNames
    ) {
        name = name.toLowerCase();
    }
    this._attribname = name;
};

Parser.prototype.onattribvalue = function attribValue(value) {
    if (this._cbs.onattribute) this._cbs.onattribute(this._attribname, value);
    if (
        this._attribs &&
        !Object.prototype.hasOwnProperty.call(this._attribs, this._attribname)
    ) {
        this._attribs[this._attribname] = value;
    }
    this._attribname = "";
};

Parser.prototype.ondeclaration = function(value) {
    if (this._cbs.onprocessinginstruction) {
        var idx = value.search(re_nameEnd),
            name = idx < 0 ? value : value.substr(0, idx);

        if (
            !(this._options.xmlMode || "lowerCaseTags" in this._options) ||
            this._options.lowerCaseTags
        ) {
            name = name.toLowerCase();
        }
        this._cbs.onprocessinginstruction("!" + name, "!" + value);
    }
};

Parser.prototype.onprocessinginstruction = function(value) {
    if (this._cbs.onprocessinginstruction) {
        var idx = value.search(re_nameEnd),
            name = idx < 0 ? value : value.substr(0, idx);

        if (
            !(this._options.xmlMode || "lowerCaseTags" in this._options) ||
            this._options.lowerCaseTags
        ) {
            name = name.toLowerCase();
        }
        this._cbs.onprocessinginstruction("?" + name, "?" + value);
    }
};

Parser.prototype.oncomment = function(value) {
    this._updatePosition(4);

    if (this._cbs.oncomment) this._cbs.oncomment(value);
    if (this._cbs.oncommentend) this._cbs.oncommentend();
};

Parser.prototype.oncdata = function(value) {
    this._updatePosition(1);

    if (this._options.xmlMode) {
        if (this._cbs.oncdatastart) this._cbs.oncdatastart();
        if (this._cbs.ontext) this._cbs.ontext(value);
        if (this._cbs.oncdataend) this._cbs.oncdataend();
    } else {
        this.oncomment("[CDATA[" + value + "]]");
    }
};

Parser.prototype.onerror = function(err) {
    if (this._cbs.onerror) this._cbs.onerror(err);
};

Parser.prototype.onend = function() {
    if (this._cbs.onclosetag) {
        for (
            var i = this._stack.length;
            i > 0;
            this._cbs.onclosetag(this._stack[--i])
        );
    }
    if (this._cbs.onend) this._cbs.onend();
};

//Resets the parser to a blank state, ready to parse a new HTML document
Parser.prototype.reset = function() {
    if (this._cbs.onreset) this._cbs.onreset();
    this._tokenizer.reset();

    this._tagname = "";
    this._attribname = "";
    this._attribs = null;
    this._stack = [];
    this._done = false;
};

//Parses a complete HTML document and pushes it to the handler
Parser.prototype.parseComplete = function(data) {
    this.reset();
    this.end(data);
};

Parser.prototype.write = function(chunk) {
    if (this._done) this.onerror(Error(".write() after done!"));
    this._tokenizer.write(chunk);
};

Parser.prototype.end = function(chunk) {
    if (this._done) this.onerror(Error(".end() after done!"));
    this._tokenizer.end(chunk);
    this._done = true;
};

//alias for backwards compat
Parser.prototype.parseChunk = Parser.prototype.write;
Parser.prototype.done = Parser.prototype.end;

module.exports = Parser;
<commit_msg>[parser] added onattribdata and onattribend events, dropped onattribvalue<commit_after>var Tokenizer = require("./Tokenizer.js");

/*
	Options:

	xmlMode: Special behavior for script/style tags (true by default)
	lowerCaseAttributeNames: call .toLowerCase for each attribute name (true if xmlMode is `false`)
	lowerCaseTags: call .toLowerCase for each tag name (true if xmlMode is `false`)
*/

/*
	Callbacks:

	oncdataend,
	oncdatastart,
	onclosetag,
	oncomment,
	oncommentend,
	onerror,
	onopentag,
	onprocessinginstruction,
	onreset,
	ontext
*/

var formTags = {
    input: true,
    option: true,
    optgroup: true,
    select: true,
    button: true,
    datalist: true,
    textarea: true
};

var openImpliesClose = {
    tr: { tr: true, th: true, td: true },
    th: { th: true },
    td: { thead: true, td: true },
    body: { head: true, link: true, script: true },
    li: { li: true },
    p: { p: true },
    select: formTags,
    input: formTags,
    output: formTags,
    button: formTags,
    datalist: formTags,
    textarea: formTags,
    option: { option: true },
    optgroup: { optgroup: true }
};

var voidElements = {
    __proto__: null,
    area: true,
    base: true,
    basefont: true,
    br: true,
    col: true,
    command: true,
    embed: true,
    frame: true,
    hr: true,
    img: true,
    input: true,
    isindex: true,
    keygen: true,
    link: true,
    meta: true,
    param: true,
    source: true,
    track: true,
    wbr: true
};

var re_nameEnd = /\s|\//;

function Parser(cbs, options) {
    this._options = options || {};
    this._cbs = cbs || {};

    this._tagname = "";
    this._attribname = "";
    this._attribvalue = "";
    this._attribs = null;
    this._stack = [];
    this._done = false;

    this.startIndex = 0;
    this.endIndex = null;

    this._tokenizer = new Tokenizer(options, this);
}

require("util").inherits(Parser, require("events").EventEmitter);

Parser.prototype._updatePosition = function(initialOffset) {
    if (this.endIndex === null) {
        this.startIndex =
            this._tokenizer._sectionStart <= initialOffset
                ? 0
                : this._tokenizer._sectionStart - initialOffset;
    }
    this.startIndex = this.endIndex + 1;
    this.endIndex = this._tokenizer._index;
};

//Tokenizer event handlers
Parser.prototype.ontext = function(data) {
    this._updatePosition(1);
    this.endIndex--;

    if (this._cbs.ontext) this._cbs.ontext(data);
};

Parser.prototype.onopentagname = function(name) {
    if (
        !(this._options.xmlMode || "lowerCaseTags" in this._options) ||
        this._options.lowerCaseTags
    ) {
        name = name.toLowerCase();
    }

    this._tagname = name;

    if (!this._options.xmlMode && name in openImpliesClose) {
        for (
            var el;
            (el = this._stack[this._stack.length - 1]) in
            openImpliesClose[name];
            this.onclosetag(el)
        );
    }

    if (this._options.xmlMode || !(name in voidElements)) {
        this._stack.push(name);
    }

    if (this._cbs.onopentagname) this._cbs.onopentagname(name);
    if (this._cbs.onopentag) this._attribs = {};
};

Parser.prototype.onopentagend = function() {
    this._updatePosition(1);

    if (this._attribs) {
        if (this._cbs.onopentag)
            this._cbs.onopentag(this._tagname, this._attribs);
        this._attribs = null;
    }

    if (
        !this._options.xmlMode &&
        this._cbs.onclosetag &&
        this._tagname in voidElements
    ) {
        this._cbs.onclosetag(this._tagname);
    }

    this._tagname = "";
};

Parser.prototype.onclosetag = function(name) {
    this._updatePosition(1);

    if (
        !(this._options.xmlMode || "lowerCaseTags" in this._options) ||
        this._options.lowerCaseTags
    ) {
        name = name.toLowerCase();
    }
    if (
        this._stack.length &&
        (!(name in voidElements) || this._options.xmlMode)
    ) {
        var pos = this._stack.lastIndexOf(name);
        if (pos !== -1) {
            if (this._cbs.onclosetag) {
                pos = this._stack.length - pos;
                while (pos--) this._cbs.onclosetag(this._stack.pop());
            } else this._stack.splice(pos);
        } else if (name === "p" && !this._options.xmlMode) {
            this.onopentagname(name);
            this.onselfclosingtag();
        }
    } else if (!this._options.xmlMode && (name === "br" || name === "p")) {
        this.onopentagname(name);
        this.onselfclosingtag();
    }
};

Parser.prototype.onselfclosingtag = function() {
    var name = this._tagname;

    this.onopentagend();

    //self-closing tags will be on the top of the stack
    //(cheaper check than in onclosetag)
    if (this._stack[this._stack.length - 1] === name) {
        if (this._cbs.onclosetag) {
            this._cbs.onclosetag(name);
        }
        this._stack.pop();
    }
};

Parser.prototype.onattribname = function(name) {
    if (
        !(
            this._options.xmlMode || "lowerCaseAttributeNames" in this._options
        ) ||
        this._options.lowerCaseAttributeNames
    ) {
        name = name.toLowerCase();
    }
    this._attribname = name;
};

Parser.prototype.onattribdata = function(value) {
    this._attribvalue += value;
};

Parser.prototype.onattribend = function() {
    if (this._cbs.onattribute)
        this._cbs.onattribute(this._attribname, this._attribvalue);
    if (
        this._attribs &&
        !Object.prototype.hasOwnProperty.call(this._attribs, this._attribname)
    ) {
        this._attribs[this._attribname] = this._attribvalue;
    }
    this._attribname = "";
    this._attribvalue = "";
};

Parser.prototype.ondeclaration = function(value) {
    if (this._cbs.onprocessinginstruction) {
        var idx = value.search(re_nameEnd),
            name = idx < 0 ? value : value.substr(0, idx);

        if (
            !(this._options.xmlMode || "lowerCaseTags" in this._options) ||
            this._options.lowerCaseTags
        ) {
            name = name.toLowerCase();
        }
        this._cbs.onprocessinginstruction("!" + name, "!" + value);
    }
};

Parser.prototype.onprocessinginstruction = function(value) {
    if (this._cbs.onprocessinginstruction) {
        var idx = value.search(re_nameEnd),
            name = idx < 0 ? value : value.substr(0, idx);

        if (
            !(this._options.xmlMode || "lowerCaseTags" in this._options) ||
            this._options.lowerCaseTags
        ) {
            name = name.toLowerCase();
        }
        this._cbs.onprocessinginstruction("?" + name, "?" + value);
    }
};

Parser.prototype.oncomment = function(value) {
    this._updatePosition(4);

    if (this._cbs.oncomment) this._cbs.oncomment(value);
    if (this._cbs.oncommentend) this._cbs.oncommentend();
};

Parser.prototype.oncdata = function(value) {
    this._updatePosition(1);

    if (this._options.xmlMode) {
        if (this._cbs.oncdatastart) this._cbs.oncdatastart();
        if (this._cbs.ontext) this._cbs.ontext(value);
        if (this._cbs.oncdataend) this._cbs.oncdataend();
    } else {
        this.oncomment("[CDATA[" + value + "]]");
    }
};

Parser.prototype.onerror = function(err) {
    if (this._cbs.onerror) this._cbs.onerror(err);
};

Parser.prototype.onend = function() {
    if (this._cbs.onclosetag) {
        for (
            var i = this._stack.length;
            i > 0;
            this._cbs.onclosetag(this._stack[--i])
        );
    }
    if (this._cbs.onend) this._cbs.onend();
};

//Resets the parser to a blank state, ready to parse a new HTML document
Parser.prototype.reset = function() {
    if (this._cbs.onreset) this._cbs.onreset();
    this._tokenizer.reset();

    this._tagname = "";
    this._attribname = "";
    this._attribs = null;
    this._stack = [];
    this._done = false;
};

//Parses a complete HTML document and pushes it to the handler
Parser.prototype.parseComplete = function(data) {
    this.reset();
    this.end(data);
};

Parser.prototype.write = function(chunk) {
    if (this._done) this.onerror(Error(".write() after done!"));
    this._tokenizer.write(chunk);
};

Parser.prototype.end = function(chunk) {
    if (this._done) this.onerror(Error(".end() after done!"));
    this._tokenizer.end(chunk);
    this._done = true;
};

//alias for backwards compat
Parser.prototype.parseChunk = Parser.prototype.write;
Parser.prototype.done = Parser.prototype.end;

module.exports = Parser;
<|endoftext|>


<commit_before>var Tokenizer = require("./Tokenizer.js");

/*
	Options:

	xmlMode: Disables the special behavior for script/style tags (false by default)
	lowerCaseAttributeNames: call .toLowerCase for each attribute name (true if xmlMode is `false`)
	lowerCaseTags: call .toLowerCase for each tag name (true if xmlMode is `false`)
*/

/*
	Callbacks:

	oncdataend,
	oncdatastart,
	onclosetag,
	oncomment,
	oncommentend,
	onerror,
	onopentag,
	onprocessinginstruction,
	onreset,
	ontext
*/

var formTags = {
	input: true,
	option: true,
	optgroup: true,
	select: true,
	button: true,
	datalist: true,
	textarea: true
};

var openImpliesClose = {
	tr      : { tr:true, th:true, td:true },
	th      : { th:true },
	td      : { thead:true, th:true, td:true },
	body    : { head:true, link:true, script:true },
	li      : { li:true },
	p       : { p:true },
	h1      : { p:true },
	h2      : { p:true },
	h3      : { p:true },
	h4      : { p:true },
	h5      : { p:true },
	h6      : { p:true },
	select  : formTags,
	input   : formTags,
	output  : formTags,
	button  : formTags,
	datalist: formTags,
	textarea: formTags,
	option  : { option:true },
	optgroup: { optgroup:true }
};

var voidElements = {
	__proto__: null,
	area: true,
	base: true,
	basefont: true,
	br: true,
	col: true,
	command: true,
	embed: true,
	frame: true,
	hr: true,
	img: true,
	input: true,
	isindex: true,
	keygen: true,
	link: true,
	meta: true,
	param: true,
	source: true,
	track: true,
	wbr: true,

	//common self closing svg elements
	path: true,
	circle: true,
	ellipse: true,
	line: true,
	rect: true,
	use: true,
	stop: true,
	polyline: true,
	polygon: true
};

var re_nameEnd = /\s|\//;

function Parser(cbs, options){
	this._options = options || {};
	this._cbs = cbs || {};

	this._tagname = "";
	this._attribname = "";
	this._attribvalue = "";
	this._attribs = null;
	this._stack = [];

	this.startIndex = 0;
	this.endIndex = null;

	this._lowerCaseTagNames = "lowerCaseTags" in this._options ?
									!!this._options.lowerCaseTags :
									!this._options.xmlMode;
	this._lowerCaseAttributeNames = "lowerCaseAttributeNames" in this._options ?
									!!this._options.lowerCaseAttributeNames :
									!this._options.xmlMode;
	this._options.Tokenizer && (Tokenizer = this._options.Tokenizer);
	this._tokenizer = new Tokenizer(this._options, this);

	if(this._cbs.onparserinit) this._cbs.onparserinit(this);
}

require("util").inherits(Parser, require("events").EventEmitter);

Parser.prototype._updatePosition = function(initialOffset){
	if(this.endIndex === null){
		if(this._tokenizer._sectionStart <= initialOffset){
			this.startIndex = 0;
		} else {
			this.startIndex = this._tokenizer._sectionStart - initialOffset;
		}
	}
	else this.startIndex = this.endIndex + 1;
	this.endIndex = this._tokenizer.getAbsoluteIndex();
};

//Tokenizer event handlers
Parser.prototype.ontext = function(data){
	this._updatePosition(1);
	this.endIndex--;

	if(this._cbs.ontext) this._cbs.ontext(data);
};

Parser.prototype.onopentagname = function(name){
	if(this._lowerCaseTagNames){
		name = name.toLowerCase();
	}

	this._tagname = name;

	if(!this._options.xmlMode && name in openImpliesClose) {
		for(
			var el;
			(el = this._stack[this._stack.length - 1]) in openImpliesClose[name];
			this.onclosetag(el)
		);
	}

	if(this._options.xmlMode || !(name in voidElements)){
		this._stack.push(name);
	}

	if(this._cbs.onopentagname) this._cbs.onopentagname(name);
	if(this._cbs.onopentag) this._attribs = {};
};

Parser.prototype.onopentagend = function(){
	this._updatePosition(1);

	if(this._attribs){
		if(this._cbs.onopentag) this._cbs.onopentag(this._tagname, this._attribs);
		this._attribs = null;
	}

	if(!this._options.xmlMode && this._cbs.onclosetag && this._tagname in voidElements){
		this._cbs.onclosetag(this._tagname);
	}

	this._tagname = "";
};

Parser.prototype.onclosetag = function(name){
	this._updatePosition(1);

	if(this._lowerCaseTagNames){
		name = name.toLowerCase();
	}

	if(this._stack.length && (!(name in voidElements) || this._options.xmlMode)){
		var pos = this._stack.lastIndexOf(name);
		if(pos !== -1){
			if(this._cbs.onclosetag){
				pos = this._stack.length - pos;
				while(pos--) this._cbs.onclosetag(this._stack.pop());
			}
			else this._stack.length = pos;
		} else if(name === "p" && !this._options.xmlMode){
			this.onopentagname(name);
			this._closeCurrentTag();
		}
	} else if(!this._options.xmlMode && (name === "br" || name === "p")){
		this.onopentagname(name);
		this._closeCurrentTag();
	}
};

Parser.prototype.onselfclosingtag = function(){
	if(this._options.xmlMode || this._options.recognizeSelfClosing){
		this._closeCurrentTag();
	} else {
		this.onopentagend();
	}
};

Parser.prototype._closeCurrentTag = function(){
	var name = this._tagname;

	this.onopentagend();

	//self-closing tags will be on the top of the stack
	//(cheaper check than in onclosetag)
	if(this._stack[this._stack.length - 1] === name){
		if(this._cbs.onclosetag){
			this._cbs.onclosetag(name);
		}
		this._stack.pop();
	}
};

Parser.prototype.onattribname = function(name){
	if(this._lowerCaseAttributeNames){
		name = name.toLowerCase();
	}
	this._attribname = name;
};

Parser.prototype.onattribdata = function(value){
	this._attribvalue += value;
};

Parser.prototype.onattribend = function(){
	if(this._cbs.onattribute) this._cbs.onattribute(this._attribname, this._attribvalue);
	if(
		this._attribs &&
		!Object.prototype.hasOwnProperty.call(this._attribs, this._attribname)
	){
		this._attribs[this._attribname] = this._attribvalue;
	}
	this._attribname = "";
	this._attribvalue = "";
};

Parser.prototype._getInstructionName = function(value){
	var idx = value.search(re_nameEnd),
	    name = idx < 0 ? value : value.substr(0, idx);

	if(this._lowerCaseTagNames){
		name = name.toLowerCase();
	}

	return name;
};

Parser.prototype.ondeclaration = function(value){
	if(this._cbs.onprocessinginstruction){
		var name = this._getInstructionName(value);
		this._cbs.onprocessinginstruction("!" + name, "!" + value);
	}
};

Parser.prototype.onprocessinginstruction = function(value){
	if(this._cbs.onprocessinginstruction){
		var name = this._getInstructionName(value);
		this._cbs.onprocessinginstruction("?" + name, "?" + value);
	}
};

Parser.prototype.oncomment = function(value){
	this._updatePosition(4);

	if(this._cbs.oncomment) this._cbs.oncomment(value);
	if(this._cbs.oncommentend) this._cbs.oncommentend();
};

Parser.prototype.oncdata = function(value){
	this._updatePosition(1);

	if(this._options.xmlMode || this._options.recognizeCDATA){
		if(this._cbs.oncdatastart) this._cbs.oncdatastart();
		if(this._cbs.ontext) this._cbs.ontext(value);
		if(this._cbs.oncdataend) this._cbs.oncdataend();
	} else {
		this.oncomment("[CDATA[" + value + "]]");
	}
};

Parser.prototype.onerror = function(err){
	if(this._cbs.onerror) this._cbs.onerror(err);
};

Parser.prototype.onend = function(){
	if(this._cbs.onclosetag){
		for(
			var i = this._stack.length;
			i > 0;
			this._cbs.onclosetag(this._stack[--i])
		);
	}
	if(this._cbs.onend) this._cbs.onend();
};

//Resets the parser to a blank state, ready to parse a new HTML document
Parser.prototype.reset = function(){
	if(this._cbs.onreset) this._cbs.onreset();
	this._tokenizer.reset();

	this._tagname = "";
	this._attribname = "";
	this._attribs = null;
	this._stack = [];

	if(this._cbs.onparserinit) this._cbs.onparserinit(this);
};

//Parses a complete HTML document and pushes it to the handler
Parser.prototype.parseComplete = function(data){
	this.reset();
	this.end(data);
};

Parser.prototype.write = function(chunk){
	this._tokenizer.write(chunk);
};

Parser.prototype.end = function(chunk){
	this._tokenizer.end(chunk);
};

Parser.prototype.pause = function(){
	this._tokenizer.pause();
};

Parser.prototype.resume = function(){
	this._tokenizer.resume();
};

//alias for backwards compat
Parser.prototype.parseChunk = Parser.prototype.write;
Parser.prototype.done = Parser.prototype.end;

module.exports = Parser;
<commit_msg>Update Parser.js<commit_after>var Tokenizer = require("./Tokenizer.js");

/*
	Options:

	xmlMode: Disables the special behavior for script/style tags (false by default)
	lowerCaseAttributeNames: call .toLowerCase for each attribute name (true if xmlMode is `false`)
	lowerCaseTags: call .toLowerCase for each tag name (true if xmlMode is `false`)
*/

/*
	Callbacks:

	oncdataend,
	oncdatastart,
	onclosetag,
	oncomment,
	oncommentend,
	onerror,
	onopentag,
	onprocessinginstruction,
	onreset,
	ontext
*/

var formTags = {
	input: true,
	option: true,
	optgroup: true,
	select: true,
	button: true,
	datalist: true,
	textarea: true
};

var openImpliesClose = {
	tr      : { tr:true, th:true, td:true },
	th      : { th:true },
	td      : { thead:true, th:true, td:true },
	body    : { head:true, link:true, script:true },
	li      : { li:true },
	p       : { p:true },
	h1      : { p:true },
	h2      : { p:true },
	h3      : { p:true },
	h4      : { p:true },
	h5      : { p:true },
	h6      : { p:true },
	select  : formTags,
	input   : formTags,
	output  : formTags,
	button  : formTags,
	datalist: formTags,
	textarea: formTags,
	option  : { option:true },
	optgroup: { optgroup:true }
};

var voidElements = {
	__proto__: null,
	area: true,
	base: true,
	basefont: true,
	br: true,
	col: true,
	command: true,
	embed: true,
	frame: true,
	hr: true,
	img: true,
	input: true,
	isindex: true,
	keygen: true,
	link: true,
	meta: true,
	param: true,
	source: true,
	track: true,
	wbr: true,

	//common self closing svg elements
	path: true,
	circle: true,
	ellipse: true,
	line: true,
	rect: true,
	use: true,
	stop: true,
	polyline: true,
	polygon: true
};

var re_nameEnd = /\s|\//;

function Parser(cbs, options){
	this._options = options || {};
	this._cbs = cbs || {};

	this._tagname = "";
	this._attribname = "";
	this._attribvalue = "";
	this._attribs = null;
	this._stack = [];

	this.startIndex = 0;
	this.endIndex = null;

	this._lowerCaseTagNames = "lowerCaseTags" in this._options ?
									!!this._options.lowerCaseTags :
									!this._options.xmlMode;
	this._lowerCaseAttributeNames = "lowerCaseAttributeNames" in this._options ?
									!!this._options.lowerCaseAttributeNames :
									!this._options.xmlMode;
	if(!!this._options.Tokenizer){
		Tokenizer = this._options.Tokenizer;
	}
	this._tokenizer = new Tokenizer(this._options, this);

	if(this._cbs.onparserinit) this._cbs.onparserinit(this);
}

require("util").inherits(Parser, require("events").EventEmitter);

Parser.prototype._updatePosition = function(initialOffset){
	if(this.endIndex === null){
		if(this._tokenizer._sectionStart <= initialOffset){
			this.startIndex = 0;
		} else {
			this.startIndex = this._tokenizer._sectionStart - initialOffset;
		}
	}
	else this.startIndex = this.endIndex + 1;
	this.endIndex = this._tokenizer.getAbsoluteIndex();
};

//Tokenizer event handlers
Parser.prototype.ontext = function(data){
	this._updatePosition(1);
	this.endIndex--;

	if(this._cbs.ontext) this._cbs.ontext(data);
};

Parser.prototype.onopentagname = function(name){
	if(this._lowerCaseTagNames){
		name = name.toLowerCase();
	}

	this._tagname = name;

	if(!this._options.xmlMode && name in openImpliesClose) {
		for(
			var el;
			(el = this._stack[this._stack.length - 1]) in openImpliesClose[name];
			this.onclosetag(el)
		);
	}

	if(this._options.xmlMode || !(name in voidElements)){
		this._stack.push(name);
	}

	if(this._cbs.onopentagname) this._cbs.onopentagname(name);
	if(this._cbs.onopentag) this._attribs = {};
};

Parser.prototype.onopentagend = function(){
	this._updatePosition(1);

	if(this._attribs){
		if(this._cbs.onopentag) this._cbs.onopentag(this._tagname, this._attribs);
		this._attribs = null;
	}

	if(!this._options.xmlMode && this._cbs.onclosetag && this._tagname in voidElements){
		this._cbs.onclosetag(this._tagname);
	}

	this._tagname = "";
};

Parser.prototype.onclosetag = function(name){
	this._updatePosition(1);

	if(this._lowerCaseTagNames){
		name = name.toLowerCase();
	}

	if(this._stack.length && (!(name in voidElements) || this._options.xmlMode)){
		var pos = this._stack.lastIndexOf(name);
		if(pos !== -1){
			if(this._cbs.onclosetag){
				pos = this._stack.length - pos;
				while(pos--) this._cbs.onclosetag(this._stack.pop());
			}
			else this._stack.length = pos;
		} else if(name === "p" && !this._options.xmlMode){
			this.onopentagname(name);
			this._closeCurrentTag();
		}
	} else if(!this._options.xmlMode && (name === "br" || name === "p")){
		this.onopentagname(name);
		this._closeCurrentTag();
	}
};

Parser.prototype.onselfclosingtag = function(){
	if(this._options.xmlMode || this._options.recognizeSelfClosing){
		this._closeCurrentTag();
	} else {
		this.onopentagend();
	}
};

Parser.prototype._closeCurrentTag = function(){
	var name = this._tagname;

	this.onopentagend();

	//self-closing tags will be on the top of the stack
	//(cheaper check than in onclosetag)
	if(this._stack[this._stack.length - 1] === name){
		if(this._cbs.onclosetag){
			this._cbs.onclosetag(name);
		}
		this._stack.pop();
	}
};

Parser.prototype.onattribname = function(name){
	if(this._lowerCaseAttributeNames){
		name = name.toLowerCase();
	}
	this._attribname = name;
};

Parser.prototype.onattribdata = function(value){
	this._attribvalue += value;
};

Parser.prototype.onattribend = function(){
	if(this._cbs.onattribute) this._cbs.onattribute(this._attribname, this._attribvalue);
	if(
		this._attribs &&
		!Object.prototype.hasOwnProperty.call(this._attribs, this._attribname)
	){
		this._attribs[this._attribname] = this._attribvalue;
	}
	this._attribname = "";
	this._attribvalue = "";
};

Parser.prototype._getInstructionName = function(value){
	var idx = value.search(re_nameEnd),
	    name = idx < 0 ? value : value.substr(0, idx);

	if(this._lowerCaseTagNames){
		name = name.toLowerCase();
	}

	return name;
};

Parser.prototype.ondeclaration = function(value){
	if(this._cbs.onprocessinginstruction){
		var name = this._getInstructionName(value);
		this._cbs.onprocessinginstruction("!" + name, "!" + value);
	}
};

Parser.prototype.onprocessinginstruction = function(value){
	if(this._cbs.onprocessinginstruction){
		var name = this._getInstructionName(value);
		this._cbs.onprocessinginstruction("?" + name, "?" + value);
	}
};

Parser.prototype.oncomment = function(value){
	this._updatePosition(4);

	if(this._cbs.oncomment) this._cbs.oncomment(value);
	if(this._cbs.oncommentend) this._cbs.oncommentend();
};

Parser.prototype.oncdata = function(value){
	this._updatePosition(1);

	if(this._options.xmlMode || this._options.recognizeCDATA){
		if(this._cbs.oncdatastart) this._cbs.oncdatastart();
		if(this._cbs.ontext) this._cbs.ontext(value);
		if(this._cbs.oncdataend) this._cbs.oncdataend();
	} else {
		this.oncomment("[CDATA[" + value + "]]");
	}
};

Parser.prototype.onerror = function(err){
	if(this._cbs.onerror) this._cbs.onerror(err);
};

Parser.prototype.onend = function(){
	if(this._cbs.onclosetag){
		for(
			var i = this._stack.length;
			i > 0;
			this._cbs.onclosetag(this._stack[--i])
		);
	}
	if(this._cbs.onend) this._cbs.onend();
};

//Resets the parser to a blank state, ready to parse a new HTML document
Parser.prototype.reset = function(){
	if(this._cbs.onreset) this._cbs.onreset();
	this._tokenizer.reset();

	this._tagname = "";
	this._attribname = "";
	this._attribs = null;
	this._stack = [];

	if(this._cbs.onparserinit) this._cbs.onparserinit(this);
};

//Parses a complete HTML document and pushes it to the handler
Parser.prototype.parseComplete = function(data){
	this.reset();
	this.end(data);
};

Parser.prototype.write = function(chunk){
	this._tokenizer.write(chunk);
};

Parser.prototype.end = function(chunk){
	this._tokenizer.end(chunk);
};

Parser.prototype.pause = function(){
	this._tokenizer.pause();
};

Parser.prototype.resume = function(){
	this._tokenizer.resume();
};

//alias for backwards compat
Parser.prototype.parseChunk = Parser.prototype.write;
Parser.prototype.done = Parser.prototype.end;

module.exports = Parser;
<|endoftext|>


<commit_before>
const Readline = require('@serialport/parser-readline')
const SerialPort = require('serialport'),
  sys = require('sys'),
  https = require('https');

function handleSerialData(data) {
  console.log('serial incoming -> ', data);
}

function handleSerialError(err) {
  console.error('error on serial connection: ', err);
}

/*
  servometer host application
  This nodeJS application handles requesting metrics data from an API
  and sending it over serial USB to the arduino for servo action

  @author Jacob Quatier
*/
function ServoMeter(options) {
  if (!options) {
    throw new Error("ServoMeter options was empty. configure API endpoint and serial port");
  }

  this._serialPort = options.serialPort;
  this._httpOptions = options.httpOptions;
  this.formatSerialData = options.formatSerialData;
}

ServoMeter.prototype.start = function() {
  var self = this;
  console.log('starting ServoMeter');

  const port = new SerialPort(self._serialPort);
  port.pipe(new Readline({ delimiter: '\n' }))
  function refreshMetrics(){
    self.refreshMetrics(port);
  }

  port.on('open', function() {
    console.log('serial connection opened on ' + self._serialPort);

    // wait 1 second to make sure serial is established
    setTimeout(refreshMetrics, 1000)

    // refresh every 10 seconds
    setInterval(refreshMetrics, 10000)
  });

  // output any data sent back FROM the arduino (for confirmation)
  port.on('data', handleSerialData);

  port.on('error', handleSerialError);
};

ServoMeter.prototype.refreshMetrics = function(port) {
  var self = this;
  // call metrics api using the configured http options object
  var req = https.request(self._httpOptions, function(res) {
    console.log('recieved API status code: ', res.statusCode, ' -- ', res.headers.date);
    var response = '';

    // buffer the response until it ends
    res.on('data', function(data) {
      response += data;
    });

    res.on('end', function() {
      // format & write out the string
      port.write(self.formatSerialData(response));
    });
  });

  req.on('error', function(e) {
    console.error('Error requesting metrics: ', e);
  });

  req.end();
}

module.exports = ServoMeter;
<commit_msg>taking out unused lib<commit_after>
const Readline = require('@serialport/parser-readline')
const SerialPort = require('serialport'),
  https = require('https');

function handleSerialData(data) {
  console.log('serial incoming -> ', data);
}

function handleSerialError(err) {
  console.error('error on serial connection: ', err);
}

/*
  servometer host application
  This nodeJS application handles requesting metrics data from an API
  and sending it over serial USB to the arduino for servo action

  @author Jacob Quatier
*/
function ServoMeter(options) {
  if (!options) {
    throw new Error("ServoMeter options was empty. configure API endpoint and serial port");
  }

  this._serialPort = options.serialPort;
  this._httpOptions = options.httpOptions;
  this.formatSerialData = options.formatSerialData;
}

ServoMeter.prototype.start = function() {
  var self = this;
  console.log('starting ServoMeter');

  const port = new SerialPort(self._serialPort);
  port.pipe(new Readline({ delimiter: '\n' }))
  function refreshMetrics(){
    self.refreshMetrics(port);
  }

  port.on('open', function() {
    console.log('serial connection opened on ' + self._serialPort);

    // wait 1 second to make sure serial is established
    setTimeout(refreshMetrics, 1000)

    // refresh every 10 seconds
    setInterval(refreshMetrics, 10000)
  });

  // output any data sent back FROM the arduino (for confirmation)
  port.on('data', handleSerialData);

  port.on('error', handleSerialError);
};

ServoMeter.prototype.refreshMetrics = function(port) {
  var self = this;
  // call metrics api using the configured http options object
  var req = https.request(self._httpOptions, function(res) {
    console.log('recieved API status code: ', res.statusCode, ' -- ', res.headers.date);
    var response = '';

    // buffer the response until it ends
    res.on('data', function(data) {
      response += data;
    });

    res.on('end', function() {
      // format & write out the string
      port.write(self.formatSerialData(response));
    });
  });

  req.on('error', function(e) {
    console.error('Error requesting metrics: ', e);
  });

  req.end();
}

module.exports = ServoMeter;
<|endoftext|>


<commit_before>"use strict";

/**
 * The viewport object is used to handle virtual visualization windows for the WebGL renderer.
 *
 * It uses normalized coordinates [0 to 1] when using RELATIVE mode or pixel based coordinated for ABSOLUTE mode.
 *
 * @class Viewport
 * @param {Number} mode
 */
function Viewport(mode)
{
	/**
	 * Camera viewport offset.
	 *
	 * Values range from 0.0 to 1.0 in screen space when in RELATIVE mode.
	 *
	 * @property offset
	 * @type {THREE.Vector2}
	*/
	this.offset = new THREE.Vector2(0.0, 0.0);

	/**
	 * Camera viewport size.
	 *
	 * Values range from 0.0 to 1.0 in screen space when in RELATIVE mode.
	 *
	 * @property size
	 * @type {THREE.Vector2}
	 */
	this.size = new THREE.Vector2(1.0, 1.0);

	/**
	 * Viewport sizing mode.
	 *
	 * Can be RELATIVE or ABSOLUTE.
	 *
	 * @property mode
	 * @type {Number}
	 */
	this.mode = mode !== undefined ? mode : Viewport.RELATIVE;

	/**
	 * Positioning anchor of the viewport.
	 *
	 * @property anchor
	 * @type {Number}
	 */
	this.anchor = Viewport.TOP_LEFT;

	/**
	 * Calculated absolute viewport value.
	 *
	 * It is calculated using the update() method that should be called after applying changes.
	 *
	 * @property viewport
	 * @type {THREE.Vector4}
	 */
	this.viewport = new THREE.Vector4();
}

/**
 * Viewport calculated relatively to the screen viewport.
 *
 * @static
 * @attribute RELATIVE
 */
Viewport.RELATIVE = 200;

/**
 * Viewport defined absolutely in pixels.
 *
 * @static
 * @attribute ABSOLUTE
 */
Viewport.ABSOLUTE = 201;

Viewport.TOP_LEFT = 301;
Viewport.TOP_RIGHT = 302;
Viewport.BOTTOM_LEFT = 303;
Viewport.BOTTOM_RIGHT = 304;

/**
 * Update the viewport box from the values.
 *
 * Has to be called after applying changes to the viewport.
 *
 * @method update
 */
Viewport.prototype.update = function()
{
	//TODO <ADD CODE HERE>
};

/**
 * Get the aspect ratio of this viewport x/y.
 *
 * @method getAspectRatio
 * @param {Canvas} canvas DOM canvas only required to calculate aspect ration on relative sizing.
 * @return {Number} The aspect ratio of the viewport.
 */
Viewport.prototype.getAspectRatio = function()
{
	return this.size.x / this.size.y;
};

/**
 * Check if the mouse is inside this viewport.
 *
 * @method isInside
 * @param {DOM} canvas Canvas for offset calculation.
 * @param {Mouse} mouse Mouse object with coordinates inside of the canvas.
 */
Viewport.prototype.isInside = function(canvas, mouse)
{
	var width = canvas.width;
	var height = canvas.height;

	if(this.mode === Viewport.RELATIVE)
	{
		var offset = new THREE.Vector2(this.offset.x * width, this.offset.y * height);
		var viewport = new THREE.Vector2(this.size.x * width, this.size.y * height);
	}
	else //if(this.mode === Viewport.ABSOLUTE)
	{
		var offset = this.offset;
		var viewport = this.size;
	}

	if(this.anchor === Viewport.TOP_LEFT)
	{
		return mouse.position.x > offset.x &&
		mouse.position.x < offset.x + viewport.x &&
		mouse.position.y > offset.y &&
		mouse.position.y < offset.y + viewport.y;
	}
	else if(this.anchor === Viewport.TOP_RIGHT)
	{
		return mouse.position.x > width - viewport.x - offset.x &&
		mouse.position.x < width - offset.x &&
		mouse.position.y > offset.y &&
		mouse.position.y < offset.y + viewport.y;
	}
	else if(this.anchor === Viewport.BOTTOM_LEFT)
	{
		return mouse.position.x > offset.x &&
		mouse.position.x < offset.x + viewport.x &&
		mouse.position.y > height - offset.y - viewport.y &&
		mouse.position.y < height - offset.y;
	}
	else //if(this.anchor === Viewport.BOTTOM_RIGHT)
	{
		return mouse.position.x > width - viewport.x - offset.x &&
		mouse.position.x < width - offset.x &&
		mouse.position.y > height - offset.y - viewport.y &&
		mouse.position.y < height - offset.y;
	}
};

/**
 * Get normalized coordinates between [-1, 1] for a canvas size and mouse position.
 *
 * Usefull to use raycasting for object picking in a viewport.
 *
 * @method getNormalized
 * @param {DOM} canvas Canvas for offset calculation.
 * @param {Mouse} mouse Mouse object with coordinates inside of the canvas.
 * @return {THREE.Vector2} Normalized coordinated of the mouse.
 */
Viewport.prototype.getNormalized = function()
{
	var normalized = new THREE.Vector2();

	return function(canvas, mouse)
	{
		var width = canvas.width;
		var height = canvas.height;

		if(this.mode === Viewport.RELATIVE)
		{
			var offset = new THREE.Vector2(this.offset.x * width, this.offset.y * height);
			var viewport = new THREE.Vector2(this.size.x * width, this.size.y * height);
		}
		else //if(this.mode === Viewport.ABSOLUTE)
		{
			var offset = this.offset;
			var viewport = this.size;
		}

		if(this.anchor === Viewport.TOP_LEFT)
		{
			var x = mouse.position.x - viewport.x - offset.x;
			var y = mouse.position.y - offset.y;
			normalized.set((x / viewport.x) * 2 + 1, (-y / viewport.y) * 2 + 1);
		}
		else if(this.anchor === Viewport.TOP_RIGHT)
		{
			var x = width - mouse.position.x - viewport.x - offset.x;
			var y = mouse.position.y - offset.y;
			normalized.set((-x / viewport.x) * 2 - 1, (-y / viewport.y) * 2 + 1);
		}
		else if(this.anchor === Viewport.BOTTOM_LEFT)
		{
			var x = mouse.position.x - viewport.x - offset.x;
			var y = height - mouse.position.y - offset.y;
			normalized.set((x / viewport.x) * 2 + 1, (y / viewport.y) * 2 - 1);
		}
		else //if(this.anchor === Viewport.BOTTOM_RIGHT)
		{
			var x = width - mouse.position.x - viewport.x - offset.x;
			var y = height - mouse.position.y - offset.y;
			normalized.set((-x / viewport.x) * 2 - 1, (y / viewport.y) * 2 - 1);
		}

		return normalized;
	};
}();

/**
 * Enable this viewport for rendering using a WebGLRenderer
 *
 * After rendering the WebGL renderer has to manually reset to the original values.
 *
 * @method enable
 * @param {THREE.WebGLRenderer} renderer
 */
Viewport.prototype.enable = function(renderer)
{
	var width = renderer.domElement.width;
	var height = renderer.domElement.height;

	if(this.mode === Viewport.RELATIVE)
	{
		var offset = new THREE.Vector2(this.offset.x * width, this.offset.y * height);
		var viewport = new THREE.Vector2(this.size.x * width, this.size.y * height);
	}
	else //if(this.mode === Viewport.ABSOLUTE)
	{
		var offset = this.offset;
		var viewport = this.size;
	}

	if(this.anchor === Viewport.BOTTOM_LEFT)
	{
		renderer.setViewport(offset.x, offset.y, viewport.x, viewport.y);
		renderer.setScissor(offset.x, offset.y, viewport.x, viewport.y);
	}
	else if(this.anchor === Viewport.BOTTOM_RIGHT)
	{
		var x = width - viewport.x - offset.x;
		renderer.setViewport(x, offset.y, viewport.x, viewport.y);
		renderer.setScissor(x, offset.y, viewport.x, viewport.y);
	}
	else if(this.anchor === Viewport.TOP_LEFT)
	{
		var y = height - viewport.y - offset.y;
		renderer.setViewport(offset.x, y, viewport.x, viewport.y);
		renderer.setScissor(offset.x, y, viewport.x, viewport.y);
	}
	else //if(this.anchor === Viewport.TOP_RIGHT)
	{
		var x = width - viewport.x - offset.x;
		var y = height - viewport.y - offset.y;
		renderer.setViewport(x, y, viewport.x, viewport.y);
		renderer.setScissor(x, y, viewport.x, viewport.y);
	}
};

/**
 * Serializer viewport data to JSON.
 *
 * @method toJSON
 * @return {Object} Serialized viewport object.
 */
Viewport.prototype.toJSON = function()
{
	var data =
	{
		offset: this.offset.toArray(),
		size: this.size.toArray(),
		mode: this.mode,
		anchor: this.anchor
	};

	return data;
};

/**
 * Fill viewport data from serialized JSON data.
 *
 * @method toJSON
 * @param {Object} data Serialized viewport object.
 */
Viewport.prototype.fromJSON = function(data)
{
	this.offset.fromArray(data.offset);
	this.size.fromArray(data.size);
	this.mode = data.mode;
	this.anchor = data.anchor;
};
<commit_msg>Viewport update method<commit_after>"use strict";

/**
 * The viewport object is used to handle virtual visualization windows for the WebGL renderer.
 *
 * It uses normalized coordinates [0 to 1] when using RELATIVE mode or pixel based coordinated for ABSOLUTE mode.
 *
 * @class Viewport
 * @param {Number} mode
 */
function Viewport(mode)
{
	/**
	 * Camera viewport offset.
	 *
	 * Values range from 0.0 to 1.0 in screen space when in RELATIVE mode.
	 *
	 * @property offset
	 * @type {THREE.Vector2}
	*/
	this.offset = new THREE.Vector2(0.0, 0.0);

	/**
	 * Camera viewport size.
	 *
	 * Values range from 0.0 to 1.0 in screen space when in RELATIVE mode.
	 *
	 * @property size
	 * @type {THREE.Vector2}
	 */
	this.size = new THREE.Vector2(1.0, 1.0);

	/**
	 * Width of the final output canvas.
	 *
	 * This width should match the canvas size / rendering resolution.
	 *
	 * @property width
	 * @type {Number}
	 */
	this.width = 1;

	/**
	 * Height of the final output canvas.
	 *
	 * This height should match the canvas size / rendering resolution.
	 *
	 * @property height
	 * @type {Number}
	 */
	this.height = 1;

	/**
	 * Viewport sizing mode.
	 *
	 * Can be RELATIVE or ABSOLUTE.
	 *
	 * @property mode
	 * @type {Number}
	 */
	this.mode = mode !== undefined ? mode : Viewport.RELATIVE;

	/**
	 * Positioning anchor of the viewport.
	 *
	 * @property anchor
	 * @type {Number}
	 */
	this.anchor = Viewport.TOP_LEFT;

	/**
	 * Calculated absolute viewport value.
	 *
	 * It is calculated using the update() method that should be called after applying changes.
	 *
	 * @property viewport
	 * @type {THREE.Vector4}
	 */
	this.viewport = new THREE.Vector4();
}

/**
 * Viewport calculated relatively to the screen viewport.
 *
 * @static
 * @attribute RELATIVE
 */
Viewport.RELATIVE = 200;

/**
 * Viewport defined absolutely in pixels.
 *
 * @static
 * @attribute ABSOLUTE
 */
Viewport.ABSOLUTE = 201;

Viewport.TOP_LEFT = 301;
Viewport.TOP_RIGHT = 302;
Viewport.BOTTOM_LEFT = 303;
Viewport.BOTTOM_RIGHT = 304;

/**
 * Update the viewport box from the values.
 *
 * Has to be called after applying changes to the viewport, the viewport is resized of the
 *
 * @method update
 */
Viewport.prototype.update = function()
{
	if(this.mode === Viewport.RELATIVE)
	{
		var offset = new THREE.Vector2(this.offset.x * this.width, this.offset.y * this.height);
		var viewport = new THREE.Vector2(this.size.x * this.width, this.size.y * this.height);
	}
	else if(this.mode === Viewport.ABSOLUTE)
	{
		var offset = this.offset;
		var viewport = this.size;
	}

	if(this.anchor === Viewport.BOTTOM_LEFT)
	{
		this.viewport.set(offset.x, offset.y, viewport.x, viewport.y);
	}
	else if(this.anchor === Viewport.BOTTOM_RIGHT)
	{
		var x = this.width - viewport.x - offset.x;
		this.viewport.set(x, offset.y, viewport.x, viewport.y);
	}
	else if(this.anchor === Viewport.TOP_LEFT)
	{
		var y = this.height - viewport.y - offset.y;
		this.viewport.set(offset.x, y, viewport.x, viewport.y);
	}
	else if(this.anchor === Viewport.TOP_RIGHT)
	{
		var x = this.width - viewport.x - offset.x;
		var y = this.height - viewport.y - offset.y;
		this.viewport.set(x, y, viewport.x, viewport.y);
	}
};

/**
 * Get the aspect ratio of this viewport x/y.
 *
 * @method getAspectRatio
 * @param {Canvas} canvas DOM canvas only required to calculate aspect ration on relative sizing.
 * @return {Number} The aspect ratio of the viewport.
 */
Viewport.prototype.getAspectRatio = function()
{
	return this.size.x / this.size.y;
};

/**
 * Check if the mouse is inside this viewport.
 *
 * @method isInside
 * @param {DOM} canvas Canvas for offset calculation.
 * @param {Mouse} mouse Mouse object with coordinates inside of the canvas.
 */
Viewport.prototype.isInside = function(canvas, mouse)
{
	//TODO <REMOVE LATER>
	this.width = canvas.width;
	this.height = canvas.height;
	this.update();

	return mouse.position.x > this.viewport.x &&
	mouse.position.x < this.viewport.x + this.viewport.z &&
	mouse.position.y < this.height - this.viewport.y &&
	mouse.position.y > this.height - this.viewport.y - this.viewport.w;
};

/**
 * Get normalized coordinates between [-1, 1] for a canvas size and mouse position.
 *
 * Usefull to use raycasting for object picking in a viewport.
 *
 * @method getNormalized
 * @param {DOM} canvas Canvas for offset calculation.
 * @param {Mouse} mouse Mouse object with coordinates inside of the canvas.
 * @return {THREE.Vector2} Normalized coordinated of the mouse.
 */
Viewport.prototype.getNormalized = function()
{
	var normalized = new THREE.Vector2();

	return function(canvas, mouse)
	{
		//TODO <REMOVE LATER>
		this.width = canvas.width;
		this.height = canvas.height;
		this.update();

		var x = mouse.position.x - this.viewport.z - this.viewport.x;
		var y = mouse.position.y - (this.height - (this.viewport.y + this.viewport.w));

		normalized.set((x / this.viewport.z) * 2 + 1, (-y / this.viewport.w) * 2 + 1);

		return normalized;
	};
}();

/**
 * Enable this viewport for rendering using a WebGLRenderer
 *
 * After rendering the WebGL renderer has to manually reset to the original values.
 *
 * @method enable
 * @param {THREE.WebGLRenderer} renderer
 */
Viewport.prototype.enable = function(renderer)
{
	//TODO <REMOVE LATER>
	this.width = renderer.domElement.width;
	this.height = renderer.domElement.height;
	this.update();

	renderer.setViewport(this.viewport);
	renderer.setScissor(this.viewport);
};

/**
 * Serializer viewport data to JSON.
 *
 * @method toJSON
 * @return {Object} Serialized viewport object.
 */
Viewport.prototype.toJSON = function()
{
	var data =
	{
		offset: this.offset.toArray(),
		size: this.size.toArray(),
		mode: this.mode,
		anchor: this.anchor
	};

	return data;
};

/**
 * Fill viewport data from serialized JSON data.
 *
 * @method toJSON
 * @param {Object} data Serialized viewport object.
 */
Viewport.prototype.fromJSON = function(data)
{
	this.offset.fromArray(data.offset);
	this.size.fromArray(data.size);
	this.mode = data.mode;
	this.anchor = data.anchor;
};
<|endoftext|>


<commit_before><commit_msg>remove debugger<commit_after><|endoftext|>


<commit_before>openerp.base = function(instance) {

    instance.base.apps_remote = null;
    instance.base.apps_client = null;

    instance.base.Apps = instance.web.Widget.extend({
        template: 'EmptyComponent',
        remote_action_id: 'loempia.action_embed',
        failback_action_id: 'base.open_module_tree',

        init: function(parent, action) {
            this._super(parent, action);
            var options = action.params || {};

            if (options.apps_user) {
                sessionStorage.setItem('apps.login', options.apps_user);
            }
            if (options.apps_access_token) {
                sessionStorage.setItem('apps.access_token', options.apps_access_token);
            }

            this.params = options; // NOTE read by embedded client action
        },

        get_client: function() {
            // return the client via a deferred, resolved or rejected depending if the remote host is available or not.
            var check_client_available = function(client) {
                var d = $.Deferred();
                var i = new Image();
                i.onerror = function() {
                    d.reject(client);
                };
                i.onload = function() {
                    client.session.session_bind(client.origin).then(function() {
                        // check if client can authenticate
                        client.authenticate().then(
                           function() {     /* done */
                            d.resolve(client);
                        }, function() {     /* fail */
                            if (client.login === 'anonymous') {
                                d.reject(client);
                            } else {
                                sessionStorage.removeItem('apps.login');
                                sessionStorage.removeItem('apps.access_token');
                                client.bind_crendentials(client.dbname, 'anonymous', 'anonymous');
                                client.authenticate().then(
                                   function() {     /* done */
                                    d.resolve(client);
                                }, function() {     /* fail */
                                    d.reject(client);
                                });
                            }
                        });
                    });

                };
                i.src = _.str.sprintf('%s/web/static/src/img/sep-a.gif', client.origin);
                return d.promise();
            };
            if (instance.base.apps_client) {
                return check_client_available(instance.base.apps_client);
            } else {
                var ICP = new instance.web.Model('ir.config_parameter');
                return ICP.call('get_param', ['apps.server', 'https://apps.openerp.com/apps']).then(function(u) {
                    var link = $(_.str.sprintf('', u))[0];
                    var host = _.str.sprintf('%s//%s', link.protocol, link.host);
                    var dbname = link.pathname;
                    if (dbname[0] === '/') {
                        dbname = dbname.substr(1);
                    }
                    var login = (sessionStorage ? sessionStorage.getItem('apps.login') : null) || 'anonymous';
                    var passwd = (sessionStorage ? sessionStorage.getItem('apps.access_token') : null) || 'anonymous';
                    if (_.isNull(instance.base.apps_remote)) {
                        instance.base.apps_remote = new openerp.init();
                    }
                    var client = new instance.base.apps_remote.web.EmbeddedClient(null, host, dbname, login, passwd);
                    instance.base.apps_client = client;
                    return check_client_available(client);
                });
            }
        },

        destroy: function() {
            if (instance.base.apps_client) {
                instance.base.apps_client.destroy();
            }
            return this._super();
        },

        start: function() {
            var self = this;
            return self.get_client().
                done(function(client) {
                    client.replace(self.$el).
                        done(function() {
                            client.$el.removeClass('openerp');
                            client.do_action(self.remote_action_id);
                        });
                }).
                fail(function(client) {
                    self.do_warn('Apps Server not reachable.', 'Showing local modules.', true);
                    self.rpc('/web/action/load', {action_id: self.failback_action_id}).done(function(action) {
                        self.do_action(action);
                        instance.webclient.menu.open_action(action.id);
                    });
                });
        },
    });

    instance.base.AppsUpdates = instance.base.Apps.extend({
        remote_action_id: 'loempia.action_embed_updates'
    });

    instance.web.client_actions.add("apps", "instance.base.Apps");
    instance.web.client_actions.add("apps.updates", "instance.base.AppsUpdates");

};
<commit_msg>[FIX] apps: make warning messages translatable<commit_after>openerp.base = function(instance) {

    instance.base.apps_remote = null;
    instance.base.apps_client = null;

    var _t = instance.web._t;

    instance.base.Apps = instance.web.Widget.extend({
        template: 'EmptyComponent',
        remote_action_id: 'loempia.action_embed',
        failback_action_id: 'base.open_module_tree',

        init: function(parent, action) {
            this._super(parent, action);
            var options = action.params || {};

            if (options.apps_user) {
                sessionStorage.setItem('apps.login', options.apps_user);
            }
            if (options.apps_access_token) {
                sessionStorage.setItem('apps.access_token', options.apps_access_token);
            }

            this.params = options; // NOTE read by embedded client action
        },

        get_client: function() {
            // return the client via a deferred, resolved or rejected depending if the remote host is available or not.
            var check_client_available = function(client) {
                var d = $.Deferred();
                var i = new Image();
                i.onerror = function() {
                    d.reject(client);
                };
                i.onload = function() {
                    client.session.session_bind(client.origin).then(function() {
                        // check if client can authenticate
                        client.authenticate().then(
                           function() {     /* done */
                            d.resolve(client);
                        }, function() {     /* fail */
                            if (client.login === 'anonymous') {
                                d.reject(client);
                            } else {
                                sessionStorage.removeItem('apps.login');
                                sessionStorage.removeItem('apps.access_token');
                                client.bind_crendentials(client.dbname, 'anonymous', 'anonymous');
                                client.authenticate().then(
                                   function() {     /* done */
                                    d.resolve(client);
                                }, function() {     /* fail */
                                    d.reject(client);
                                });
                            }
                        });
                    });

                };
                i.src = _.str.sprintf('%s/web/static/src/img/sep-a.gif', client.origin);
                return d.promise();
            };
            if (instance.base.apps_client) {
                return check_client_available(instance.base.apps_client);
            } else {
                var ICP = new instance.web.Model('ir.config_parameter');
                return ICP.call('get_param', ['apps.server', 'https://apps.openerp.com/apps']).then(function(u) {
                    var link = $(_.str.sprintf('', u))[0];
                    var host = _.str.sprintf('%s//%s', link.protocol, link.host);
                    var dbname = link.pathname;
                    if (dbname[0] === '/') {
                        dbname = dbname.substr(1);
                    }
                    var login = (sessionStorage ? sessionStorage.getItem('apps.login') : null) || 'anonymous';
                    var passwd = (sessionStorage ? sessionStorage.getItem('apps.access_token') : null) || 'anonymous';
                    if (_.isNull(instance.base.apps_remote)) {
                        instance.base.apps_remote = new openerp.init();
                    }
                    var client = new instance.base.apps_remote.web.EmbeddedClient(null, host, dbname, login, passwd);
                    instance.base.apps_client = client;
                    return check_client_available(client);
                });
            }
        },

        destroy: function() {
            if (instance.base.apps_client) {
                instance.base.apps_client.destroy();
            }
            return this._super();
        },

        start: function() {
            var self = this;
            return self.get_client().
                done(function(client) {
                    client.replace(self.$el).
                        done(function() {
                            client.$el.removeClass('openerp');
                            client.do_action(self.remote_action_id);
                        });
                }).
                fail(function(client) {
                    self.do_warn(_t('OpenERP Apps Unreachable'), _t('Showing locally available modules'), true);
                    self.rpc('/web/action/load', {action_id: self.failback_action_id}).done(function(action) {
                        self.do_action(action);
                        instance.webclient.menu.open_action(action.id);
                    });
                });
        },
    });

    instance.base.AppsUpdates = instance.base.Apps.extend({
        remote_action_id: 'loempia.action_embed_updates'
    });

    instance.web.client_actions.add("apps", "instance.base.Apps");
    instance.web.client_actions.add("apps.updates", "instance.base.AppsUpdates");

};
<|endoftext|>