<commit_before>import Parchment from 'parchment';
import clone from 'clone';
import equal from 'deep-equal';
import Emitter from './emitter';
import logger from './logger';

let debug = logger('quill:selection');

class Range {
  constructor(index, length = 0) {
    this.index = index;
    this.length = length;
  }
}

class Selection {
  constructor(scroll, emitter) {
    this.emitter = emitter;
    this.scroll = scroll;
    this.composing = false;
    this.root = this.scroll.domNode;
    this.root.addEventListener('compositionstart', () => {
      this.composing = true;
    });
    this.root.addEventListener('compositionend', () => {
      this.composing = false;
    });
    this.cursor = Parchment.create('cursor', this);
    // savedRange is last non-null range
    this.lastRange = this.savedRange = new Range(0, 0);
    ['keyup', 'mouseup', 'mouseleave', 'touchend', 'touchleave', 'focus', 'blur'].forEach((eventName) => {
      this.root.addEventListener(eventName, () => {
        // When range used to be a selection and user click within the selection,
        // the range now being a cursor has not updated yet without setTimeout
        setTimeout(this.update.bind(this, Emitter.sources.USER), 100);
      });
    });
    this.emitter.on(Emitter.events.EDITOR_CHANGE, (type, delta) => {
      if (type === Emitter.events.TEXT_CHANGE && delta.length() > 0) {
        this.update(Emitter.sources.SILENT);
      }
    });
    this.emitter.on(Emitter.events.SCROLL_BEFORE_UPDATE, () => {
      if (!this.hasFocus()) return;
      let native = this.getNativeRange();
      if (native == null) return;
      if (native.start.node === this.cursor.textNode) return;  // cursor.restore() will handle
      // TODO unclear if this has negative side effects
      this.emitter.once(Emitter.events.SCROLL_UPDATE, () => {
        try {
          this.setNativeRange(native.start.node, native.start.offset, native.end.node, native.end.offset);
        } catch (ignored) {}
      });
    });
    this.update(Emitter.sources.SILENT);
  }

  focus() {
    if (this.hasFocus()) return;
    this.root.focus();
    this.setRange(this.savedRange);
  }

  format(format, value) {
    if (this.scroll.whitelist != null && !this.scroll.whitelist[format]) return;
    this.scroll.update();
    let nativeRange = this.getNativeRange();
    if (nativeRange == null || !nativeRange.native.collapsed || Parchment.query(format, Parchment.Scope.BLOCK)) return;
    if (nativeRange.start.node !== this.cursor.textNode) {
      let blot = Parchment.find(nativeRange.start.node, false);
      if (blot == null) return;
      // TODO Give blot ability to not split
      if (blot instanceof Parchment.Leaf) {
        let after = blot.split(nativeRange.start.offset);
        blot.parent.insertBefore(this.cursor, after);
      } else {
        blot.insertBefore(this.cursor, nativeRange.start.node);  // Should never happen
      }
      this.cursor.attach();
    }
    this.cursor.format(format, value);
    this.scroll.optimize();
    this.setNativeRange(this.cursor.textNode, this.cursor.textNode.data.length);
    this.update();
  }

  getBounds(index, length = 0) {
    let scrollLength = this.scroll.length();
    index = Math.min(index, scrollLength - 1);
    length = Math.min(index + length, scrollLength - 1) - index;
    let node, [leaf, offset] = this.scroll.leaf(index);
    if (leaf == null) return null;
    [node, offset] = leaf.position(offset, true);
    let range = document.createRange();
    if (length > 0) {
      range.setStart(node, offset);
      [leaf, offset] = this.scroll.leaf(index + length);
      if (leaf == null) return null;
      [node, offset] = leaf.position(offset, true);
      range.setEnd(node, offset);
      return range.getBoundingClientRect();
    } else {
      let side = 'left';
      let rect;
      if (node instanceof Text) {
        if (offset < node.data.length) {
          range.setStart(node, offset);
          range.setEnd(node, offset + 1);
        } else {
          range.setStart(node, offset - 1);
          range.setEnd(node, offset);
          side = 'right';
        }
        rect = range.getBoundingClientRect();
      } else {
        rect = leaf.domNode.getBoundingClientRect();
        if (offset > 0) side = 'right';
      }
      return {
        bottom: rect.top + rect.height,
        height: rect.height,
        left: rect[side],
        right: rect[side],
        top: rect.top,
        width: 0
      };
    }
  }

  getNativeRange() {
    let selection = document.getSelection();
    if (selection == null || selection.rangeCount <= 0) return null;
    let nativeRange = selection.getRangeAt(0);
    if (nativeRange == null) return null;
    let range = this.normalizeNative(nativeRange);
    debug.info('getNativeRange', range);
    return range;
  }

  getRange() {
    let normalized = this.getNativeRange();
    if (normalized == null) return [null, null];
    let range = this.normalizedToRange(normalized);
    return [range, normalized];
  }

  hasFocus() {
    return document.activeElement === this.root;
  }

  normalizedToRange(range) {
    let positions = [[range.start.node, range.start.offset]];
    if (!range.native.collapsed) {
      positions.push([range.end.node, range.end.offset]);
    }
    let indexes = positions.map((position) => {
      let [node, offset] = position;
      let blot = Parchment.find(node, true);
      let index = blot.offset(this.scroll);
      if (offset === 0) {
        return index;
      } else if (blot instanceof Parchment.Container) {
        return index + blot.length();
      } else {
        return index + blot.index(node, offset);
      }
    });
    let start = Math.min(...indexes), end = Math.max(...indexes);
    end = Math.min(end, this.scroll.length() - 1);
    return new Range(start, end-start);
  }

  normalizeNative(nativeRange) {
    if (!contains(this.root, nativeRange.startContainer) ||
        (!nativeRange.collapsed && !contains(this.root, nativeRange.endContainer))) {
      return null;
    }
    let range = {
      start: { node: nativeRange.startContainer, offset: nativeRange.startOffset },
      end: { node: nativeRange.endContainer, offset: nativeRange.endOffset },
      native: nativeRange
    };
    [range.start, range.end].forEach(function(position) {
      let node = position.node, offset = position.offset;
      while (!(node instanceof Text) && node.childNodes.length > 0) {
        if (node.childNodes.length > offset) {
          node = node.childNodes[offset];
          offset = 0;
        } else if (node.childNodes.length === offset) {
          node = node.lastChild;
          offset = node instanceof Text ? node.data.length : node.childNodes.length + 1;
        } else {
          break;
        }
      }
      position.node = node, position.offset = offset;
    });
    return range;
  }

  rangeToNative(range) {
    let indexes = range.collapsed ? [range.index] : [range.index, range.index + range.length];
    let args = [];
    let scrollLength = this.scroll.length();
    indexes.forEach((index, i) => {
      index = Math.min(scrollLength - 1, index);
      let node, [leaf, offset] = this.scroll.leaf(index);
      [node, offset] = leaf.position(offset, i !== 0);
      args.push(node, offset);
    });
    if (args.length < 2) {
      args = args.concat(args);
    }
    return args;
  }

  scrollIntoView(range = this.lastRange) {
    if (range == null) return;
    let bounds = this.getBounds(range.index, range.length);
    if (bounds == null) return;
    let limit = this.scroll.length()-1;
    let [first, ] = this.scroll.line(Math.min(range.index, limit));
    let last = first;
    if (range.length > 0) {
      [last, ] = this.scroll.line(Math.min(range.index + range.length, limit));
    }
    if (first == null || last == null) return;
    let scroller = this.scroll.scrollingContainer;
    let scrollBounds = scroller.getBoundingClientRect();
    if (bounds.top < scrollBounds.top) {
      scroller.scrollTop -= (scrollBounds.top - bounds.top);
    } else if (bounds.bottom > scrollBounds.bottom) {
      scroller.scrollTop += (bounds.bottom - scrollBounds.bottom);
    }
  }

  setNativeRange(startNode, startOffset, endNode = startNode, endOffset = startOffset, force = false) {
    debug.info('setNativeRange', startNode, startOffset, endNode, endOffset);
    if (startNode != null && (this.root.parentNode == null || startNode.parentNode == null || endNode.parentNode == null)) {
      return;
    }
    let selection = document.getSelection();
    if (selection == null) return;
    if (startNode != null) {
      if (!this.hasFocus()) this.root.focus();
      let native = (this.getNativeRange() || {}).native;
      if (native == null || force ||
          startNode !== native.startContainer ||
          startOffset !== native.startOffset ||
          endNode !== native.endContainer ||
          endOffset !== native.endOffset) {

        if (startNode.tagName == "BR") {
          startOffset = [].indexOf.call(startNode.parentNode.childNodes, startNode);
          startNode = startNode.parentNode;
        }
        if (endNode.tagName == "BR") {
          endOffset = [].indexOf.call(endNode.parentNode.childNodes, endNode);
          endNode = endNode.parentNode;
        }
        let range = document.createRange();
        range.setStart(startNode, startOffset);
        range.setEnd(endNode, endOffset);
        selection.removeAllRanges();
        selection.addRange(range);
      }
    } else {
      selection.removeAllRanges();
      this.root.blur();
      document.body.focus();  // root.blur() not enough on IE11+Travis+SauceLabs (but not local VMs)
    }
  }

  setRange(range, force = false, source = Emitter.sources.API) {
    if (typeof force === 'string') {
      source = force;
      force = false;
    }
    debug.info('setRange', range);
    if (range != null) {
      let args = this.rangeToNative(range);
      this.setNativeRange(...args, force);
    } else {
      this.setNativeRange(null);
    }
    this.update(source);
  }

  update(source = Emitter.sources.USER) {
    let oldRange = this.lastRange;
    let [lastRange, nativeRange] = this.getRange();
    this.lastRange = lastRange;
    if (this.lastRange != null) {
      this.savedRange = this.lastRange;
    }
    if (!equal(oldRange, this.lastRange)) {
      if (!this.composing && nativeRange != null && nativeRange.native.collapsed && nativeRange.start.node !== this.cursor.textNode) {
        this.cursor.restore();
      }
      let args = [Emitter.events.SELECTION_CHANGE, clone(this.lastRange), clone(oldRange), source];
      this.emitter.emit(Emitter.events.EDITOR_CHANGE, ...args);
      if (source !== Emitter.sources.SILENT) {
        this.emitter.emit(...args);
      }
    }
  }
}

function contains(parent, descendant) {
  try {
    // Firefox inserts inaccessible nodes around video elements
    descendant.parentNode;
  } catch (e) {
    return false;
  }
  // IE11 has bug with Text nodes
  // https://connect.microsoft.com/IE/feedback/details/780874/node-contains-is-incorrect
  if (descendant instanceof Text) {
    descendant = descendant.parentNode;
  }
  return parent.contains(descendant);
}

export { Range, Selection as default };
<commit_msg>fix range having negative length<commit_after>import Parchment from 'parchment';
import clone from 'clone';
import equal from 'deep-equal';
import Emitter from './emitter';
import logger from './logger';

let debug = logger('quill:selection');

class Range {
  constructor(index, length = 0) {
    this.index = index;
    this.length = length;
  }
}

class Selection {
  constructor(scroll, emitter) {
    this.emitter = emitter;
    this.scroll = scroll;
    this.composing = false;
    this.root = this.scroll.domNode;
    this.root.addEventListener('compositionstart', () => {
      this.composing = true;
    });
    this.root.addEventListener('compositionend', () => {
      this.composing = false;
    });
    this.cursor = Parchment.create('cursor', this);
    // savedRange is last non-null range
    this.lastRange = this.savedRange = new Range(0, 0);
    ['keyup', 'mouseup', 'mouseleave', 'touchend', 'touchleave', 'focus', 'blur'].forEach((eventName) => {
      this.root.addEventListener(eventName, () => {
        // When range used to be a selection and user click within the selection,
        // the range now being a cursor has not updated yet without setTimeout
        setTimeout(this.update.bind(this, Emitter.sources.USER), 100);
      });
    });
    this.emitter.on(Emitter.events.EDITOR_CHANGE, (type, delta) => {
      if (type === Emitter.events.TEXT_CHANGE && delta.length() > 0) {
        this.update(Emitter.sources.SILENT);
      }
    });
    this.emitter.on(Emitter.events.SCROLL_BEFORE_UPDATE, () => {
      if (!this.hasFocus()) return;
      let native = this.getNativeRange();
      if (native == null) return;
      if (native.start.node === this.cursor.textNode) return;  // cursor.restore() will handle
      // TODO unclear if this has negative side effects
      this.emitter.once(Emitter.events.SCROLL_UPDATE, () => {
        try {
          this.setNativeRange(native.start.node, native.start.offset, native.end.node, native.end.offset);
        } catch (ignored) {}
      });
    });
    this.update(Emitter.sources.SILENT);
  }

  focus() {
    if (this.hasFocus()) return;
    this.root.focus();
    this.setRange(this.savedRange);
  }

  format(format, value) {
    if (this.scroll.whitelist != null && !this.scroll.whitelist[format]) return;
    this.scroll.update();
    let nativeRange = this.getNativeRange();
    if (nativeRange == null || !nativeRange.native.collapsed || Parchment.query(format, Parchment.Scope.BLOCK)) return;
    if (nativeRange.start.node !== this.cursor.textNode) {
      let blot = Parchment.find(nativeRange.start.node, false);
      if (blot == null) return;
      // TODO Give blot ability to not split
      if (blot instanceof Parchment.Leaf) {
        let after = blot.split(nativeRange.start.offset);
        blot.parent.insertBefore(this.cursor, after);
      } else {
        blot.insertBefore(this.cursor, nativeRange.start.node);  // Should never happen
      }
      this.cursor.attach();
    }
    this.cursor.format(format, value);
    this.scroll.optimize();
    this.setNativeRange(this.cursor.textNode, this.cursor.textNode.data.length);
    this.update();
  }

  getBounds(index, length = 0) {
    let scrollLength = this.scroll.length();
    index = Math.min(index, scrollLength - 1);
    length = Math.min(index + length, scrollLength - 1) - index;
    let node, [leaf, offset] = this.scroll.leaf(index);
    if (leaf == null) return null;
    [node, offset] = leaf.position(offset, true);
    let range = document.createRange();
    if (length > 0) {
      range.setStart(node, offset);
      [leaf, offset] = this.scroll.leaf(index + length);
      if (leaf == null) return null;
      [node, offset] = leaf.position(offset, true);
      range.setEnd(node, offset);
      return range.getBoundingClientRect();
    } else {
      let side = 'left';
      let rect;
      if (node instanceof Text) {
        if (offset < node.data.length) {
          range.setStart(node, offset);
          range.setEnd(node, offset + 1);
        } else {
          range.setStart(node, offset - 1);
          range.setEnd(node, offset);
          side = 'right';
        }
        rect = range.getBoundingClientRect();
      } else {
        rect = leaf.domNode.getBoundingClientRect();
        if (offset > 0) side = 'right';
      }
      return {
        bottom: rect.top + rect.height,
        height: rect.height,
        left: rect[side],
        right: rect[side],
        top: rect.top,
        width: 0
      };
    }
  }

  getNativeRange() {
    let selection = document.getSelection();
    if (selection == null || selection.rangeCount <= 0) return null;
    let nativeRange = selection.getRangeAt(0);
    if (nativeRange == null) return null;
    let range = this.normalizeNative(nativeRange);
    debug.info('getNativeRange', range);
    return range;
  }

  getRange() {
    let normalized = this.getNativeRange();
    if (normalized == null) return [null, null];
    let range = this.normalizedToRange(normalized);
    return [range, normalized];
  }

  hasFocus() {
    return document.activeElement === this.root;
  }

  normalizedToRange(range) {
    let positions = [[range.start.node, range.start.offset]];
    if (!range.native.collapsed) {
      positions.push([range.end.node, range.end.offset]);
    }
    let indexes = positions.map((position) => {
      let [node, offset] = position;
      let blot = Parchment.find(node, true);
      let index = blot.offset(this.scroll);
      if (offset === 0) {
        return index;
      } else if (blot instanceof Parchment.Container) {
        return index + blot.length();
      } else {
        return index + blot.index(node, offset);
      }
    });
    let end = Math.min(Math.max(...indexes), this.scroll.length() - 1);
    let start = Math.min(end, ...indexes);
    return new Range(start, end-start);
  }

  normalizeNative(nativeRange) {
    if (!contains(this.root, nativeRange.startContainer) ||
        (!nativeRange.collapsed && !contains(this.root, nativeRange.endContainer))) {
      return null;
    }
    let range = {
      start: { node: nativeRange.startContainer, offset: nativeRange.startOffset },
      end: { node: nativeRange.endContainer, offset: nativeRange.endOffset },
      native: nativeRange
    };
    [range.start, range.end].forEach(function(position) {
      let node = position.node, offset = position.offset;
      while (!(node instanceof Text) && node.childNodes.length > 0) {
        if (node.childNodes.length > offset) {
          node = node.childNodes[offset];
          offset = 0;
        } else if (node.childNodes.length === offset) {
          node = node.lastChild;
          offset = node instanceof Text ? node.data.length : node.childNodes.length + 1;
        } else {
          break;
        }
      }
      position.node = node, position.offset = offset;
    });
    return range;
  }

  rangeToNative(range) {
    let indexes = range.collapsed ? [range.index] : [range.index, range.index + range.length];
    let args = [];
    let scrollLength = this.scroll.length();
    indexes.forEach((index, i) => {
      index = Math.min(scrollLength - 1, index);
      let node, [leaf, offset] = this.scroll.leaf(index);
      [node, offset] = leaf.position(offset, i !== 0);
      args.push(node, offset);
    });
    if (args.length < 2) {
      args = args.concat(args);
    }
    return args;
  }

  scrollIntoView(range = this.lastRange) {
    if (range == null) return;
    let bounds = this.getBounds(range.index, range.length);
    if (bounds == null) return;
    let limit = this.scroll.length()-1;
    let [first, ] = this.scroll.line(Math.min(range.index, limit));
    let last = first;
    if (range.length > 0) {
      [last, ] = this.scroll.line(Math.min(range.index + range.length, limit));
    }
    if (first == null || last == null) return;
    let scroller = this.scroll.scrollingContainer;
    let scrollBounds = scroller.getBoundingClientRect();
    if (bounds.top < scrollBounds.top) {
      scroller.scrollTop -= (scrollBounds.top - bounds.top);
    } else if (bounds.bottom > scrollBounds.bottom) {
      scroller.scrollTop += (bounds.bottom - scrollBounds.bottom);
    }
  }

  setNativeRange(startNode, startOffset, endNode = startNode, endOffset = startOffset, force = false) {
    debug.info('setNativeRange', startNode, startOffset, endNode, endOffset);
    if (startNode != null && (this.root.parentNode == null || startNode.parentNode == null || endNode.parentNode == null)) {
      return;
    }
    let selection = document.getSelection();
    if (selection == null) return;
    if (startNode != null) {
      if (!this.hasFocus()) this.root.focus();
      let native = (this.getNativeRange() || {}).native;
      if (native == null || force ||
          startNode !== native.startContainer ||
          startOffset !== native.startOffset ||
          endNode !== native.endContainer ||
          endOffset !== native.endOffset) {

        if (startNode.tagName == "BR") {
          startOffset = [].indexOf.call(startNode.parentNode.childNodes, startNode);
          startNode = startNode.parentNode;
        }
        if (endNode.tagName == "BR") {
          endOffset = [].indexOf.call(endNode.parentNode.childNodes, endNode);
          endNode = endNode.parentNode;
        }
        let range = document.createRange();
        range.setStart(startNode, startOffset);
        range.setEnd(endNode, endOffset);
        selection.removeAllRanges();
        selection.addRange(range);
      }
    } else {
      selection.removeAllRanges();
      this.root.blur();
      document.body.focus();  // root.blur() not enough on IE11+Travis+SauceLabs (but not local VMs)
    }
  }

  setRange(range, force = false, source = Emitter.sources.API) {
    if (typeof force === 'string') {
      source = force;
      force = false;
    }
    debug.info('setRange', range);
    if (range != null) {
      let args = this.rangeToNative(range);
      this.setNativeRange(...args, force);
    } else {
      this.setNativeRange(null);
    }
    this.update(source);
  }

  update(source = Emitter.sources.USER) {
    let oldRange = this.lastRange;
    let [lastRange, nativeRange] = this.getRange();
    this.lastRange = lastRange;
    if (this.lastRange != null) {
      this.savedRange = this.lastRange;
    }
    if (!equal(oldRange, this.lastRange)) {
      if (!this.composing && nativeRange != null && nativeRange.native.collapsed && nativeRange.start.node !== this.cursor.textNode) {
        this.cursor.restore();
      }
      let args = [Emitter.events.SELECTION_CHANGE, clone(this.lastRange), clone(oldRange), source];
      this.emitter.emit(Emitter.events.EDITOR_CHANGE, ...args);
      if (source !== Emitter.sources.SILENT) {
        this.emitter.emit(...args);
      }
    }
  }
}

function contains(parent, descendant) {
  try {
    // Firefox inserts inaccessible nodes around video elements
    descendant.parentNode;
  } catch (e) {
    return false;
  }
  // IE11 has bug with Text nodes
  // https://connect.microsoft.com/IE/feedback/details/780874/node-contains-is-incorrect
  if (descendant instanceof Text) {
    descendant = descendant.parentNode;
  }
  return parent.contains(descendant);
}

export { Range, Selection as default };
<|endoftext|>


<commit_before>import { createSelector } from 'reselect';

const getPastLength = (blueprint) => {
  const pastLength = blueprint.past.length;
  return pastLength;
};

export const makeGetPastLength = () => createSelector(
  [getPastLength],
  (pastLength) => pastLength
);

const getFutureLength = (blueprint) => {
  const futureLength = blueprint.future.length;
  return futureLength;
};

export const makeGetFutureLength = () => createSelector(
  [getFutureLength],
  (futureLength) => futureLength
);

const getBlueprintById = (state, blueprintId) => {
  const blueprintById = state.blueprints.find(blueprint => blueprint.present.id === blueprintId);
  return blueprintById;
};

export const makeGetBlueprintById = () => createSelector(
  [getBlueprintById],
  (blueprint) => blueprint
);

const getSortedSelectedComponents = (state, blueprint) => {
  const selectedComponentNames = blueprint.packages.map(item => item.name);

  const components = blueprint.components;
  if (components === undefined) {
    return [];
  }

  const sortedSelectedComponents = components.filter(component => selectedComponentNames.includes(component.name));
  const key = state.sort.components.key;
  const value = state.sort.components.value;
  sortedSelectedComponents.sort((a, b) => {
    if (a[key] > b[key]) return value === 'DESC' ? 1 : -1;
    if (b[key] > a[key]) return value === 'DESC' ? -1 : 1;
    return 0;
  });
  return sortedSelectedComponents;
};

export const makeGetSortedSelectedComponents = () => createSelector(
  [getSortedSelectedComponents],
  (selectedComponents) => selectedComponents
);

const getSortedDependencies = (state, blueprint) => {
  const selectedComponentNames = blueprint.packages.map(item => item.name);
  const dependencies = blueprint.components;
  if (dependencies === undefined) {
    return [];
  }

  const sortedDependencies = dependencies.filter(dependency => !selectedComponentNames.includes(dependency.name));

  const key = state.sort.dependencies.key;
  const value = state.sort.dependencies.value;
  sortedDependencies.sort((a, b) => {
    if (a[key] > b[key]) return value === 'DESC' ? 1 : -1;
    if (b[key] > a[key]) return value === 'DESC' ? -1 : 1;
    return 0;
  });
  return sortedDependencies;
};

export const makeGetSortedDependencies = () => createSelector(
  [getSortedDependencies],
  (dependencies) => dependencies
);

const getSortedBlueprints = (state) => {
  const sortedBlueprints = state.blueprints;
  const key = state.sort.blueprints.key;
  const value = state.sort.blueprints.value;
  sortedBlueprints.sort((a, b) => {
    if (a.present[key] > b.present[key]) return value === 'DESC' ? 1 : -1;
    if (b.present[key] > a.present[key]) return value === 'DESC' ? -1 : 1;
    return 0;
  });
  return sortedBlueprints;
};

export const makeGetSortedBlueprints = () => createSelector(
  [getSortedBlueprints],
  (blueprints) => blueprints
);
<commit_msg>Fixed components inBlueprint class<commit_after>import { createSelector } from 'reselect';

const getPastLength = (blueprint) => {
  const pastLength = blueprint.past.length;
  return pastLength;
};

export const makeGetPastLength = () => createSelector(
  [getPastLength],
  (pastLength) => pastLength
);

const getFutureLength = (blueprint) => {
  const futureLength = blueprint.future.length;
  return futureLength;
};

export const makeGetFutureLength = () => createSelector(
  [getFutureLength],
  (futureLength) => futureLength
);

const getBlueprintById = (state, blueprintId) => {
  const blueprintById = state.blueprints.find(blueprint => blueprint.present.id === blueprintId);
  return blueprintById;
};

export const makeGetBlueprintById = () => createSelector(
  [getBlueprintById],
  (blueprint) => blueprint
);

const getSortedSelectedComponents = (state, blueprint) => {
  const selectedComponentNames = blueprint.packages.map(item => item.name);

  const components = blueprint.components;
  if (components === undefined) {
    return [];
  }

  const sortedSelectedComponents = components.filter(component => selectedComponentNames.includes(component.name));
  sortedSelectedComponents.map(component => {
    component.inBlueprint = true; // eslint-disable-line no-param-reassign
    component.user_selected = true;
  });
  const key = state.sort.components.key;
  const value = state.sort.components.value;
  sortedSelectedComponents.sort((a, b) => {
    if (a[key] > b[key]) return value === 'DESC' ? 1 : -1;
    if (b[key] > a[key]) return value === 'DESC' ? -1 : 1;
    return 0;
  });
  return sortedSelectedComponents;
};

export const makeGetSortedSelectedComponents = () => createSelector(
  [getSortedSelectedComponents],
  (selectedComponents) => selectedComponents
);

const getSortedDependencies = (state, blueprint) => {
  const selectedComponentNames = blueprint.packages.map(item => item.name);
  const dependencies = blueprint.components;
  if (dependencies === undefined) {
    return [];
  }

  const sortedDependencies = dependencies.filter(dependency => !selectedComponentNames.includes(dependency.name));
  sortedDependencies.map(dependency => {
    dependency.inBlueprint = true; // eslint-disable-line no-param-reassign
  });
  const key = state.sort.dependencies.key;
  const value = state.sort.dependencies.value;
  sortedDependencies.sort((a, b) => {
    if (a[key] > b[key]) return value === 'DESC' ? 1 : -1;
    if (b[key] > a[key]) return value === 'DESC' ? -1 : 1;
    return 0;
  });
  return sortedDependencies;
};

export const makeGetSortedDependencies = () => createSelector(
  [getSortedDependencies],
  (dependencies) => dependencies
);

const getSortedBlueprints = (state) => {
  const sortedBlueprints = state.blueprints;
  const key = state.sort.blueprints.key;
  const value = state.sort.blueprints.value;
  sortedBlueprints.sort((a, b) => {
    if (a.present[key] > b.present[key]) return value === 'DESC' ? 1 : -1;
    if (b.present[key] > a.present[key]) return value === 'DESC' ? -1 : 1;
    return 0;
  });
  return sortedBlueprints;
};

export const makeGetSortedBlueprints = () => createSelector(
  [getSortedBlueprints],
  (blueprints) => blueprints
);
<|endoftext|>


<commit_before>// moog implements the pattern we use to initialize modules. It supports
// inheritance via `extend`, the `init`, `beforeSuperClass`,
// `methods`, and `extendMethods` features, etc.

const _ = require('lodash');

module.exports = function(options) {

  options = options || {};

  const self = {};

  self.options = options;

  self.definitions = {};

  self.ordinal = 0;

  // The "extending" argument is of interest to subclasses like
  // moog-require that need to know about relative paths. Must
  // return the new definition for the convenience of moog-require too

  self.define = function(className, definition, extending) {

    if (!definition) {
      // This can happen because we use self.define as an autoloader
      // when resolving "extend". The moog-require module overloads
      // self.define to handle this case
      if (extending) {
        throw new Error(`The module ${className} is not defined. Referenced in ${extending.__meta.name}.`);
      } else {
        throw new Error(`The module ${className} is not defined.`);
      }
    }

    // Make a shallow clone to avoid numerous problems with multiple
    // intentionally separate instances of moog; otherwise they wind
    // up sharing `__meta` depending on whether they were loaded with
    // `require`, and so on. We must clone `__meta` itself for the
    // same reason, and also `__meta.chain` because it is an array
    // object. All other properties of `__meta` are simple values.

    definition = _.clone(definition);
    if (definition.__meta !== undefined) {
      definition.__meta = _.clone(definition.__meta);
      if (definition.__meta.chain) {
        definition.__meta.chain = _.clone(definition.__meta.chain);
      }
    }
    definition.__meta = definition.__meta || {};
    definition.__meta.name = className;
    definition.__meta.ordinal = self.ordinal++;

    if (!extending) {
      definition.__meta.explicit = true;
    }

    const exists = _.has(self.definitions, className);
    if (definition.extendIfFirst && (!exists)) {
      definition.extend = definition.extendIfFirst;
    }

    if ((!definition.extend) && (definition.extend !== false)) {
      if (exists) {
        // Double definitions result in implicit subclassing of
        // the original definition by the new one; anything else
        // trying to access this className name will see
        // the resulting subclass via self.definitions. However
        // we reset the __name property for the benefit of
        // implementations that need to distinguish assets that
        // come from each subclass in the inheritance chain.
        definition.extend = self.definitions[className];
        definition.__meta.name = self.originalToMy(definition.__meta.name);
      } else {
        // Extend the default base class by default, if any, unless
        // we're it
        if (self.options.defaultBaseClass && className !== self.options.defaultBaseClass) {
          definition.extend = self.options.defaultBaseClass;
        }
      }
    }
    self.definitions[className] = definition;
    return definition;
  };

  self.redefine = function(className, definition) {
    delete self.definitions[className];
    return self.define(className, definition);
  };

  self.isDefined = function(className, options) {
    options = options || {};
    if (_.has(self.definitions, className)) {
      return true;
    }
    if (options.autoload === false) {
      return false;
    }
    try {
      // Can we autoload it?
      self.define(className);
      // Yes, but we don't really want it yet
      delete self.definitions[className];
      return true;
    } catch (e) {
      return false;
    }
  };

  // Create an instance of the given class name, awaiting the
  // init functions of each class in the inheritance tree,
  // if present

  self.create = async function(className, _options) {

    const {
      that,
      steps
    } = createPrep(className);

    const options = {};

    // Everything in Apostrophe appreciates self.options being
    // a thing already after create is complete
    that.options = options;

    // Now we want to start from the base class and go down
    steps.reverse();

    // Actually just the apos object merging in, app.js merging was handled elsewhere
    Object.assign(options, _options || {});

    self.options.sections = self.options.sections || [];
    self.options.unparsedSections = self.options.unparsedSections || [];

    const validKeys = [ '__meta', 'options', 'cascades', 'beforeSuperClass', 'init', 'afterAllSections', 'extend', 'improve', 'methods', 'extendMethods' ]
      .concat(self.options.sections)
      .concat(self.options.sections.map(getExtendKey))
      .concat(self.options.unparsedSections)
      .concat(self.options.unparsedSections.map(getExtendKey));

    const upgradeHints = {
      construct: 'in Apostrophe 3.x, "construct" has been replaced with "methods", "routes", "apiRoutes", etc.',
      beforeConstruct: 'in Apostrophe 3.x, "beforeConstruct" has been replaced with "beforeSuperClass". It takes (self, options) and should be solely concerned with modifying the options before the base class sees them. It must be synchronous. Check out the new fields section, you might not need beforeSuperClass.',
      afterConstruct: 'in Apostrophe 3.x, "afterConstruct" has been replaced with "init". It takes (self, options) and may be an async function.'
    };

    for (const step of steps) {
      Object.assign(options, step.options || {});
    }

    let cascades = [];
    for (const step of steps) {
      if (step.cascades) {
        cascades = cascades.concat(step.cascades);
      }
      for (const key of Object.keys(step)) {
        if (!(validKeys.includes(key) || cascades.includes(key))) {
          const message = upgradeHints[key] || `${key} is not a valid top level property for an Apostrophe 3.x module. Make sure you nest regular module options in the new "options" property.`;
          throw `${clarifyModuleName(step.__meta.name)}: ${message}`;
        }
      }

      for (const cascade of cascades) {
        if (!that[cascade]) {
          that[cascade] = {};
        }
        if (!that[`${cascade}Groups`]) {
          that[`${cascade}Groups`] = {};
        }
        // You can have access to options within a function, if you choose to provide one
        const fields = ((typeof step[cascade]) === 'function') ? step[cascade](that, options) : step[cascade];
        if (fields) {
          const valid = [ 'add', 'remove', 'group' ];
          if (fields.add) {
            that[cascade] = {
              ...that[cascade],
              ...fields.add
            };
          }
          if (fields.remove) {
            for (const field of fields.remove) {
              delete that[cascade][field];
            }
          }
          if (fields.group) {
            that[`${cascade}Groups`] = {
              ...that[`${cascade}Groups`],
              ...fields.group
            };
          }
          for (const key of Object.keys(fields)) {
            if (!valid.includes(key)) {
              throw `${clarifyModuleName(step.__meta.name)}: ${key} is not valid inside "${cascade}".\nPossibly you forgot to nest a field in "add".`;
            }
          }
        }
      }
    }

    // This needs to be after the options and fields are compiled so it can manipulate
    // the result, yet we've already reordered the steps with the superclass first,
    // so walk them backwards to implement beforeSuperClass
    for (let i = (steps.length - 1); (i >= 0); i--) {
      const step = steps[i];
      if (step.beforeSuperClass) {
        step.beforeSuperClass(that, options);
      }
    }

    // Always build methods first so they can be referenced safely
    // in other sections
    build(null, 'methods');

    // Unparsed sections, like `queries` and `extendQueries`. These are just captured
    // in an object with the props from each level so the module can do something with
    // them at runtime, useful for instantiating cursors. Do it before init so that init
    // can carry out queries with cursors
    (self.options.unparsedSections || []).forEach(section => capture(section));

    // init is called BEFORE routes etc. are called so that the section functions
    // for those things can benefit from methods, properties, etc. set by init
    // when deciding what to return
    for (const step of steps) {
      if (step.init) {
        await step.init(that, options);
      }
    }

    // helpers, eventHandlers, apiRoutes, renderRoutes, whatever comes up
    (self.options.sections || []).forEach(section => build(section, section));

    // afterAllSections is called last and has access to self.routes, etc.
    // For project-level developers this is usually not important, but
    // the core does call certain methods in the @apostrophecms/modules base class
    // implementation of afterAllSections to actually add the routes and handlers
    // to the system
    for (const step of steps) {
      if (step.afterAllSections) {
        await step.afterAllSections(that, options);
      }
    }

    function build(section, keyword) {
      for (const step of steps) {
        let context;
        if (section) {
          // non-methods attach to a named section object
          that[section] = that[section] || {};
          context = that[section];
        } else {
          // methods attach directly to self
          context = that;
        }
        if (step[keyword]) {
          if (Array.isArray(step[keyword])) {
            // If an array of method names is passed rather than an object,
            // add helper functions that invoke the methods of the same name.
            // Helpers in particular are often also needed as methods and it
            // is tedious to wrap them all by hand. Doing it this way ensures
            // that extendHelpers will behave sensibly
            const methods = step[keyword];
            step[keyword] = function(self, options) {
              const wrapped = {};
              methods.forEach(method => {
                wrapped[method] = function(...rest) {
                  return self[method](...rest);
                };
              });
              return wrapped;
            };
          }
          if ((typeof step[keyword]) !== 'function') {
            throw stepError(step, `${keyword} must be a function that takes (self, options) and returns an object`);
          }
          _.merge(context, step[keyword](that, options));
        }
        const extend = getExtendKey(keyword);
        if (step[extend]) {
          if ((typeof step[extend]) !== 'function') {
            throw stepError(step, `${extend} must be a function that takes (self, options) and returns an object`);
          }
          const extensions = step[extend](that, options);
          wrap(context, extensions);
        }
      }
    }

    return that;

    function wrap(context, extensions) {
      _.each(extensions, (fn, name) => {
        if ((typeof fn) !== 'function') {
          // Nested structure is allowed, for instance to implement
          // the eventHandlers syntax, with event names
          // and the actual named handlers grouped within them
          context[name] = context[name] || {};
          return wrap(context[name], fn);
        }
        let superMethod;
        if (Array.isArray(context[name])) {
          // Middleware chain - actual route fn will be last fn
          superMethod = context[name][context[name].length - 1];
          context[name][context[name].length - 1] = function() {
            return fn.apply(that, [ superMethod, ...arguments ]);
          };
        } else {
          superMethod = context[name];
          context[name] = function() {
            return fn.apply(that, [ superMethod, ...arguments ]);
          };
        }
      });
    }

    function capture(section) {
      that[section] = {};
      for (const step of steps) {
        that[section][step.__meta.name] = step[section];
      }
    }

  };

  // Returns true if the given object is of the given moog class.
  // If the object is not a moog object, `false` is returned.

  self.instanceOf = function(object, name) {
    if (!object.__meta) {
      return false;
    }
    if (!object.__meta.chain) {
      return false;
    }
    return !!_.find(object.__meta.chain, { name: name });
  };

  // Given a moog class name like `my-foo` or `@namespace/my-foo`,
  // this method will return `foo` or `@namespace/my-foo`. Any other
  // name is returned as-is.

  self.myToOriginal = function(name) {
    if (name.match(/^my-/)) {
      return name.replace(/^my-/, '');
    }
    return name.replace(/^@([^/]+)\/my-(.*)$/, '@$1/$2');
  };

  // Given a moog class name like `foo` or `@namespace/foo`, this method
  // will return `my-foo` or `@namespace/my-foo` as appropriate. The behavior
  // of this method when given a name that already has a my- prefix is
  // undefined and should not be relied upon (see isMy).

  self.originalToMy = function(name) {
    if (name.match(/^@/)) {
      return name.replace(/^@([^/]+)\/(.*)$/, '@$1/my-$2');
    } else {
      return 'my-' + name;
    }
  };

  // Given a moog class name like `my-foo` or `@namespace/my-foo`, this
  // method will return true. Otherwise it will return false.

  self.isMy = function(name) {
    if (name.match(/^my-/)) {
      return true;
    }
    if (name.match(/^@([^/]+)\/my-(.*)$/)) {
      return true;
    }
    return false;
  };

  return self;

  function createPrep(className) {

    const that = {};
    const steps = [];
    const seen = {};
    let next = self.definitions[className];
    if (!next) {
      throw new Error('The className ' + className + ' is not defined.');
    }
    while (next) {
      const current = next;
      if (_.has(seen, current.__meta.ordinal)) {
        throw new Error('The className ' + className + ' encounters an infinite loop, "extend" probably points back to itself or its subclass.');
      }
      seen[current.__meta.ordinal] = true;
      steps.push(current);
      next = current.extend;
      // In most cases it'll be a string we need to look up
      // in self.definitions. In a few cases it is already
      // a pointer to another definition (see double defines, above)
      if (typeof (next) === 'string') {
        const nextName = next;
        next = self.definitions[nextName];
        if (!next) {
          // Try to use define as an autoloader. This will fail in
          // the default implementation
          next = self.define(nextName, undefined, current);
        }
      }
    }

    // Attach metadata about the modules in the
    // inheritance chain, base class first
    that.__meta = {
      chain: [],
      name: className
    };
    let i = steps.length - 1;
    while (i >= 0) {
      that.__meta.chain.push(steps[i].__meta);
      i--;
    }

    return {
      that,
      steps
    };
  }

};

function getExtendKey(keyword) {
  return 'extend' + keyword.charAt(0).toUpperCase() + keyword.slice(1);
}

function clarifyModuleName(original) {
  const name = original.replace(/^my-/, '').replace(/^(@[^/]+\/)my-(.*)$/, '$1$2');
  if (name !== original) {
    return `${name} (project level)`;
  }
  return name;
}

function stepError(step, message) {
  throw new Error(`${clarifyModuleName(step.__meta.name)}: ${message}`);
}
<commit_msg>avoid confusing variable name<commit_after>// moog implements the pattern we use to initialize modules. It supports
// inheritance via `extend`, the `init`, `beforeSuperClass`,
// `methods`, and `extendMethods` features, etc.

const _ = require('lodash');

module.exports = function(options) {

  options = options || {};

  const self = {};

  self.options = options;

  self.definitions = {};

  self.ordinal = 0;

  // The "extending" argument is of interest to subclasses like
  // moog-require that need to know about relative paths. Must
  // return the new definition for the convenience of moog-require too

  self.define = function(className, definition, extending) {

    if (!definition) {
      // This can happen because we use self.define as an autoloader
      // when resolving "extend". The moog-require module overloads
      // self.define to handle this case
      if (extending) {
        throw new Error(`The module ${className} is not defined. Referenced in ${extending.__meta.name}.`);
      } else {
        throw new Error(`The module ${className} is not defined.`);
      }
    }

    // Make a shallow clone to avoid numerous problems with multiple
    // intentionally separate instances of moog; otherwise they wind
    // up sharing `__meta` depending on whether they were loaded with
    // `require`, and so on. We must clone `__meta` itself for the
    // same reason, and also `__meta.chain` because it is an array
    // object. All other properties of `__meta` are simple values.

    definition = _.clone(definition);
    if (definition.__meta !== undefined) {
      definition.__meta = _.clone(definition.__meta);
      if (definition.__meta.chain) {
        definition.__meta.chain = _.clone(definition.__meta.chain);
      }
    }
    definition.__meta = definition.__meta || {};
    definition.__meta.name = className;
    definition.__meta.ordinal = self.ordinal++;

    if (!extending) {
      definition.__meta.explicit = true;
    }

    const exists = _.has(self.definitions, className);
    if (definition.extendIfFirst && (!exists)) {
      definition.extend = definition.extendIfFirst;
    }

    if ((!definition.extend) && (definition.extend !== false)) {
      if (exists) {
        // Double definitions result in implicit subclassing of
        // the original definition by the new one; anything else
        // trying to access this className name will see
        // the resulting subclass via self.definitions. However
        // we reset the __name property for the benefit of
        // implementations that need to distinguish assets that
        // come from each subclass in the inheritance chain.
        definition.extend = self.definitions[className];
        definition.__meta.name = self.originalToMy(definition.__meta.name);
      } else {
        // Extend the default base class by default, if any, unless
        // we're it
        if (self.options.defaultBaseClass && className !== self.options.defaultBaseClass) {
          definition.extend = self.options.defaultBaseClass;
        }
      }
    }
    self.definitions[className] = definition;
    return definition;
  };

  self.redefine = function(className, definition) {
    delete self.definitions[className];
    return self.define(className, definition);
  };

  self.isDefined = function(className, options) {
    options = options || {};
    if (_.has(self.definitions, className)) {
      return true;
    }
    if (options.autoload === false) {
      return false;
    }
    try {
      // Can we autoload it?
      self.define(className);
      // Yes, but we don't really want it yet
      delete self.definitions[className];
      return true;
    } catch (e) {
      return false;
    }
  };

  // Create an instance of the given class name, awaiting the
  // init functions of each class in the inheritance tree,
  // if present

  self.create = async function(className, _options) {

    const {
      that,
      steps
    } = createPrep(className);

    const options = {};

    // Everything in Apostrophe appreciates self.options being
    // a thing already after create is complete
    that.options = options;

    // Now we want to start from the base class and go down
    steps.reverse();

    // Actually just the apos object merging in, app.js merging was handled elsewhere
    Object.assign(options, _options || {});

    self.options.sections = self.options.sections || [];
    self.options.unparsedSections = self.options.unparsedSections || [];

    const validKeys = [ '__meta', 'options', 'cascades', 'beforeSuperClass', 'init', 'afterAllSections', 'extend', 'improve', 'methods', 'extendMethods' ]
      .concat(self.options.sections)
      .concat(self.options.sections.map(getExtendKey))
      .concat(self.options.unparsedSections)
      .concat(self.options.unparsedSections.map(getExtendKey));

    const upgradeHints = {
      construct: 'in Apostrophe 3.x, "construct" has been replaced with "methods", "routes", "apiRoutes", etc.',
      beforeConstruct: 'in Apostrophe 3.x, "beforeConstruct" has been replaced with "beforeSuperClass". It takes (self, options) and should be solely concerned with modifying the options before the base class sees them. It must be synchronous. Check out the new fields section, you might not need beforeSuperClass.',
      afterConstruct: 'in Apostrophe 3.x, "afterConstruct" has been replaced with "init". It takes (self, options) and may be an async function.'
    };

    for (const step of steps) {
      Object.assign(options, step.options || {});
    }

    let cascades = [];
    for (const step of steps) {
      if (step.cascades) {
        cascades = cascades.concat(step.cascades);
      }
      for (const key of Object.keys(step)) {
        if (!(validKeys.includes(key) || cascades.includes(key))) {
          const message = upgradeHints[key] || `${key} is not a valid top level property for an Apostrophe 3.x module. Make sure you nest regular module options in the new "options" property.`;
          throw `${clarifyModuleName(step.__meta.name)}: ${message}`;
        }
      }

      for (const cascade of cascades) {
        if (!that[cascade]) {
          that[cascade] = {};
        }
        if (!that[`${cascade}Groups`]) {
          that[`${cascade}Groups`] = {};
        }
        // You can have access to options within a function, if you choose to provide one
        const properties = ((typeof step[cascade]) === 'function') ? step[cascade](that, options) : step[cascade];
        if (properties) {
          const valid = [ 'add', 'remove', 'group' ];
          if (properties.add) {
            that[cascade] = {
              ...that[cascade],
              ...properties.add
            };
          }
          if (properties.remove) {
            for (const field of properties.remove) {
              delete that[cascade][field];
            }
          }
          if (properties.group) {
            that[`${cascade}Groups`] = {
              ...that[`${cascade}Groups`],
              ...properties.group
            };
          }
          for (const key of Object.keys(properties)) {
            if (!valid.includes(key)) {
              throw `${clarifyModuleName(step.__meta.name)}: ${key} is not valid inside "${cascade}".\nPossibly you forgot to nest a field in "add".`;
            }
          }
        }
      }
    }

    // This needs to be after the options and cascades are compiled so it can manipulate
    // the result, yet we've already reordered the steps with the superclass first,
    // so walk them backwards to implement beforeSuperClass
    for (let i = (steps.length - 1); (i >= 0); i--) {
      const step = steps[i];
      if (step.beforeSuperClass) {
        step.beforeSuperClass(that, options);
      }
    }

    // Always build methods first so they can be referenced safely
    // in other sections
    build(null, 'methods');

    // Unparsed sections, like `queries` and `extendQueries`. These are just captured
    // in an object with the props from each level so the module can do something with
    // them at runtime, useful for instantiating cursors. Do it before init so that init
    // can carry out queries with cursors
    (self.options.unparsedSections || []).forEach(section => capture(section));

    // init is called BEFORE routes etc. are called so that the section functions
    // for those things can benefit from methods, properties, etc. set by init
    // when deciding what to return
    for (const step of steps) {
      if (step.init) {
        await step.init(that, options);
      }
    }

    // helpers, eventHandlers, apiRoutes, renderRoutes, whatever comes up
    (self.options.sections || []).forEach(section => build(section, section));

    // afterAllSections is called last and has access to self.routes, etc.
    // For project-level developers this is usually not important, but
    // the core does call certain methods in the @apostrophecms/modules base class
    // implementation of afterAllSections to actually add the routes and handlers
    // to the system
    for (const step of steps) {
      if (step.afterAllSections) {
        await step.afterAllSections(that, options);
      }
    }

    function build(section, keyword) {
      for (const step of steps) {
        let context;
        if (section) {
          // non-methods attach to a named section object
          that[section] = that[section] || {};
          context = that[section];
        } else {
          // methods attach directly to self
          context = that;
        }
        if (step[keyword]) {
          if (Array.isArray(step[keyword])) {
            // If an array of method names is passed rather than an object,
            // add helper functions that invoke the methods of the same name.
            // Helpers in particular are often also needed as methods and it
            // is tedious to wrap them all by hand. Doing it this way ensures
            // that extendHelpers will behave sensibly
            const methods = step[keyword];
            step[keyword] = function(self, options) {
              const wrapped = {};
              methods.forEach(method => {
                wrapped[method] = function(...rest) {
                  return self[method](...rest);
                };
              });
              return wrapped;
            };
          }
          if ((typeof step[keyword]) !== 'function') {
            throw stepError(step, `${keyword} must be a function that takes (self, options) and returns an object`);
          }
          _.merge(context, step[keyword](that, options));
        }
        const extend = getExtendKey(keyword);
        if (step[extend]) {
          if ((typeof step[extend]) !== 'function') {
            throw stepError(step, `${extend} must be a function that takes (self, options) and returns an object`);
          }
          const extensions = step[extend](that, options);
          wrap(context, extensions);
        }
      }
    }

    return that;

    function wrap(context, extensions) {
      _.each(extensions, (fn, name) => {
        if ((typeof fn) !== 'function') {
          // Nested structure is allowed, for instance to implement
          // the eventHandlers syntax, with event names
          // and the actual named handlers grouped within them
          context[name] = context[name] || {};
          return wrap(context[name], fn);
        }
        let superMethod;
        if (Array.isArray(context[name])) {
          // Middleware chain - actual route fn will be last fn
          superMethod = context[name][context[name].length - 1];
          context[name][context[name].length - 1] = function() {
            return fn.apply(that, [ superMethod, ...arguments ]);
          };
        } else {
          superMethod = context[name];
          context[name] = function() {
            return fn.apply(that, [ superMethod, ...arguments ]);
          };
        }
      });
    }

    function capture(section) {
      that[section] = {};
      for (const step of steps) {
        that[section][step.__meta.name] = step[section];
      }
    }

  };

  // Returns true if the given object is of the given moog class.
  // If the object is not a moog object, `false` is returned.

  self.instanceOf = function(object, name) {
    if (!object.__meta) {
      return false;
    }
    if (!object.__meta.chain) {
      return false;
    }
    return !!_.find(object.__meta.chain, { name: name });
  };

  // Given a moog class name like `my-foo` or `@namespace/my-foo`,
  // this method will return `foo` or `@namespace/my-foo`. Any other
  // name is returned as-is.

  self.myToOriginal = function(name) {
    if (name.match(/^my-/)) {
      return name.replace(/^my-/, '');
    }
    return name.replace(/^@([^/]+)\/my-(.*)$/, '@$1/$2');
  };

  // Given a moog class name like `foo` or `@namespace/foo`, this method
  // will return `my-foo` or `@namespace/my-foo` as appropriate. The behavior
  // of this method when given a name that already has a my- prefix is
  // undefined and should not be relied upon (see isMy).

  self.originalToMy = function(name) {
    if (name.match(/^@/)) {
      return name.replace(/^@([^/]+)\/(.*)$/, '@$1/my-$2');
    } else {
      return 'my-' + name;
    }
  };

  // Given a moog class name like `my-foo` or `@namespace/my-foo`, this
  // method will return true. Otherwise it will return false.

  self.isMy = function(name) {
    if (name.match(/^my-/)) {
      return true;
    }
    if (name.match(/^@([^/]+)\/my-(.*)$/)) {
      return true;
    }
    return false;
  };

  return self;

  function createPrep(className) {

    const that = {};
    const steps = [];
    const seen = {};
    let next = self.definitions[className];
    if (!next) {
      throw new Error('The className ' + className + ' is not defined.');
    }
    while (next) {
      const current = next;
      if (_.has(seen, current.__meta.ordinal)) {
        throw new Error('The className ' + className + ' encounters an infinite loop, "extend" probably points back to itself or its subclass.');
      }
      seen[current.__meta.ordinal] = true;
      steps.push(current);
      next = current.extend;
      // In most cases it'll be a string we need to look up
      // in self.definitions. In a few cases it is already
      // a pointer to another definition (see double defines, above)
      if (typeof (next) === 'string') {
        const nextName = next;
        next = self.definitions[nextName];
        if (!next) {
          // Try to use define as an autoloader. This will fail in
          // the default implementation
          next = self.define(nextName, undefined, current);
        }
      }
    }

    // Attach metadata about the modules in the
    // inheritance chain, base class first
    that.__meta = {
      chain: [],
      name: className
    };
    let i = steps.length - 1;
    while (i >= 0) {
      that.__meta.chain.push(steps[i].__meta);
      i--;
    }

    return {
      that,
      steps
    };
  }

};

function getExtendKey(keyword) {
  return 'extend' + keyword.charAt(0).toUpperCase() + keyword.slice(1);
}

function clarifyModuleName(original) {
  const name = original.replace(/^my-/, '').replace(/^(@[^/]+\/)my-(.*)$/, '$1$2');
  if (name !== original) {
    return `${name} (project level)`;
  }
  return name;
}

function stepError(step, message) {
  throw new Error(`${clarifyModuleName(step.__meta.name)}: ${message}`);
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013 Taye Adeyemi
 * Open source under the MIT License.
 * https://raw.github.com/taye/interact.js/master/LICENSE
 */

(function(interact) {
    'use strict';

    var canvas,
        context,
        guidesCanvas,
        guidesContext,
        width = 800,
        height = 800,
        status,
        prevX = 0,
        prevY = 0,
        blue = '#2299ee',
        lightBlue = '#88ccff',
        peppermint = '#44ee44',
        tango = '#ff4400',
        draggingAnchor = null;

    function drawGrid (grid, gridOffset, range) {
        if (!grid.x || !grid.y) { return; }

        var barLength = 16;

        guidesContext.clearRect(0, 0, width, height);

        guidesContext.fillStyle = lightBlue;

        if (range < 0 || range === Infinity) {
            guidesContext.fillRect(0, 0, width, height);
        }

        for (var i = -(1 + gridOffset.x / grid.x | 0), lenX = width / grid.x + 1; i < lenX; i++) {
            for (var j = -( 1 + gridOffset.y / grid.y | 0), lenY = height / grid.y + 1; j < lenY; j++) {
                if (range > 0 && range !== Infinity) {
                    guidesContext.circle(i * grid.x + gridOffset.x, j * grid.y + gridOffset.y, range, blue).fill();
                }

                guidesContext.beginPath();
                guidesContext.moveTo(i * grid.x + gridOffset.x, j * grid.y + gridOffset.y - barLength / 2);
                guidesContext.lineTo(i * grid.x + gridOffset.x, j * grid.y + gridOffset.y + barLength / 2);
                guidesContext.stroke();

                guidesContext.beginPath();
                guidesContext.moveTo(i * grid.x + gridOffset.x - barLength / 2, j * grid.y + gridOffset.y);
                guidesContext.lineTo(i * grid.x + gridOffset.x + barLength / 2, j * grid.y + gridOffset.y);
                guidesContext.stroke();
            }
        }
    }

    function drawAnchors (anchors, defaultRange) {
        var barLength = 16;

        guidesContext.clearRect(0, 0, width, height);

        if (range < 0 && range !== Infinity) {
            guidesContext.fillStyle = lightBlue;
            guidesContext.fillRect(0, 0, width, height);
        }

        for (var i = 0, len = anchors.length; i < len; i++) {
            var anchor = anchors[i],
                range = typeof anchor.range === 'number'? anchor.range: defaultRange;

            if (range > 0 && range !== Infinity) {
                guidesContext.circle(anchor.x, anchor.y, range, blue).fill();
            }

            guidesContext.beginPath();
            guidesContext.moveTo(anchor.x, anchor.y - barLength / 2);
            guidesContext.lineTo(anchor.x, anchor.y + barLength / 2);
            guidesContext.stroke();

            guidesContext.beginPath();
            guidesContext.moveTo(anchor.x - barLength / 2, anchor.y);
            guidesContext.lineTo(anchor.x + barLength / 2, anchor.y);
            guidesContext.stroke();
        }
    }

    function drawSnap (snap) {
        context.clearRect(0, 0, width, height);
        guidesContext.clearRect(0, 0, width, height);

        if (!status.offMode.checked) {
            if (snap.mode === 'grid') {
                drawGrid(snap.grid, snap.gridOffset, snap.range);
            }
            else if (snap.mode === 'anchor') {
                drawAnchors(snap.anchors, snap.range);
            }
        }
    }

    function circle (x, y, radius, color) {
        this.fillStyle = color || this.fillStyle;
        this.beginPath();
        this.arc(x, y, radius, 0, 2*Math.PI);

        return this;
    }
    window.CanvasRenderingContext2D.prototype.circle = circle;

    function dragMove (event) {
        var snap = event.snap;

        context.clearRect(0, 0, width, height);

        if (snap && snap.range !== Infinity) {
            context.circle(snap.x, snap.y, snap.range + 1, 'rgba(102, 225, 117, 0.8)').fill();
        }

        context.circle(event.pageX, event.pageY, 10, tango).fill();

        prevX = event.pageX;
        prevY = event.pageY;
    }

    function dragEnd (event) {
        context.clearRect(0, 0, width, height);
        context.circle(event.pageX, event.pageY, 10, tango).fill();

        prevX = event.pageX;
        prevY = event.pageY;
    }

    function anchorDragStart (event) {
        if (event.snap.locked) {
            interact(canvas).snap(false);
            draggingAnchor = event.snap.anchors.closest;
        }
    }

    function anchorDragMove (event) {
        if (draggingAnchor) {
            var snap = interact(canvas).snap();

            draggingAnchor.x += event.dx;
            draggingAnchor.y += event.dy;

            drawAnchors(snap.anchors, snap.range);
        }
    }

    function anchorDragEnd (event) {
        interact(canvas).snap(true);
        draggingAnchor = null;
    }

    function sliderChange (event, valid) {
        if (!valid) {
            return;
        }

        interact(canvas).snap({
            grid: {
                x: Number(status.gridX.value),
                y: Number(status.gridY.value)
            },
            gridOffset: {
                x: Number(status.offsetX.value),
                y: Number(status.offsetY.value)
            },
            range: Number(status.range.value)
        });

        drawSnap(interact(canvas).snap());
    }

    function modeChange (event) {
        var snap = interact(canvas).snap(true).snap();

        if (status.anchorDrag.checked && !status.anchorMode.checked) {
            status.anchorMode.checked = true;
        }

        if (status.anchorDrag.checked) {
            status.anchorMode.disabled = status.offMode.disabled = status.gridMode.disabled = true;
            status.modes.classList.add('disabled');

            interact(canvas)
                .off('dragstart', dragMove)
                .off('dragmove', dragMove)
                .off('dragend', dragEnd)
                .on('dragstart', anchorDragStart)
                .on('dragmove', anchorDragMove)
                .on('dragend', anchorDragEnd);
        }
        else {
            status.anchorMode.disabled = status.offMode.disabled = status.gridMode.disabled = false;
            status.modes.classList.remove('disabled');

            interact(canvas)
                .on('dragstart', dragMove)
                .on('dragmove', dragMove)
                .on('dragend', dragEnd)
                .off('dragstart', anchorDragStart)
                .off('dragmove', anchorDragMove)
                .off('dragend', anchorDragEnd);
        }

        interact(canvas).snap({
            mode: status.anchorMode.checked
                ? 'anchor'
                : 'grid'
            });

        interact(canvas).snap(status.offMode.checked? false: true);

        drawSnap(interact(canvas).snap());
    }

    function sliderInput (event) {
        if (event.target.type === 'range' &&
            (Number(event.target.value) > Number(event.target.max)) ||
            Number(event.target.value) < Number(event.target.min)) {

            return;
        }

        sliderChange(event, true);
    }

    interact.styleCursor(false);

    interact(document).on('DOMContentLoaded', function () {
        canvas = document.getElementById('drag');
        canvas.width = width;
        canvas.height = height;
        context = canvas.getContext('2d');

        interact(canvas)
            .snap({
                mode: 'grid',
                grid: {x: 0, y: 0},
                gridOffset: {x: 0, y: 0},
                range: Infinity,
                anchors: [
                    {x: 100, y: 100, range: 200},
                    {x: 600, y: 400},
                    {x: 500, y: 150},
                    {x: 250, y: 250}
                ]
            })
            .draggable(true);

        guidesCanvas = document.getElementById('grid');
        guidesCanvas.width = width;
        guidesCanvas.height = height;
        guidesContext = guidesCanvas.getContext('2d');

        status = {
            container: document.getElementById('status'),

            sliders: document.getElementById('sliders'),
            gridX: document.getElementById('grid-x'),
            gridY: document.getElementById('grid-y'),
            offsetX: document.getElementById('offset-x'),
            offsetY: document.getElementById('offset-y'),
            range: document.getElementById('snap-range'),

            modes: document.getElementById('modes'),
            offMode: document.getElementById('off-mode'),
            gridMode: document.getElementById('grid-mode'),
            anchorMode: document.getElementById('anchor-mode'),
            anchorDrag: document.getElementById('drag-anchors')
        };

        interact(status.sliders)
            .on('change', sliderChange)
            .on('input', sliderInput);

        interact(document.getElementById('modes'))
            .on('change', modeChange);

        sliderChange(null, true);
        modeChange();
    });

    window.grid = {
        drawGrid: drawGrid
    };

}(window.interact));
<commit_msg>demo/snap: enable inertia<commit_after>/*
 * Copyright (c) 2013 Taye Adeyemi
 * Open source under the MIT License.
 * https://raw.github.com/taye/interact.js/master/LICENSE
 */

(function(interact) {
    'use strict';

    var canvas,
        context,
        guidesCanvas,
        guidesContext,
        width = 800,
        height = 800,
        status,
        prevX = 0,
        prevY = 0,
        blue = '#2299ee',
        lightBlue = '#88ccff',
        peppermint = '#44ee44',
        tango = '#ff4400',
        draggingAnchor = null;

    function drawGrid (grid, gridOffset, range) {
        if (!grid.x || !grid.y) { return; }

        var barLength = 16;

        guidesContext.clearRect(0, 0, width, height);

        guidesContext.fillStyle = lightBlue;

        if (range < 0 || range === Infinity) {
            guidesContext.fillRect(0, 0, width, height);
        }

        for (var i = -(1 + gridOffset.x / grid.x | 0), lenX = width / grid.x + 1; i < lenX; i++) {
            for (var j = -( 1 + gridOffset.y / grid.y | 0), lenY = height / grid.y + 1; j < lenY; j++) {
                if (range > 0 && range !== Infinity) {
                    guidesContext.circle(i * grid.x + gridOffset.x, j * grid.y + gridOffset.y, range, blue).fill();
                }

                guidesContext.beginPath();
                guidesContext.moveTo(i * grid.x + gridOffset.x, j * grid.y + gridOffset.y - barLength / 2);
                guidesContext.lineTo(i * grid.x + gridOffset.x, j * grid.y + gridOffset.y + barLength / 2);
                guidesContext.stroke();

                guidesContext.beginPath();
                guidesContext.moveTo(i * grid.x + gridOffset.x - barLength / 2, j * grid.y + gridOffset.y);
                guidesContext.lineTo(i * grid.x + gridOffset.x + barLength / 2, j * grid.y + gridOffset.y);
                guidesContext.stroke();
            }
        }
    }

    function drawAnchors (anchors, defaultRange) {
        var barLength = 16;

        guidesContext.clearRect(0, 0, width, height);

        if (range < 0 && range !== Infinity) {
            guidesContext.fillStyle = lightBlue;
            guidesContext.fillRect(0, 0, width, height);
        }

        for (var i = 0, len = anchors.length; i < len; i++) {
            var anchor = anchors[i],
                range = typeof anchor.range === 'number'? anchor.range: defaultRange;

            if (range > 0 && range !== Infinity) {
                guidesContext.circle(anchor.x, anchor.y, range, blue).fill();
            }

            guidesContext.beginPath();
            guidesContext.moveTo(anchor.x, anchor.y - barLength / 2);
            guidesContext.lineTo(anchor.x, anchor.y + barLength / 2);
            guidesContext.stroke();

            guidesContext.beginPath();
            guidesContext.moveTo(anchor.x - barLength / 2, anchor.y);
            guidesContext.lineTo(anchor.x + barLength / 2, anchor.y);
            guidesContext.stroke();
        }
    }

    function drawSnap (snap) {
        context.clearRect(0, 0, width, height);
        guidesContext.clearRect(0, 0, width, height);

        if (!status.offMode.checked) {
            if (snap.mode === 'grid') {
                drawGrid(snap.grid, snap.gridOffset, snap.range);
            }
            else if (snap.mode === 'anchor') {
                drawAnchors(snap.anchors, snap.range);
            }
        }
    }

    function circle (x, y, radius, color) {
        this.fillStyle = color || this.fillStyle;
        this.beginPath();
        this.arc(x, y, radius, 0, 2*Math.PI);

        return this;
    }
    window.CanvasRenderingContext2D.prototype.circle = circle;

    function dragMove (event) {
        var snap = event.snap;

        context.clearRect(0, 0, width, height);

        if (snap && snap.range !== Infinity) {
            context.circle(snap.x, snap.y, snap.range + 1, 'rgba(102, 225, 117, 0.8)').fill();
        }

        context.circle(event.pageX, event.pageY, 10, tango).fill();

        prevX = event.pageX;
        prevY = event.pageY;
    }

    function dragEnd (event) {
        context.clearRect(0, 0, width, height);
        context.circle(event.pageX, event.pageY, 10, tango).fill();

        prevX = event.pageX;
        prevY = event.pageY;
    }

    function anchorDragStart (event) {
        if (event.snap.locked) {
            interact(canvas).snap(false);
            draggingAnchor = event.snap.anchors.closest;
        }
    }

    function anchorDragMove (event) {
        if (draggingAnchor) {
            var snap = interact(canvas).snap();

            draggingAnchor.x += event.dx;
            draggingAnchor.y += event.dy;

            drawAnchors(snap.anchors, snap.range);
        }
    }

    function anchorDragEnd (event) {
        interact(canvas).snap(true);
        draggingAnchor = null;
    }

    function sliderChange (event, valid) {
        if (!valid) {
            return;
        }

        interact(canvas).snap({
            grid: {
                x: Number(status.gridX.value),
                y: Number(status.gridY.value)
            },
            gridOffset: {
                x: Number(status.offsetX.value),
                y: Number(status.offsetY.value)
            },
            range: Number(status.range.value)
        });

        drawSnap(interact(canvas).snap());
    }

    function modeChange (event) {
        var snap = interact(canvas).snap(true).snap();

        if (status.anchorDrag.checked && !status.anchorMode.checked) {
            status.anchorMode.checked = true;
        }

        if (status.anchorDrag.checked) {
            status.anchorMode.disabled = status.offMode.disabled = status.gridMode.disabled = true;
            status.modes.classList.add('disabled');

            interact(canvas)
                .off('dragstart', dragMove)
                .off('dragmove', dragMove)
                .off('dragend', dragEnd)
                .on('dragstart', anchorDragStart)
                .on('dragmove', anchorDragMove)
                .on('dragend', anchorDragEnd);
        }
        else {
            status.anchorMode.disabled = status.offMode.disabled = status.gridMode.disabled = false;
            status.modes.classList.remove('disabled');

            interact(canvas)
                .on('dragstart', dragMove)
                .on('dragmove', dragMove)
                .on('dragend', dragEnd)
                .off('dragstart', anchorDragStart)
                .off('dragmove', anchorDragMove)
                .off('dragend', anchorDragEnd);
        }

        interact(canvas).snap({
            mode: status.anchorMode.checked
                ? 'anchor'
                : 'grid'
            });

        interact(canvas).snap(status.offMode.checked? false: true);

        drawSnap(interact(canvas).snap());
    }

    function sliderInput (event) {
        if (event.target.type === 'range' &&
            (Number(event.target.value) > Number(event.target.max)) ||
            Number(event.target.value) < Number(event.target.min)) {

            return;
        }

        sliderChange(event, true);
    }

    interact.styleCursor(false);

    interact(document).on('DOMContentLoaded', function () {
        canvas = document.getElementById('drag');
        canvas.width = width;
        canvas.height = height;
        context = canvas.getContext('2d');

        interact(canvas)
            .snap({
                mode: 'grid',
                grid: {x: 0, y: 0},
                gridOffset: {x: 0, y: 0},
                range: Infinity,
                anchors: [
                    {x: 100, y: 100, range: 200},
                    {x: 600, y: 400},
                    {x: 500, y: 150},
                    {x: 250, y: 250}
                ]
            })
            .inertia({
                resisitance: 10,
                minSpeed: 100,
                endSpeed: 10
            })
            .draggable(true);

        guidesCanvas = document.getElementById('grid');
        guidesCanvas.width = width;
        guidesCanvas.height = height;
        guidesContext = guidesCanvas.getContext('2d');

        status = {
            container: document.getElementById('status'),

            sliders: document.getElementById('sliders'),
            gridX: document.getElementById('grid-x'),
            gridY: document.getElementById('grid-y'),
            offsetX: document.getElementById('offset-x'),
            offsetY: document.getElementById('offset-y'),
            range: document.getElementById('snap-range'),

            modes: document.getElementById('modes'),
            offMode: document.getElementById('off-mode'),
            gridMode: document.getElementById('grid-mode'),
            anchorMode: document.getElementById('anchor-mode'),
            anchorDrag: document.getElementById('drag-anchors')
        };

        interact(status.sliders)
            .on('change', sliderChange)
            .on('input', sliderInput);

        interact(document.getElementById('modes'))
            .on('change', modeChange);

        sliderChange(null, true);
        modeChange();
    });

    window.grid = {
        drawGrid: drawGrid
    };

}(window.interact));
<|endoftext|>


<commit_before>'use strict';

var es = require('event-stream');
var Long = require('long');
var decoders = require('sgvdata/lib/protobuf');
var direction = require('sgvdata/lib/utils').direction;
var mqtt = require('mqtt');
var moment = require('moment');

function process ( ) {
  var stream = es.through(
    function _write(data) {
      this.push(data);
    }
  );
  return stream;
}

function every (storage) {
  function iter(item, next) {
    storage.create(item, next);
  }

  return es.map(iter);
}

function downloader () {
  var opts = {
    model: decoders.models.G4Download
    , json: function (o) {
      return o;
    }
    , payload: function (o) {
      return o;
    }
  };
  return decoders(opts);
}

function toSGV (proto, vars) {
  vars.sgv = proto.sgv_mgdl;
  vars.direction = direction(proto.trend);
  vars.noise = proto.noise;
  vars.type = 'sgv';
  return vars;
}

function toCal (proto, vars) {
  vars.slope = proto.slope;
  vars.intercept = proto.intercept;
  vars.scale = proto.scale;
  vars.type = 'cal';
  return vars;
}

function toSensor (proto, vars) {
  vars.filtered = new Long(proto.filtered).toInt();
  vars.unfiltered = new Long(proto.unfiltered).toInt();
  vars.rssi = proto.rssi;
  vars.type = 'sensor';
  return vars;
}

function toMeter (proto, result) {
  result.type = 'mbg';
  result.mbg = proto.mbg || proto.meter_bg_mgdl;
  return result;
}

function toTimestamp (proto, receiver_time, download_time) {
  var record_offset = receiver_time - proto.sys_timestamp_sec;
  var record_time = download_time.clone( ).subtract(record_offset, 'second');
  var obj = {
    device: 'dexcom'
    , date: record_time.unix() * 1000
    , dateString: record_time.format( )
  };
  return obj;
}

function sgvSensorMerge(packet) {
  var receiver_time = packet.receiver_system_time_sec;
  var download_time = moment(packet.download_timestamp);

  function timestamp(item) {
    return toTimestamp(item, receiver_time, download_time.clone( ));
  }

  function timeSort(a, b) {
    return a.date - b.date;
  }

  var sgvs = (packet['sgv'] || []).map(function(sgv) {
    var timestamped = timestamp(sgv);
    return toSGV(sgv, timestamped);
  }).sort(timeSort);

  var sensors = (packet['sensor'] || []).map(function(sensor) {
    var timestamped = timestamp(sensor);
    return toSensor(sensor, timestamped);
  }).sort(timeSort);

  //based on com.nightscout.core.dexcom.Utils#mergeGlucoseDataRecords
  var merged = []
    , sgvsLength = sgvs.length
    , sensorsLength = sensors.length;

  if (sgvsLength >= 0 && sensorsLength == 0) {
    merged = sgvs;
  } else {
    var smallerLength = Math.min(sgvsLength, sensorsLength);
    for (var i = 1; i <= smallerLength; i++) {
      var sgv = sgvs[sgvsLength - i];
      var sensor = sensors[sensorsLength - i];
      if (sgv && sensor && Math.abs(sgv.date - sensor.date) < 10000) {
        //timestamps are close so merge
        sgv.filtered = sensor.filtered;
        sgv.unfiltered = sensor.unfiltered;
        sgv.rssi = sensor.rssi;
        merged.push(sgv);
      } else {
        console.info('mismatch or missing, sgv: ', sgv, ' sensor: ', sensor);
        //timestamps aren't close enough so add both
        if (sgv) { merged.push(sgv); }
        //but the sensor will become and sgv now
        if (sensor) {
          sensor.type = 'sgv';
          merged.push(sensor);
        }
      }
    }

    //any extra sgvs?
    if (sgvsLength > smallerLength) {
      for (var j = 0; j < sgvsLength - smallerLength; j++) {
        var extraSGV = sgvs[j];
        merged.push(extraSGV);
      }
    }

    //any extra sensors?
    if (sensorsLength > smallerLength) {
      for (var k = 0; k < sensorsLength - smallerLength; k++) {
        var extraSensor = sensors[k];
        //from now on we consider it a sgv
        extraSensor.type = 'sgv';
        merged.push(extraSensor);
      }
    }

  }

  return merged;
}

function iter_mqtt_record_stream (packet, prop, sync) {
  var list = packet[prop];
  console.log('incoming', prop, (list || [ ]).length);
  var stream = es.readArray(list || [ ]);
  var receiver_time = packet.receiver_system_time_sec;
  var download_time = moment(packet.download_timestamp);
  function map(item, next) {
    var timestamped = toTimestamp(item, receiver_time, download_time.clone( ));
    var r = sync(item, timestamped);
    if (!('type' in r)) {
      r.type = prop;
    }
    console.log('ITEM', item, 'TO', prop, r);
    next(null, r);
  }
  return stream.pipe(es.map(map));
}

function configure(env, ctx) {
  var uri = env['MQTT_MONITOR'];
  var opts = {
    encoding: 'binary',
    clean: false,
    clientId: env.mqtt_client_id
  };
  var client = mqtt.connect(uri, opts);
  var downloads = downloader();
  client.subscribe('sgvs');
  client.subscribe('published');
  client.subscribe('/downloads/protobuf', {qos: 2}, granted);
  client.subscribe('/uploader', granted);
  client.subscribe('/entries/sgv', granted);
  function granted() {
    console.log('granted', arguments);
  }

  client.on('message', function (topic, msg) {
    console.log('topic', topic);
    console.log(topic, 'on message', 'msg', msg.length);
    switch (topic) {
      case '/uploader':
        console.log({type: topic, msg: msg.toString()});
        break;
      case '/downloads/protobuf':
        var b = new Buffer(msg, 'binary');
        console.log('BINARY', b.length, b.toString('hex'));
        try {
          var packet = downloads.parse(b);
          if (!packet.type) {
            packet.type = topic;
          }
        } catch (e) {
          console.log('DID NOT PARSE', e);
          break;
        }
        console.log('DOWNLOAD msg', msg.length, packet);
        console.log('download SGV', packet.sgv[0]);
        console.log('download_timestamp', packet.download_timestamp, new Date(Date.parse(packet.download_timestamp)));
        console.log('WRITE TO MONGO');
        var download_timestamp = moment(packet.download_timestamp);
        if (packet.download_status === 0) {
          es.readArray(sgvSensorMerge(packet)).pipe(ctx.entries.persist(function empty(err, result) {
            console.log('DONE WRITING MERGED SGV TO MONGO', err, result);
          }));

          iter_mqtt_record_stream(packet, 'cal', toCal)
            .pipe(ctx.entries.persist(function empty(err, result) {
              console.log('DONE WRITING Cal TO MONGO', err, result.length);
            }));
          iter_mqtt_record_stream(packet, 'meter', toMeter)
            .pipe(ctx.entries.persist(function empty(err, result) {
              console.log('DONE WRITING Meter TO MONGO', err, result.length);
            }));
        }
        packet.type = 'download';
        ctx.devicestatus.create({
          uploaderBattery: packet.uploader_battery,
          created_at: download_timestamp.toISOString()
        }, function empty(err, result) {
          console.log('DONE WRITING TO MONGO devicestatus ', result, err);
        });

        ctx.entries.create([ packet ], function empty(err, res) {
          console.log('Download written to mongo: ', packet);
        });

        // ctx.entries.write(packet);
        break;
      default:
        console.log(topic, 'on message', 'msg', msg);
        // ctx.entries.write(msg);
        break;
    }
  });
  client.entries = process(client);
  client.every = every;
  return client;
}

//expose for tests that don't need to connect
configure.sgvSensorMerge = sgvSensorMerge;

module.exports = configure;
<commit_msg>mqtt fixes for issues reported by codacy<commit_after>'use strict';

var es = require('event-stream');
var Long = require('long');
var decoders = require('sgvdata/lib/protobuf');
var direction = require('sgvdata/lib/utils').direction;
var mqtt = require('mqtt');
var moment = require('moment');

function process ( ) {
  var stream = es.through(
    function _write(data) {
      this.push(data);
    }
  );
  return stream;
}

function every (storage) {
  function iter(item, next) {
    storage.create(item, next);
  }

  return es.map(iter);
}

function downloader () {
  var opts = {
    model: decoders.models.G4Download
    , json: function (o) {
      return o;
    }
    , payload: function (o) {
      return o;
    }
  };
  return decoders(opts);
}

function toSGV (proto, vars) {
  vars.sgv = proto.sgv_mgdl;
  vars.direction = direction(proto.trend);
  vars.noise = proto.noise;
  vars.type = 'sgv';
  return vars;
}

function toCal (proto, vars) {
  vars.slope = proto.slope;
  vars.intercept = proto.intercept;
  vars.scale = proto.scale;
  vars.type = 'cal';
  return vars;
}

function toSensor (proto, vars) {
  vars.filtered = new Long(proto.filtered).toInt();
  vars.unfiltered = new Long(proto.unfiltered).toInt();
  vars.rssi = proto.rssi;
  vars.type = 'sensor';
  return vars;
}

function toMeter (proto, result) {
  result.type = 'mbg';
  result.mbg = proto.mbg || proto.meter_bg_mgdl;
  return result;
}

function toTimestamp (proto, receiver_time, download_time) {
  var record_offset = receiver_time - proto.sys_timestamp_sec;
  var record_time = download_time.clone( ).subtract(record_offset, 'second');
  var obj = {
    device: 'dexcom'
    , date: record_time.unix() * 1000
    , dateString: record_time.format( )
  };
  return obj;
}

function sgvSensorMerge(packet) {
  var receiver_time = packet.receiver_system_time_sec;
  var download_time = moment(packet.download_timestamp);

  function timestamp(item) {
    return toTimestamp(item, receiver_time, download_time.clone( ));
  }

  function timeSort(a, b) {
    return a.date - b.date;
  }

  var sgvs = (packet['sgv'] || []).map(function(sgv) {
    var timestamped = timestamp(sgv);
    return toSGV(sgv, timestamped);
  }).sort(timeSort);

  var sensors = (packet['sensor'] || []).map(function(sensor) {
    var timestamped = timestamp(sensor);
    return toSensor(sensor, timestamped);
  }).sort(timeSort);

  //based on com.nightscout.core.dexcom.Utils#mergeGlucoseDataRecords
  var merged = []
    , sgvsLength = sgvs.length
    , sensorsLength = sensors.length;

  if (sgvsLength >= 0 && sensorsLength === 0) {
    merged = sgvs;
  } else {
    var smallerLength = Math.min(sgvsLength, sensorsLength);
    for (var i = 1; i <= smallerLength; i++) {
      var sgv = sgvs[sgvsLength - i];
      var sensor = sensors[sensorsLength - i];
      if (sgv && sensor && Math.abs(sgv.date - sensor.date) < 10000) {
        //timestamps are close so merge
        sgv.filtered = sensor.filtered;
        sgv.unfiltered = sensor.unfiltered;
        sgv.rssi = sensor.rssi;
        merged.push(sgv);
      } else {
        console.info('mismatch or missing, sgv: ', sgv, ' sensor: ', sensor);
        //timestamps aren't close enough so add both
        if (sgv) { merged.push(sgv); }
        //but the sensor will become and sgv now
        if (sensor) {
          sensor.type = 'sgv';
          merged.push(sensor);
        }
      }
    }

    //any extra sgvs?
    if (sgvsLength > smallerLength) {
      for (var j = 0; j < sgvsLength - smallerLength; j++) {
        var extraSGV = sgvs[j];
        merged.push(extraSGV);
      }
    }

    //any extra sensors?
    if (sensorsLength > smallerLength) {
      for (var k = 0; k < sensorsLength - smallerLength; k++) {
        var extraSensor = sensors[k];
        //from now on we consider it a sgv
        extraSensor.type = 'sgv';
        merged.push(extraSensor);
      }
    }

  }

  return merged;
}

function iter_mqtt_record_stream (packet, prop, sync) {
  var list = packet[prop];
  console.log('incoming', prop, (list || [ ]).length);
  var stream = es.readArray(list || [ ]);
  var receiver_time = packet.receiver_system_time_sec;
  var download_time = moment(packet.download_timestamp);
  function map(item, next) {
    var timestamped = toTimestamp(item, receiver_time, download_time.clone( ));
    var r = sync(item, timestamped);
    if (!('type' in r)) {
      r.type = prop;
    }
    console.log('ITEM', item, 'TO', prop, r);
    next(null, r);
  }
  return stream.pipe(es.map(map));
}

function configure(env, ctx) {
  var uri = env['MQTT_MONITOR'];
  var opts = {
    encoding: 'binary',
    clean: false,
    clientId: env.mqtt_client_id
  };
  var client = mqtt.connect(uri, opts);
  var downloads = downloader();
  client.subscribe('sgvs');
  client.subscribe('published');
  client.subscribe('/downloads/protobuf', {qos: 2}, granted);
  client.subscribe('/uploader', granted);
  client.subscribe('/entries/sgv', granted);
  function granted() {
    console.log('granted', arguments);
  }

  client.on('message', function (topic, msg) {
    console.log('topic', topic);
    console.log(topic, 'on message', 'msg', msg.length);
    switch (topic) {
      case '/uploader':
        console.log({type: topic, msg: msg.toString()});
        break;
      case '/downloads/protobuf':
        var b = new Buffer(msg, 'binary');
        console.log('BINARY', b.length, b.toString('hex'));
        try {
          var packet = downloads.parse(b);
          if (!packet.type) {
            packet.type = topic;

          }
          console.log('DOWNLOAD msg', msg.length, packet);
          console.log('download SGV', packet.sgv[0]);
          console.log('download_timestamp', packet.download_timestamp, new Date(Date.parse(packet.download_timestamp)));
          console.log('WRITE TO MONGO');
          var download_timestamp = moment(packet.download_timestamp);
          if (packet.download_status === 0) {
            es.readArray(sgvSensorMerge(packet)).pipe(ctx.entries.persist(function empty(err, result) {
              console.log('DONE WRITING MERGED SGV TO MONGO', err, result);
            }));

            iter_mqtt_record_stream(packet, 'cal', toCal)
              .pipe(ctx.entries.persist(function empty(err, result) {
                console.log('DONE WRITING Cal TO MONGO', err, result.length);
              }));
            iter_mqtt_record_stream(packet, 'meter', toMeter)
              .pipe(ctx.entries.persist(function empty(err, result) {
                console.log('DONE WRITING Meter TO MONGO', err, result.length);
              }));
          }
          packet.type = 'download';
          ctx.devicestatus.create({
            uploaderBattery: packet.uploader_battery,
            created_at: download_timestamp.toISOString()
          }, function empty(err, result) {
            console.log('DONE WRITING TO MONGO devicestatus ', result, err);
          });

          ctx.entries.create([ packet ], function empty(err, res) {
            console.log('Download written to mongo: ', packet);
          });
        } catch (e) {
          console.log('DID NOT PARSE', e);
        }

      default:
        console.log(topic, 'on message', 'msg', msg);
        // ctx.entries.write(msg);
        break;
    }
  });
  client.entries = process(client);
  client.every = every;
  return client;
}

//expose for tests that don't need to connect
configure.sgvSensorMerge = sgvSensorMerge;

module.exports = configure;
<|endoftext|>