<commit_before>const {contains} = require("../util/dom")
const browser = require("../util/browser")

const {posFromDOM, DOMAfterPos, DOMFromPos, coordsAtPos} = require("./dompos")

// Track the state of the current editor selection. Keeps the editor
// selection in sync with the DOM selection by polling for changes,
// as there is no DOM event for DOM selection changes.
class SelectionState {
  constructor(pm, range) {
    this.pm = pm
    // The current editor selection.
    this.range = range

    // The timeout ID for the poller when active.
    this.polling = null
    // Track the state of the DOM selection.
    this.lastAnchorNode = this.lastHeadNode = this.lastAnchorOffset = this.lastHeadOffset = null
    // The corresponding DOM node when a node selection is active.
    this.lastNode = null

    pm.content.addEventListener("focus", () => this.receivedFocus())

    this.poller = this.poller.bind(this)
  }

  // : (Selection, boolean)
  // Set the current selection and signal an event on the editor.
  setAndSignal(range, clearLast) {
    this.set(range, clearLast)
    // :: () #path=ProseMirror#events#selectionChange
    // Indicates that the editor's selection has changed.
    this.pm.signal("selectionChange")
  }

  // : (Selection, boolean)
  // Set the current selection.
  set(range, clearLast) {
    this.pm.ensureOperation({readSelection: false, selection: range})
    this.range = range
    if (clearLast !== false) this.lastAnchorNode = null
  }

  poller() {
    if (hasFocus(this.pm)) {
      if (!this.pm.operation) this.readFromDOM()
      this.polling = setTimeout(this.poller, 100)
    } else {
      this.polling = null
    }
  }

  startPolling() {
    clearTimeout(this.polling)
    this.polling = setTimeout(this.poller, 50)
  }

  fastPoll() {
    this.startPolling()
  }

  stopPolling() {
    clearTimeout(this.polling)
    this.polling = null
  }

  // : () → bool
  // Whether the DOM selection has changed from the last known state.
  domChanged() {
    let sel = window.getSelection()
    return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||
      sel.focusNode != this.lastHeadNode || sel.focusOffset != this.lastHeadOffset
  }

  // Store the current state of the DOM selection.
  storeDOMState() {
    let sel = window.getSelection()
    this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset
    this.lastHeadNode = sel.focusNode; this.lastHeadOffset = sel.focusOffset
  }

  // : () → bool
  // When the DOM selection changes in a notable manner, modify the
  // current selection state to match.
  readFromDOM() {
    if (!hasFocus(this.pm) || !this.domChanged()) return false

    let {range, adjusted} = selectionFromDOM(this.pm, this.pm.doc, this.range.head)
    this.setAndSignal(range)

    if (range instanceof NodeSelection || adjusted) {
      this.toDOM()
    } else {
      this.clearNode()
      this.storeDOMState()
    }
    return true
  }

  toDOM(takeFocus) {
    if (!hasFocus(this.pm)) {
      if (!takeFocus) return
      // See https://bugzilla.mozilla.org/show_bug.cgi?id=921444
      else if (browser.gecko) this.pm.content.focus()
    }
    if (this.range instanceof NodeSelection)
      this.nodeToDOM()
    else
      this.rangeToDOM()
  }

  // Make changes to the DOM for a node selection.
  nodeToDOM() {
    let dom = DOMAfterPos(this.pm, this.range.from)
    if (dom != this.lastNode) {
      this.clearNode()
      dom.classList.add("ProseMirror-selectednode")
      this.pm.content.classList.add("ProseMirror-nodeselection")
      this.lastNode = dom
    }
    let range = document.createRange(), sel = window.getSelection()
    range.selectNode(dom)
    sel.removeAllRanges()
    sel.addRange(range)
    this.storeDOMState()
  }

  // Make changes to the DOM for a text selection.
  rangeToDOM() {
    this.clearNode()

    let anchor = DOMFromPos(this.pm, this.range.anchor)
    let head = DOMFromPos(this.pm, this.range.head)

    let sel = window.getSelection(), range = document.createRange()
    if (sel.extend) {
      range.setEnd(anchor.node, anchor.offset)
      range.collapse(false)
    } else {
      if (this.range.anchor > this.range.head) { let tmp = anchor; anchor = head; head = tmp }
      range.setEnd(head.node, head.offset)
      range.setStart(anchor.node, anchor.offset)
    }
    sel.removeAllRanges()
    sel.addRange(range)
    if (sel.extend)
      sel.extend(head.node, head.offset)
    this.storeDOMState()
  }

  // Clear all DOM statefulness of the last node selection.
  clearNode() {
    if (this.lastNode) {
      this.lastNode.classList.remove("ProseMirror-selectednode")
      this.pm.content.classList.remove("ProseMirror-nodeselection")
      this.lastNode = null
      return true
    }
  }

  receivedFocus() {
    if (this.polling == null) this.startPolling()
  }
}
exports.SelectionState = SelectionState

// ;; An editor selection. Can be one of two selection types:
// `TextSelection` and `NodeSelection`. Both have the properties
// listed here, but also contain more information (such as the
// selected [node](#NodeSelection.node) or the
// [head](#TextSelection.head) and [anchor](#TextSelection.anchor)).
class Selection {
  // :: number
  // The left bound of the selection.
  get from() { return this.$from.pos }

  // :: number
  // The right bound of the selection.
  get to() { return this.$to.pos }

  constructor($from, $to) {
    // :: ResolvedPos
    // The resolved left bound of the selection
    this.$from = $from
    // :: ResolvedPos
    // The resolved right bound of the selection
    this.$to = $to
  }

  // :: bool
  // True if the selection is an empty text selection (head an anchor
  // are the same).
  get empty() {
    return this.from == this.to
  }

  // :: (other: Selection) → bool #path=Selection.prototype.eq
  // Test whether the selection is the same as another selection.

  // :: (doc: Node, mapping: Mappable) → Selection #path=Selection.prototype.map
  // Map this selection through a [mappable](#Mappable) thing. `doc`
  // should be the new document, to which we are mapping.
}
exports.Selection = Selection

// ;; A text selection represents a classical editor
// selection, with a head (the moving side) and anchor (immobile
// side), both of which point into textblock nodes. It can be empty (a
// regular cursor position).
class TextSelection extends Selection {
  // :: number
  // The selection's immobile side (does not move when pressing
  // shift-arrow).
  get anchor() { return this.$anchor.pos }
  // :: number
  // The selection's mobile side (the side that moves when pressing
  // shift-arrow).
  get head() { return this.$head.pos }

  // :: (ResolvedPos, ?ResolvedPos)
  // Construct a text selection. When `head` is not given, it defaults
  // to `anchor`.
  constructor($anchor, $head = $anchor) {
    let inv = $anchor.pos > $head.pos
    super(inv ? $head : $anchor, inv ? $anchor : $head)
    // :: ResolvedPos The resolved anchor of the selection.
    this.$anchor = $anchor
    // :: ResolvedPos The resolved head of the selection.
    this.$head = $head
  }

  get inverted() { return this.anchor > this.head }

  eq(other) {
    return other instanceof TextSelection && other.head == this.head && other.anchor == this.anchor
  }

  map(doc, mapping) {
    let $head = doc.resolve(mapping.map(this.head))
    if (!$head.parent.isTextblock) return findSelectionNear($head)
    let $anchor = doc.resolve(mapping.map(this.anchor))
    return new TextSelection($anchor.parent.isTextblock ? $anchor : $head, $head)
  }

  get token() {
    return new SelectionToken(TextSelection, this.anchor, this.head)
  }

  static mapToken(token, mapping) {
    return new SelectionToken(TextSelection, mapping.map(token.a), mapping.map(token.b))
  }

  static fromToken(token, doc) {
    let $head = doc.resolve(token.b)
    if (!$head.parent.isTextblock) return findSelectionNear($head)
    let $anchor = doc.resolve(token.a)
    return new TextSelection($anchor.parent.isTextblock ? $anchor : $head, $head)
  }
}
exports.TextSelection = TextSelection

// ;; A node selection is a selection that points at a
// single node. All nodes marked [selectable](#NodeType.selectable)
// can be the target of a node selection. In such an object, `from`
// and `to` point directly before and after the selected node.
class NodeSelection extends Selection {
  // :: (ResolvedPos)
  // Create a node selection. Does not verify the validity of its
  // argument. Use `ProseMirror.setNodeSelection` for an easier,
  // error-checking way to create a node selection.
  constructor($from) {
    let $to = $from.plusOne()
    super($from, $to)
    // :: Node The selected node.
    this.node = $from.nodeAfter
  }

  eq(other) {
    return other instanceof NodeSelection && this.from == other.from
  }

  map(doc, mapping) {
    let $from = doc.resolve(mapping.map(this.from, 1))
    let to = mapping.map(this.to, -1)
    let node = $from.nodeAfter
    if (node && to == $from.pos + node.nodeSize && node.type.selectable)
      return new NodeSelection($from)
    return findSelectionNear($from)
  }

  get token() {
    return new SelectionToken(NodeSelection, this.from, this.to)
  }

  static mapToken(token, mapping) {
    return new SelectionToken(NodeSelection, mapping.map(token.a, 1), mapping.map(token.b, -1))
  }

  static fromToken(token, doc) {
    let $from = doc.resolve(token.a), node = $from.nodeAfter
    if (node && token.b == token.a + node.nodeSize && node.type.selectable)
      return new NodeSelection($from)
    return findSelectionNear($from)
  }
}
exports.NodeSelection = NodeSelection

class SelectionToken {
  constructor(type, a, b) {
    this.type = type
    this.a = a
    this.b = b
  }
}

function selectionFromDOM(pm, doc, oldHead, loose) {
  let sel = window.getSelection()
  let anchor = posFromDOM(pm, sel.anchorNode, sel.anchorOffset, loose)
  let head = sel.isCollapsed ? anchor : posFromDOM(pm, sel.focusNode, sel.focusOffset, loose)

  let range = findSelectionNear(doc.resolve(head), oldHead != null && oldHead < head ? 1 : -1)
  if (range instanceof TextSelection) {
    let selNearAnchor = findSelectionNear(doc.resolve(anchor), anchor > range.to ? -1 : 1, true)
    range = new TextSelection(selNearAnchor.$anchor, range.$head)
  } else if (anchor < range.from || anchor > range.to) {
    // If head falls on a node, but anchor falls outside of it,
    // create a text selection between them
    let inv = anchor > range.to
    range = new TextSelection(findSelectionNear(doc.resolve(anchor), inv ? -1 : 1, true).$anchor,
                              findSelectionNear(inv ? range.$from : range.$to, inv ? 1 : -1, true).$head)
  }
  return {range, adjusted: head != range.head || anchor != range.anchor}
}
exports.selectionFromDOM = selectionFromDOM

function hasFocus(pm) {
  if (document.activeElement != pm.content) return false
  let sel = window.getSelection()
  return sel.rangeCount && contains(pm.content, sel.anchorNode)
}
exports.hasFocus = hasFocus

// Try to find a selection inside the given node. `pos` points at the
// position where the search starts. When `text` is true, only return
// text selections.
function findSelectionIn(doc, node, pos, index, dir, text) {
  if (node.isTextblock) return new TextSelection(doc.resolve(pos + dir))
  for (let i = index - (dir > 0 ? 0 : 1); dir > 0 ? i < node.childCount : i >= 0; i += dir) {
    let child = node.child(i)
    if (!child.type.isLeaf) {
      let inner = findSelectionIn(doc, child, pos + dir, dir < 0 ? child.childCount : 0, dir, text)
      if (inner) return inner
    } else if (!text && child.type.selectable) {
      return new NodeSelection(doc.resolve(pos - (dir < 0 ? child.nodeSize : 0)))
    }
    pos += child.nodeSize * dir
  }
}

// FIXME we'll need some awareness of text direction when scanning for selections

// Create a selection which is moved relative to a position in a
// given direction. When a selection isn't found at the given position,
// walks up the document tree one level and one step in the
// desired direction.
function findSelectionFrom($pos, dir, text) {
  let inner = $pos.parent.isTextblock ? new TextSelection($pos)
      : findSelectionIn($pos.node(0), $pos.parent, $pos.pos, $pos.index(), dir, text)
  if (inner) return inner

  for (let depth = $pos.depth - 1; depth >= 0; depth--) {
    let found = dir < 0
        ? findSelectionIn($pos.node(0), $pos.node(depth), $pos.before(depth + 1), $pos.index(depth), dir, text)
        : findSelectionIn($pos.node(0), $pos.node(depth), $pos.after(depth + 1), $pos.index(depth) + 1, dir, text)
    if (found) return found
  }
}
exports.findSelectionFrom = findSelectionFrom

function findSelectionNear($pos, bias = 1, text) {
  let result = findSelectionFrom($pos, bias, text) ||
      findSelectionFrom($pos, -bias, text)
  if (!result) throw new RangeError("Searching for selection in invalid document " + $pos.node(0))
  return result
}
exports.findSelectionNear = findSelectionNear

// Find the selection closest to the start of the given node. `pos`,
// if given, should point at the start of the node's content.
function findSelectionAtStart(doc, text) {
  return findSelectionIn(doc, doc, 0, 0, 1, text)
}
exports.findSelectionAtStart = findSelectionAtStart

// Find the selection closest to the end of the given node.
function findSelectionAtEnd(doc, text) {
  return findSelectionIn(doc, doc, doc.content.size, doc.childCount, -1, text)
}
exports.findSelectionAtEnd = findSelectionAtEnd

// : (ProseMirror, number, number)
// Whether vertical position motion in a given direction
// from a position would leave a text block.
function verticalMotionLeavesTextblock(pm, $pos, dir) {
  let dom = $pos.depth ? DOMAfterPos(pm, $pos.before()) : pm.content
  let coords = coordsAtPos(pm, $pos.pos)
  for (let child = dom.firstChild; child; child = child.nextSibling) {
    if (child.nodeType != 1) continue
    let boxes = child.getClientRects()
    for (let i = 0; i < boxes.length; i++) {
      let box = boxes[i]
      if (dir < 0 ? box.bottom < coords.top : box.top > coords.bottom)
        return false
    }
  }
  return true
}
exports.verticalMotionLeavesTextblock = verticalMotionLeavesTextblock
<commit_msg>Fix bug introduced in 0c7aff3753257ea3<commit_after>const {contains} = require("../util/dom")
const browser = require("../util/browser")

const {posFromDOM, DOMAfterPos, DOMFromPos, coordsAtPos} = require("./dompos")

// Track the state of the current editor selection. Keeps the editor
// selection in sync with the DOM selection by polling for changes,
// as there is no DOM event for DOM selection changes.
class SelectionState {
  constructor(pm, range) {
    this.pm = pm
    // The current editor selection.
    this.range = range

    // The timeout ID for the poller when active.
    this.polling = null
    // Track the state of the DOM selection.
    this.lastAnchorNode = this.lastHeadNode = this.lastAnchorOffset = this.lastHeadOffset = null
    // The corresponding DOM node when a node selection is active.
    this.lastNode = null

    pm.content.addEventListener("focus", () => this.receivedFocus())

    this.poller = this.poller.bind(this)
  }

  // : (Selection, boolean)
  // Set the current selection and signal an event on the editor.
  setAndSignal(range, clearLast) {
    this.set(range, clearLast)
    // :: () #path=ProseMirror#events#selectionChange
    // Indicates that the editor's selection has changed.
    this.pm.signal("selectionChange")
  }

  // : (Selection, boolean)
  // Set the current selection.
  set(range, clearLast) {
    this.pm.ensureOperation({readSelection: false, selection: range})
    this.range = range
    if (clearLast !== false) this.lastAnchorNode = null
  }

  poller() {
    if (hasFocus(this.pm)) {
      if (!this.pm.operation) this.readFromDOM()
      this.polling = setTimeout(this.poller, 100)
    } else {
      this.polling = null
    }
  }

  startPolling() {
    clearTimeout(this.polling)
    this.polling = setTimeout(this.poller, 50)
  }

  fastPoll() {
    this.startPolling()
  }

  stopPolling() {
    clearTimeout(this.polling)
    this.polling = null
  }

  // : () → bool
  // Whether the DOM selection has changed from the last known state.
  domChanged() {
    let sel = window.getSelection()
    return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||
      sel.focusNode != this.lastHeadNode || sel.focusOffset != this.lastHeadOffset
  }

  // Store the current state of the DOM selection.
  storeDOMState() {
    let sel = window.getSelection()
    this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset
    this.lastHeadNode = sel.focusNode; this.lastHeadOffset = sel.focusOffset
  }

  // : () → bool
  // When the DOM selection changes in a notable manner, modify the
  // current selection state to match.
  readFromDOM() {
    if (!hasFocus(this.pm) || !this.domChanged()) return false

    let {range, adjusted} = selectionFromDOM(this.pm, this.pm.doc, this.range.head)
    this.setAndSignal(range)

    if (range instanceof NodeSelection || adjusted) {
      this.toDOM()
    } else {
      this.clearNode()
      this.storeDOMState()
    }
    return true
  }

  toDOM(takeFocus) {
    if (!hasFocus(this.pm)) {
      if (!takeFocus) return
      // See https://bugzilla.mozilla.org/show_bug.cgi?id=921444
      else if (browser.gecko) this.pm.content.focus()
    }
    if (this.range instanceof NodeSelection)
      this.nodeToDOM()
    else
      this.rangeToDOM()
  }

  // Make changes to the DOM for a node selection.
  nodeToDOM() {
    let dom = DOMAfterPos(this.pm, this.range.from)
    if (dom != this.lastNode) {
      this.clearNode()
      dom.classList.add("ProseMirror-selectednode")
      this.pm.content.classList.add("ProseMirror-nodeselection")
      this.lastNode = dom
    }
    let range = document.createRange(), sel = window.getSelection()
    range.selectNode(dom)
    sel.removeAllRanges()
    sel.addRange(range)
    this.storeDOMState()
  }

  // Make changes to the DOM for a text selection.
  rangeToDOM() {
    this.clearNode()

    let anchor = DOMFromPos(this.pm, this.range.anchor)
    let head = DOMFromPos(this.pm, this.range.head)

    let sel = window.getSelection(), range = document.createRange()
    if (sel.extend) {
      range.setEnd(anchor.node, anchor.offset)
      range.collapse(false)
    } else {
      if (this.range.anchor > this.range.head) { let tmp = anchor; anchor = head; head = tmp }
      range.setEnd(head.node, head.offset)
      range.setStart(anchor.node, anchor.offset)
    }
    sel.removeAllRanges()
    sel.addRange(range)
    if (sel.extend)
      sel.extend(head.node, head.offset)
    this.storeDOMState()
  }

  // Clear all DOM statefulness of the last node selection.
  clearNode() {
    if (this.lastNode) {
      this.lastNode.classList.remove("ProseMirror-selectednode")
      this.pm.content.classList.remove("ProseMirror-nodeselection")
      this.lastNode = null
      return true
    }
  }

  receivedFocus() {
    if (this.polling == null) this.startPolling()
  }
}
exports.SelectionState = SelectionState

// ;; An editor selection. Can be one of two selection types:
// `TextSelection` and `NodeSelection`. Both have the properties
// listed here, but also contain more information (such as the
// selected [node](#NodeSelection.node) or the
// [head](#TextSelection.head) and [anchor](#TextSelection.anchor)).
class Selection {
  // :: number
  // The left bound of the selection.
  get from() { return this.$from.pos }

  // :: number
  // The right bound of the selection.
  get to() { return this.$to.pos }

  constructor($from, $to) {
    // :: ResolvedPos
    // The resolved left bound of the selection
    this.$from = $from
    // :: ResolvedPos
    // The resolved right bound of the selection
    this.$to = $to
  }

  // :: bool
  // True if the selection is an empty text selection (head an anchor
  // are the same).
  get empty() {
    return this.from == this.to
  }

  // :: (other: Selection) → bool #path=Selection.prototype.eq
  // Test whether the selection is the same as another selection.

  // :: (doc: Node, mapping: Mappable) → Selection #path=Selection.prototype.map
  // Map this selection through a [mappable](#Mappable) thing. `doc`
  // should be the new document, to which we are mapping.
}
exports.Selection = Selection

// ;; A text selection represents a classical editor
// selection, with a head (the moving side) and anchor (immobile
// side), both of which point into textblock nodes. It can be empty (a
// regular cursor position).
class TextSelection extends Selection {
  // :: number
  // The selection's immobile side (does not move when pressing
  // shift-arrow).
  get anchor() { return this.$anchor.pos }
  // :: number
  // The selection's mobile side (the side that moves when pressing
  // shift-arrow).
  get head() { return this.$head.pos }

  // :: (ResolvedPos, ?ResolvedPos)
  // Construct a text selection. When `head` is not given, it defaults
  // to `anchor`.
  constructor($anchor, $head = $anchor) {
    let inv = $anchor.pos > $head.pos
    super(inv ? $head : $anchor, inv ? $anchor : $head)
    // :: ResolvedPos The resolved anchor of the selection.
    this.$anchor = $anchor
    // :: ResolvedPos The resolved head of the selection.
    this.$head = $head
  }

  get inverted() { return this.anchor > this.head }

  eq(other) {
    return other instanceof TextSelection && other.head == this.head && other.anchor == this.anchor
  }

  map(doc, mapping) {
    let $head = doc.resolve(mapping.map(this.head))
    if (!$head.parent.isTextblock) return findSelectionNear($head)
    let $anchor = doc.resolve(mapping.map(this.anchor))
    return new TextSelection($anchor.parent.isTextblock ? $anchor : $head, $head)
  }

  get token() {
    return new SelectionToken(TextSelection, this.anchor, this.head)
  }

  static mapToken(token, mapping) {
    return new SelectionToken(TextSelection, mapping.map(token.a), mapping.map(token.b))
  }

  static fromToken(token, doc) {
    let $head = doc.resolve(token.b)
    if (!$head.parent.isTextblock) return findSelectionNear($head)
    let $anchor = doc.resolve(token.a)
    return new TextSelection($anchor.parent.isTextblock ? $anchor : $head, $head)
  }
}
exports.TextSelection = TextSelection

// ;; A node selection is a selection that points at a
// single node. All nodes marked [selectable](#NodeType.selectable)
// can be the target of a node selection. In such an object, `from`
// and `to` point directly before and after the selected node.
class NodeSelection extends Selection {
  // :: (ResolvedPos)
  // Create a node selection. Does not verify the validity of its
  // argument. Use `ProseMirror.setNodeSelection` for an easier,
  // error-checking way to create a node selection.
  constructor($from) {
    let $to = $from.plusOne()
    super($from, $to)
    // :: Node The selected node.
    this.node = $from.nodeAfter
  }

  eq(other) {
    return other instanceof NodeSelection && this.from == other.from
  }

  map(doc, mapping) {
    let $from = doc.resolve(mapping.map(this.from, 1))
    let to = mapping.map(this.to, -1)
    let node = $from.nodeAfter
    if (node && to == $from.pos + node.nodeSize && node.type.selectable)
      return new NodeSelection($from)
    return findSelectionNear($from)
  }

  get token() {
    return new SelectionToken(NodeSelection, this.from, this.to)
  }

  static mapToken(token, mapping) {
    return new SelectionToken(NodeSelection, mapping.map(token.a, 1), mapping.map(token.b, -1))
  }

  static fromToken(token, doc) {
    let $from = doc.resolve(token.a), node = $from.nodeAfter
    if (node && token.b == token.a + node.nodeSize && node.type.selectable)
      return new NodeSelection($from)
    return findSelectionNear($from)
  }
}
exports.NodeSelection = NodeSelection

class SelectionToken {
  constructor(type, a, b) {
    this.type = type
    this.a = a
    this.b = b
  }
}

function selectionFromDOM(pm, doc, oldHead, loose) {
  let sel = window.getSelection()
  let anchor = posFromDOM(pm, sel.anchorNode, sel.anchorOffset, loose)
  let head = sel.isCollapsed ? anchor : posFromDOM(pm, sel.focusNode, sel.focusOffset, loose)

  let range = findSelectionNear(doc.resolve(head), oldHead != null && oldHead < head ? 1 : -1)
  if (range instanceof TextSelection) {
    let selNearAnchor = findSelectionNear(doc.resolve(anchor), anchor > range.to ? -1 : 1, true)
    range = new TextSelection(selNearAnchor.$anchor, range.$head)
  } else if (anchor < range.from || anchor > range.to) {
    // If head falls on a node, but anchor falls outside of it,
    // create a text selection between them
    let inv = anchor > range.to
    range = new TextSelection(findSelectionNear(doc.resolve(anchor), inv ? -1 : 1, true).$anchor,
                              findSelectionNear(inv ? range.$from : range.$to, inv ? 1 : -1, true).$head)
  }
  return {range, adjusted: head != range.head || anchor != range.anchor}
}
exports.selectionFromDOM = selectionFromDOM

function hasFocus(pm) {
  if (document.activeElement != pm.content) return false
  let sel = window.getSelection()
  return sel.rangeCount && contains(pm.content, sel.anchorNode)
}
exports.hasFocus = hasFocus

// Try to find a selection inside the given node. `pos` points at the
// position where the search starts. When `text` is true, only return
// text selections.
function findSelectionIn(doc, node, pos, index, dir, text) {
  if (node.isTextblock) return new TextSelection(doc.resolve(pos))
  for (let i = index - (dir > 0 ? 0 : 1); dir > 0 ? i < node.childCount : i >= 0; i += dir) {
    let child = node.child(i)
    if (!child.type.isLeaf) {
      let inner = findSelectionIn(doc, child, pos + dir, dir < 0 ? child.childCount : 0, dir, text)
      if (inner) return inner
    } else if (!text && child.type.selectable) {
      return new NodeSelection(doc.resolve(pos - (dir < 0 ? child.nodeSize : 0)))
    }
    pos += child.nodeSize * dir
  }
}

// FIXME we'll need some awareness of text direction when scanning for selections

// Create a selection which is moved relative to a position in a
// given direction. When a selection isn't found at the given position,
// walks up the document tree one level and one step in the
// desired direction.
function findSelectionFrom($pos, dir, text) {
  let inner = $pos.parent.isTextblock ? new TextSelection($pos)
      : findSelectionIn($pos.node(0), $pos.parent, $pos.pos, $pos.index(), dir, text)
  if (inner) return inner

  for (let depth = $pos.depth - 1; depth >= 0; depth--) {
    let found = dir < 0
        ? findSelectionIn($pos.node(0), $pos.node(depth), $pos.before(depth + 1), $pos.index(depth), dir, text)
        : findSelectionIn($pos.node(0), $pos.node(depth), $pos.after(depth + 1), $pos.index(depth) + 1, dir, text)
    if (found) return found
  }
}
exports.findSelectionFrom = findSelectionFrom

function findSelectionNear($pos, bias = 1, text) {
  let result = findSelectionFrom($pos, bias, text) ||
      findSelectionFrom($pos, -bias, text)
  if (!result) throw new RangeError("Searching for selection in invalid document " + $pos.node(0))
  return result
}
exports.findSelectionNear = findSelectionNear

// Find the selection closest to the start of the given node. `pos`,
// if given, should point at the start of the node's content.
function findSelectionAtStart(doc, text) {
  return findSelectionIn(doc, doc, 0, 0, 1, text)
}
exports.findSelectionAtStart = findSelectionAtStart

// Find the selection closest to the end of the given node.
function findSelectionAtEnd(doc, text) {
  return findSelectionIn(doc, doc, doc.content.size, doc.childCount, -1, text)
}
exports.findSelectionAtEnd = findSelectionAtEnd

// : (ProseMirror, number, number)
// Whether vertical position motion in a given direction
// from a position would leave a text block.
function verticalMotionLeavesTextblock(pm, $pos, dir) {
  let dom = $pos.depth ? DOMAfterPos(pm, $pos.before()) : pm.content
  let coords = coordsAtPos(pm, $pos.pos)
  for (let child = dom.firstChild; child; child = child.nextSibling) {
    if (child.nodeType != 1) continue
    let boxes = child.getClientRects()
    for (let i = 0; i < boxes.length; i++) {
      let box = boxes[i]
      if (dir < 0 ? box.bottom < coords.top : box.top > coords.bottom)
        return false
    }
  }
  return true
}
exports.verticalMotionLeavesTextblock = verticalMotionLeavesTextblock
<|endoftext|>


<commit_before>var Thread = require('./thread');

/**
 * Utility function to determine if a value is a Promise.
 * @param {*} value Value to check for a Promise.
 * @return {Boolean} True if the value appears to be a Promise.
 */
var isPromise = function (value) {
    return value && value.then && typeof value.then === 'function';
};

/**
 * Execute a block.
 * @param {!Sequencer} sequencer Which sequencer is executing.
 * @param {!Thread} thread Thread which to read and execute.
 */
var execute = function (sequencer, thread) {
    var runtime = sequencer.runtime;
    var target = runtime.targetForThread(thread);

    // Current block to execute is the one on the top of the stack.
    var currentBlockId = thread.peekStack();
    var currentStackFrame = thread.peekStackFrame();

    var opcode = target.blocks.getOpcode(currentBlockId);

    if (!opcode) {
        console.warn('Could not get opcode for block: ' + currentBlockId);
        return;
    }

    var blockFunction = runtime.getOpcodeFunction(opcode);
    var isHat = runtime.getIsHat(opcode);

    // Hats are implemented slightly differently from regular blocks.
    // If they have an associated block function, it's treated as a predicate;
    // if not, execution will proceed right through it (as a no-op).
    if (isHat) {
        var nextBlock = target.blocks.getNextBlock(currentBlockId);
        if (!nextBlock) {
            // Hat with no next block - don't try to evaluate it.
            sequencer.retireThread(thread);
            return;
        }
        if (!blockFunction) {
            // No predicate for the hat - just continue to next block.
            sequencer.proceedThread(thread);
            return;
        }
    } else {
        if (!blockFunction) {
            console.warn('Could not get implementation for opcode: ' + opcode);
            return;
        }
    }

    // Generate values for arguments (inputs).
    var argValues = {};

    // Add all fields on this block to the argValues.
    var fields = target.blocks.getFields(currentBlockId);
    for (var fieldName in fields) {
        argValues[fieldName] = fields[fieldName].value;
    }

    // Recursively evaluate input blocks.
    var inputs = target.blocks.getInputs(currentBlockId);
    for (var inputName in inputs) {
        var input = inputs[inputName];
        var inputBlockId = input.block;
        // Is there no value for this input waiting in the stack frame?
        if (typeof currentStackFrame.reported[inputName] === 'undefined') {
            // If there's not, we need to evaluate the block.
            var reporterYielded = (
                sequencer.stepToReporter(thread, inputBlockId, inputName)
            );
            // If the reporter yielded, return immediately;
            // it needs time to finish and report its value.
            if (reporterYielded) {
                return;
            }
        }
        argValues[inputName] = currentStackFrame.reported[inputName];
    }

    // If we've gotten this far, all of the input blocks are evaluated,
    // and `argValues` is fully populated. So, execute the block primitive.
    // First, clear `currentStackFrame.reported`, so any subsequent execution
    // (e.g., on return from a branch) gets fresh inputs.
    currentStackFrame.reported = {};

    var primitiveReportedValue = null;
    primitiveReportedValue = blockFunction(argValues, {
        stackFrame: currentStackFrame.executionContext,
        target: target,
        yield: function() {
            thread.setStatus(Thread.STATUS_YIELD);
        },
        yieldFrame: function() {
            thread.setStatus(Thread.STATUS_YIELD_FRAME);
        },
        done: function() {
            thread.setStatus(Thread.STATUS_RUNNING);
            sequencer.proceedThread(thread);
        },
        startBranch: function (branchNum) {
            sequencer.stepToBranch(thread, branchNum);
        },
        triggerHats: function(requestedHat, opt_matchFields, opt_target) {
            return (
                runtime.triggerHats(requestedHat, opt_matchFields, opt_target)
            );
        },
        ioQuery: function (device, func, args) {
            // Find the I/O device and execute the query/function call.
            if (runtime.ioDevices[device] && runtime.ioDevices[device][func]) {
                var devObject = runtime.ioDevices[device];
                return devObject[func].call(devObject, args);
            }
        }
    });

    /**
     * Handle any reported value from the primitive, either directly returned
     * or after a promise resolves.
     * @param {*} resolvedValue Value eventually returned from the primitive.
     */
    var handleReport = function (resolvedValue) {
        thread.pushReportedValue(resolvedValue);
        if (isHat) {
            // Hat predicate was evaluated.
            if (runtime.getIsEdgeTriggeredHat(opcode)) {
                // If this is an edge-triggered hat, only proceed if
                // the value is true and used to be false.
                var oldEdgeValue = runtime.updateEdgeTriggeredValue(
                    currentBlockId,
                    resolvedValue
                );
                var edgeWasTriggered = !oldEdgeValue && resolvedValue;
                if (!edgeWasTriggered) {
                    sequencer.retireThread(thread);
                }
            } else {
                // Not an edge-triggered hat: retire the thread
                // if predicate was false.
                if (!resolvedValue) {
                    sequencer.retireThread(thread);
                }
            }
        } else {
            // In a non-hat, report the value visually if necessary if
            // at the top of the thread stack.
            if (typeof resolvedValue !== 'undefined' && thread.atStackTop()) {
                runtime.visualReport(currentBlockId, resolvedValue);
            }
            // Finished any yields.
            thread.setStatus(Thread.STATUS_RUNNING);
        }
    };

    // If it's a promise, wait until promise resolves.
    if (isPromise(primitiveReportedValue)) {
        if (thread.status === Thread.STATUS_RUNNING) {
            // Primitive returned a promise; automatically yield thread.
            thread.setStatus(Thread.STATUS_YIELD);
        }
        // Promise handlers
        primitiveReportedValue.then(function(resolvedValue) {
            handleReport(resolvedValue);
            sequencer.proceedThread(thread);
        }, function(rejectionReason) {
            // Promise rejected: the primitive had some error.
            // Log it and proceed.
            console.warn('Primitive rejected promise: ', rejectionReason);
            thread.setStatus(Thread.STATUS_RUNNING);
            sequencer.proceedThread(thread);
        });
    } else if (thread.status === Thread.STATUS_RUNNING) {
        handleReport(primitiveReportedValue);
    }
};

module.exports = execute;
<commit_msg>Simplify `execute` hat check.<commit_after>var Thread = require('./thread');

/**
 * Utility function to determine if a value is a Promise.
 * @param {*} value Value to check for a Promise.
 * @return {Boolean} True if the value appears to be a Promise.
 */
var isPromise = function (value) {
    return value && value.then && typeof value.then === 'function';
};

/**
 * Execute a block.
 * @param {!Sequencer} sequencer Which sequencer is executing.
 * @param {!Thread} thread Thread which to read and execute.
 */
var execute = function (sequencer, thread) {
    var runtime = sequencer.runtime;
    var target = runtime.targetForThread(thread);

    // Current block to execute is the one on the top of the stack.
    var currentBlockId = thread.peekStack();
    var currentStackFrame = thread.peekStackFrame();

    var opcode = target.blocks.getOpcode(currentBlockId);

    if (!opcode) {
        console.warn('Could not get opcode for block: ' + currentBlockId);
        return;
    }

    var blockFunction = runtime.getOpcodeFunction(opcode);
    var isHat = runtime.getIsHat(opcode);

    // Hats are implemented slightly differently from regular blocks.
    // If they have an associated block function, it's treated as a predicate;
    // if not, execution will proceed right through it (as a no-op).
    if (!blockFunction) {
        if (!isHat) {
            console.warn('Could not get implementation for opcode: ' + opcode);
        }
        // Skip through the block.
        // (either hat with no predicate, or missing op).
        return;
    }

    // Generate values for arguments (inputs).
    var argValues = {};

    // Add all fields on this block to the argValues.
    var fields = target.blocks.getFields(currentBlockId);
    for (var fieldName in fields) {
        argValues[fieldName] = fields[fieldName].value;
    }

    // Recursively evaluate input blocks.
    var inputs = target.blocks.getInputs(currentBlockId);
    for (var inputName in inputs) {
        var input = inputs[inputName];
        var inputBlockId = input.block;
        // Is there no value for this input waiting in the stack frame?
        if (typeof currentStackFrame.reported[inputName] === 'undefined') {
            // If there's not, we need to evaluate the block.
            var reporterYielded = (
                sequencer.stepToReporter(thread, inputBlockId, inputName)
            );
            // If the reporter yielded, return immediately;
            // it needs time to finish and report its value.
            if (reporterYielded) {
                return;
            }
        }
        argValues[inputName] = currentStackFrame.reported[inputName];
    }

    // If we've gotten this far, all of the input blocks are evaluated,
    // and `argValues` is fully populated. So, execute the block primitive.
    // First, clear `currentStackFrame.reported`, so any subsequent execution
    // (e.g., on return from a branch) gets fresh inputs.
    currentStackFrame.reported = {};

    var primitiveReportedValue = null;
    primitiveReportedValue = blockFunction(argValues, {
        stackFrame: currentStackFrame.executionContext,
        target: target,
        yield: function() {
            thread.setStatus(Thread.STATUS_YIELD);
        },
        yieldFrame: function() {
            thread.setStatus(Thread.STATUS_YIELD_FRAME);
        },
        done: function() {
            thread.setStatus(Thread.STATUS_RUNNING);
            sequencer.proceedThread(thread);
        },
        startBranch: function (branchNum) {
            sequencer.stepToBranch(thread, branchNum);
        },
        triggerHats: function(requestedHat, opt_matchFields, opt_target) {
            return (
                runtime.triggerHats(requestedHat, opt_matchFields, opt_target)
            );
        },
        ioQuery: function (device, func, args) {
            // Find the I/O device and execute the query/function call.
            if (runtime.ioDevices[device] && runtime.ioDevices[device][func]) {
                var devObject = runtime.ioDevices[device];
                return devObject[func].call(devObject, args);
            }
        }
    });

    /**
     * Handle any reported value from the primitive, either directly returned
     * or after a promise resolves.
     * @param {*} resolvedValue Value eventually returned from the primitive.
     */
    var handleReport = function (resolvedValue) {
        thread.pushReportedValue(resolvedValue);
        if (isHat) {
            // Hat predicate was evaluated.
            if (runtime.getIsEdgeTriggeredHat(opcode)) {
                // If this is an edge-triggered hat, only proceed if
                // the value is true and used to be false.
                var oldEdgeValue = runtime.updateEdgeTriggeredValue(
                    currentBlockId,
                    resolvedValue
                );
                var edgeWasTriggered = !oldEdgeValue && resolvedValue;
                if (!edgeWasTriggered) {
                    sequencer.retireThread(thread);
                }
            } else {
                // Not an edge-triggered hat: retire the thread
                // if predicate was false.
                if (!resolvedValue) {
                    sequencer.retireThread(thread);
                }
            }
        } else {
            // In a non-hat, report the value visually if necessary if
            // at the top of the thread stack.
            if (typeof resolvedValue !== 'undefined' && thread.atStackTop()) {
                runtime.visualReport(currentBlockId, resolvedValue);
            }
            // Finished any yields.
            thread.setStatus(Thread.STATUS_RUNNING);
        }
    };

    // If it's a promise, wait until promise resolves.
    if (isPromise(primitiveReportedValue)) {
        if (thread.status === Thread.STATUS_RUNNING) {
            // Primitive returned a promise; automatically yield thread.
            thread.setStatus(Thread.STATUS_YIELD);
        }
        // Promise handlers
        primitiveReportedValue.then(function(resolvedValue) {
            handleReport(resolvedValue);
            sequencer.proceedThread(thread);
        }, function(rejectionReason) {
            // Promise rejected: the primitive had some error.
            // Log it and proceed.
            console.warn('Primitive rejected promise: ', rejectionReason);
            thread.setStatus(Thread.STATUS_RUNNING);
            sequencer.proceedThread(thread);
        });
    } else if (thread.status === Thread.STATUS_RUNNING) {
        handleReport(primitiveReportedValue);
    }
};

module.exports = execute;
<|endoftext|>


<commit_before>var EventEmitter = require('events');
var Sequencer = require('./sequencer');
var Thread = require('./thread');
var util = require('util');

/**
 * Manages blocks, stacks, and the sequencer.
 */
function Runtime () {
    // Bind event emitter
    EventEmitter.call(this);

    // State for the runtime
    /**
     * All blocks in the workspace.
     * Keys are block IDs, values are metadata about the block.
     * @type {Object.<string, Object>}
     */
    this.blocks = {};

    /**
     * All stacks in the workspace.
     * A list of block IDs that represent stacks (first block in stack).
     * @type {Array.<String>}
     */
    this.stacks = [];

    /**
     * A list of threads that are currently running in the VM.
     * Threads are added when execution starts and pruned when execution ends.
     * @type {Array.<Thread>}
     */
    this.threads = [];

    /** @type {!Sequencer} */
    this.sequencer = new Sequencer(this);
}

/**
 * Inherit from EventEmitter
 */
util.inherits(Runtime, EventEmitter);

/**
 * How rapidly we try to step threads, in ms.
 */
Runtime.THREAD_STEP_INTERVAL = 1000 / 60;

/**
 * Block management: create blocks and stacks from a `create` event
 * @param {!Object} block Blockly create event to be processed
 */
Runtime.prototype.createBlock = function (block, opt_isFlyoutBlock) {
    // Create new block
    this.blocks[block.id] = block;

    // Walk each field and add any shadow blocks
    // @todo Expand this to cover vertical / nested blocks
    for (var i in block.fields) {
        var shadows = block.fields[i].blocks;
        for (var y in shadows) {
            var shadow = shadows[y];
            this.blocks[shadow.id] = shadow;
        }
    }

    // Push block id to stacks array. New blocks are always a stack even if only
    // momentary. If the new block is added to an existing stack this stack will
    // be removed by the `moveBlock` method below.
    if (!opt_isFlyoutBlock) {
        this.stacks.push(block.id);
    }
};

/**
 * Block management: change block field values
 * @param {!Object} args Blockly change event to be processed
 */
Runtime.prototype.changeBlock = function (args) {
    // Validate
    if (args.element !== 'field') return;
    if (typeof this.blocks[args.id] === 'undefined') return;
    if (typeof this.blocks[args.id].fields[args.name] === 'undefined') return;

    // Update block value
    this.blocks[args.id].fields[args.name].value = args.value;
};

/**
 * Block management: move blocks from parent to parent
 * @param {!Object} e Blockly move event to be processed
 */
Runtime.prototype.moveBlock = function (e) {
    var _this = this;

    // Block has a new parent
    if (e.oldParent === undefined && e.newParent !== undefined) {
        // Remove stack
        _this._deleteStack(e.id);

        // Update new parent
        if (e.newField === undefined) {
            _this.blocks[e.newParent].next = e.id;
        } else {
            _this.blocks[e.newParent].fields[e.newField] = {
                name: e.newField,
                value: e.id,
                blocks: {}
            };
        }
    }

    // Block was removed from parent
    if (e.newParent === undefined && e.oldParent !== undefined) {
        // Add stack
        _this.stacks.push(e.id);

        // Update old parent
        if (e.oldField === undefined) {
            _this.blocks[e.oldParent].next = null;
        } else {
            delete _this.blocks[e.oldParent].fields[e.oldField];
        }
    }
};

/**
 * Block management: delete blocks and their associated stacks
 * @param {!Object} e Blockly delete event to be processed
 */
Runtime.prototype.deleteBlock = function (e) {
    // @todo Stop threads running on this stack

    // Get block
    var block = this.blocks[e.id];

    // Delete children
    if (block.next !== null) {
        this.deleteBlock({id: block.next});
    }

    // Delete substacks and fields
    for (var field in block.fields) {
        if (field === 'SUBSTACK') {
            this.deleteBlock({id: block.fields[field].value});
        } else {
            for (var shadow in block.fields[field].blocks) {
                this.deleteBlock({id: shadow});
            }
        }
    }

    // Delete stack
    this._deleteStack(e.id);

    // Delete block
    delete this.blocks[e.id];
};

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

/**
 * Create a thread and push it to the list of threads.
 * @param {!string} id ID of block that starts the stack
 */
Runtime.prototype._pushThread = function (id) {
    if (this.stacks.indexOf(id) < -1) return;
    var thread = new Thread(id);
    this.threads.push(thread);
};

/**
 * Remove a thread from the list of threads.
 * @param {?Thread} thread Thread object to remove from actives
 */
Runtime.prototype._removeThread = function (thread) {
    var i = this.threads.indexOf(thread);
    if (i > -1) this.threads.splice(i, 1);
};

/**
 * Toggle a stack
 * @param {!string} stackId ID of block that starts the stack
 */
Runtime.prototype.toggleStack = function (stackId) {
    // Remove any existing thread
    for (var i = 0; i < this.threads.length; i++) {
        if (this.threads[i].topBlock == stackId) {
            this._removeThread(this.threads[i]);
        }
    }
    this._pushThread(stackId);
};

/**
 * Repeatedly run `sequencer.stepThreads` and filter out
 * inactive threads after each iteration.
 */
Runtime.prototype._step = function () {
    var inactiveThreads = this.sequencer.stepThreads(this.threads);
    for (var i = 0; i < inactiveThreads.length; i++) {
        this._removeThread(inactiveThreads[i]);
    }
};

/**
 * Set up timers to repeatedly step in a browser
 */
Runtime.prototype.start = function () {
    if (!window.setInterval) return;
    window.setInterval(function() {
        this._step();
    }.bind(this), Runtime.THREAD_STEP_INTERVAL);
};

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

/**
 * Helper to remove a stack from `this.stacks`
 * @param {?string} id ID of block that starts the stack
 */
Runtime.prototype._deleteStack = function (id) {
    var i = this.stacks.indexOf(id);
    if (i > -1) this.stacks.splice(i, 1);
};

/**
 * Helper to get the next block for a particular block
 * @param {?string} id ID of block to get the next block for
 * @return {?string} ID of next block in the sequence
 */
Runtime.prototype._getNextBlock = function (id) {
    if (typeof this.blocks[id] === 'undefined') return null;
    return this.blocks[id].next;
};

/**
 * Helper to get the substack for a particular C-shaped block
 * @param {?string} id ID for block to get the substack for
 * @return {?string} ID of block in the substack
 */
Runtime.prototype._getSubstack = function (id) {
    if (typeof this.blocks[id] === 'undefined') return null;
    return this.blocks[id].fields['SUBSTACK'];
};

module.exports = Runtime;
<commit_msg>Return if a thread is toggled off<commit_after>var EventEmitter = require('events');
var Sequencer = require('./sequencer');
var Thread = require('./thread');
var util = require('util');

/**
 * Manages blocks, stacks, and the sequencer.
 */
function Runtime () {
    // Bind event emitter
    EventEmitter.call(this);

    // State for the runtime
    /**
     * All blocks in the workspace.
     * Keys are block IDs, values are metadata about the block.
     * @type {Object.<string, Object>}
     */
    this.blocks = {};

    /**
     * All stacks in the workspace.
     * A list of block IDs that represent stacks (first block in stack).
     * @type {Array.<String>}
     */
    this.stacks = [];

    /**
     * A list of threads that are currently running in the VM.
     * Threads are added when execution starts and pruned when execution ends.
     * @type {Array.<Thread>}
     */
    this.threads = [];

    /** @type {!Sequencer} */
    this.sequencer = new Sequencer(this);
}

/**
 * Inherit from EventEmitter
 */
util.inherits(Runtime, EventEmitter);

/**
 * How rapidly we try to step threads, in ms.
 */
Runtime.THREAD_STEP_INTERVAL = 1000 / 60;

/**
 * Block management: create blocks and stacks from a `create` event
 * @param {!Object} block Blockly create event to be processed
 */
Runtime.prototype.createBlock = function (block, opt_isFlyoutBlock) {
    // Create new block
    this.blocks[block.id] = block;

    // Walk each field and add any shadow blocks
    // @todo Expand this to cover vertical / nested blocks
    for (var i in block.fields) {
        var shadows = block.fields[i].blocks;
        for (var y in shadows) {
            var shadow = shadows[y];
            this.blocks[shadow.id] = shadow;
        }
    }

    // Push block id to stacks array. New blocks are always a stack even if only
    // momentary. If the new block is added to an existing stack this stack will
    // be removed by the `moveBlock` method below.
    if (!opt_isFlyoutBlock) {
        this.stacks.push(block.id);
    }
};

/**
 * Block management: change block field values
 * @param {!Object} args Blockly change event to be processed
 */
Runtime.prototype.changeBlock = function (args) {
    // Validate
    if (args.element !== 'field') return;
    if (typeof this.blocks[args.id] === 'undefined') return;
    if (typeof this.blocks[args.id].fields[args.name] === 'undefined') return;

    // Update block value
    this.blocks[args.id].fields[args.name].value = args.value;
};

/**
 * Block management: move blocks from parent to parent
 * @param {!Object} e Blockly move event to be processed
 */
Runtime.prototype.moveBlock = function (e) {
    var _this = this;

    // Block has a new parent
    if (e.oldParent === undefined && e.newParent !== undefined) {
        // Remove stack
        _this._deleteStack(e.id);

        // Update new parent
        if (e.newField === undefined) {
            _this.blocks[e.newParent].next = e.id;
        } else {
            _this.blocks[e.newParent].fields[e.newField] = {
                name: e.newField,
                value: e.id,
                blocks: {}
            };
        }
    }

    // Block was removed from parent
    if (e.newParent === undefined && e.oldParent !== undefined) {
        // Add stack
        _this.stacks.push(e.id);

        // Update old parent
        if (e.oldField === undefined) {
            _this.blocks[e.oldParent].next = null;
        } else {
            delete _this.blocks[e.oldParent].fields[e.oldField];
        }
    }
};

/**
 * Block management: delete blocks and their associated stacks
 * @param {!Object} e Blockly delete event to be processed
 */
Runtime.prototype.deleteBlock = function (e) {
    // @todo Stop threads running on this stack

    // Get block
    var block = this.blocks[e.id];

    // Delete children
    if (block.next !== null) {
        this.deleteBlock({id: block.next});
    }

    // Delete substacks and fields
    for (var field in block.fields) {
        if (field === 'SUBSTACK') {
            this.deleteBlock({id: block.fields[field].value});
        } else {
            for (var shadow in block.fields[field].blocks) {
                this.deleteBlock({id: shadow});
            }
        }
    }

    // Delete stack
    this._deleteStack(e.id);

    // Delete block
    delete this.blocks[e.id];
};

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

/**
 * Create a thread and push it to the list of threads.
 * @param {!string} id ID of block that starts the stack
 */
Runtime.prototype._pushThread = function (id) {
    if (this.stacks.indexOf(id) < -1) return;
    var thread = new Thread(id);
    this.threads.push(thread);
};

/**
 * Remove a thread from the list of threads.
 * @param {?Thread} thread Thread object to remove from actives
 */
Runtime.prototype._removeThread = function (thread) {
    var i = this.threads.indexOf(thread);
    if (i > -1) this.threads.splice(i, 1);
};

/**
 * Toggle a stack
 * @param {!string} stackId ID of block that starts the stack
 */
Runtime.prototype.toggleStack = function (stackId) {
    // Remove any existing thread
    for (var i = 0; i < this.threads.length; i++) {
        if (this.threads[i].topBlock == stackId) {
            this._removeThread(this.threads[i]);
            return;
        }
    }
    // Otherwise add it
    this._pushThread(stackId);
};

/**
 * Repeatedly run `sequencer.stepThreads` and filter out
 * inactive threads after each iteration.
 */
Runtime.prototype._step = function () {
    var inactiveThreads = this.sequencer.stepThreads(this.threads);
    for (var i = 0; i < inactiveThreads.length; i++) {
        this._removeThread(inactiveThreads[i]);
    }
};

/**
 * Set up timers to repeatedly step in a browser
 */
Runtime.prototype.start = function () {
    if (!window.setInterval) return;
    window.setInterval(function() {
        this._step();
    }.bind(this), Runtime.THREAD_STEP_INTERVAL);
};

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

/**
 * Helper to remove a stack from `this.stacks`
 * @param {?string} id ID of block that starts the stack
 */
Runtime.prototype._deleteStack = function (id) {
    var i = this.stacks.indexOf(id);
    if (i > -1) this.stacks.splice(i, 1);
};

/**
 * Helper to get the next block for a particular block
 * @param {?string} id ID of block to get the next block for
 * @return {?string} ID of next block in the sequence
 */
Runtime.prototype._getNextBlock = function (id) {
    if (typeof this.blocks[id] === 'undefined') return null;
    return this.blocks[id].next;
};

/**
 * Helper to get the substack for a particular C-shaped block
 * @param {?string} id ID for block to get the substack for
 * @return {?string} ID of block in the substack
 */
Runtime.prototype._getSubstack = function (id) {
    if (typeof this.blocks[id] === 'undefined') return null;
    return this.blocks[id].fields['SUBSTACK'];
};

module.exports = Runtime;
<|endoftext|>


<commit_before>function EveStretch() {
    this.name = '';
    this.display = true;
    this.update = true;
    this.source = null;
    this.dest = null;
    this.sourceObject = null;
    this.destObject = null;
    this.stretchObject = null;
    this.curveSets = [];
    this.length = new Tw2Float();
    this._time = 0;
    this._useTransformsForStretch = false;
    this._sourcePosition = vec3.create();
    this._destinationPosition = vec3.create();
    this._displaySourceObject = true;
    this._sourceTransform = null;
    this._displayDestObject = true;
    this._useTransformsForStretch = false;
    this._isNegZForward = false;
}

EveStretch._tempVec3 = [vec3.create(), vec3.create(), vec3.create()];
EveStretch._tempMat4 = [mat4.create(), mat4.create()];

EveStretch.prototype.Update = function (dt) {
    for (var i = 0; i < this.curveSets.length; ++i) {
        this.curveSets[i].Update(dt);
    }
    this._time += dt;
    if (this.source) {
        this.source.GetValueAt(t, this._sourcePosition);
    }
    else if (this._useTransformsForStretch) {
        this._sourcePosition[0] = this._sourceTransform[12];
        this._sourcePosition[1] = this._sourceTransform[13];
        this._sourcePosition[2] = this._sourceTransform[14];
    }
    if (this.dest) {
        this.source.GetValueAt(t, this._destinationPosition);
    }
    var directionVec = vec3.subtract(this._destinationPosition, this._sourcePosition, EveStretch._tempVec3[0]);
    var scalingLength = vec3.length(directionVec);
    this.length.value = scalingLength;
    vec3.normalize(directionVec);
    if (this.sourceObject && this._displaySourceObject) {
        this.sourceObject.Update(dt);
    }
    if (this.stretchObject) {
        this.stretchObject.Update(dt);
    }
    if (this.destObject && this._displayDestObject) {
        this.destObject.Update(dt);
    }
}

EveStretch.prototype.UpdateViewDependentData = function () {
    if (!this.display) {
        return;
    }
    var directionVec = vec3.subtract(this._destinationPosition, this._sourcePosition, EveStretch._tempVec3[0]);
    var scalingLength = vec3.length(directionVec);
    vec3.normalize(directionVec);

    var m = EveStretch._tempMat4[0];
    if (this._useTransformsForStretch) {
        mat4.identity(m);
        mat4.rotateX(m, -Math.PI / 2);
        mat4.multiply(this._sourceTransform, m, m);
    }
    else {
        mat4.identity(m);
        var up = EveStretch._tempVec3[2];
        if (Math.abs(directionVec[1]) > 0.9) {
            up[2] = 1;
        }
        else {
            up[1] = 1;
        }
        var x = vec3.normalize(vec3.cross(up, directionVec, EveStretch._tempVec3[1]));
        vec3.cross(directionVec, x, up);
        m[0] = x[0]; m[1] = x[1]; m[2] = x[2];
        m[4] = -directionVec[0]; m[5] = -directionVec[1]; m[6] = -directionVec[2];
        m[8] = up[0]; m[9] = up[1]; m[10] = up[2];
    }
    if (this.destObject && this._displayDestObject) {
        m[12] = this._destinationPosition[0]; m[13] = this._destinationPosition[1]; m[14] = this._destinationPosition[2];
        this.destObject.UpdateViewDependentData(m);
    }
    if (this.sourceObject && this._displaySourceObject) {
        if (this._useTransformsForStretch) {
            mat4.identity(m);
            mat4.rotateX(m, -Math.PI / 2);
            mat4.multiply(this._sourceTransform, m, m);
        }
        else {
            m[12] = this._sourcePosition[0]; m[13] = this._sourcePosition[1]; m[14] = this._sourcePosition[2];
        }
        this.sourceObject.UpdateViewDependentData(m);
    }
    if (this.stretchObject) {
        if (this._useTransformsForStretch) {
            mat4.identity(m);
            mat4.scale(m, [1, 1, scalingLength]);
            mat4.multiply(this._sourceTransform, m, m);
        }
        else {
            m[0] = x[0]; m[1] = x[1]; m[2] = x[2];
            m[4] = up[0]; m[5] = up[1]; m[6] = up[2];
            m[8] = -directionVec[0]; m[9] = -directionVec[1]; m[10] = -directionVec[2];
            if (this._isNegZForward) {
                scalingLength = -scalingLength;
            }
            var s = mat4.scale(mat4.identity(EveStretch._tempMat4[1]), [1, 1, scalingLength]);
            mat4.multiply(m, s, m);
        }
        this.stretchObject.UpdateViewDependentData(m);
    }
}

EveStretch.prototype.GetBatches = function (mode, accumulator, perObjectData) {
    if (!this.display) {
        return;
    }
    if (this.sourceObject && this._displaySourceObject) {
        this.sourceObject.GetBatches(mode, accumulator, perObjectData);
    }
    if (this.destObject && this._displayDestObject) {
        this.destObject.GetBatches(mode, accumulator, perObjectData);
    }
    if (this.stretchObject) {
        this.stretchObject.GetBatches(mode, accumulator, perObjectData);
    }
};

EveStretch.prototype.SetSourcePosition = function (position) {
    this._useTransformsForStretch = false;
    this._sourcePosition = position;
}

EveStretch.prototype.SetDestinationPosition = function (position) {
    this._destinationPosition = position;
}

EveStretch.prototype.SetSourceTransform = function (transform) {
    this._useTransformsForStretch = true;
    this._sourceTransform = transform;
}

EveStretch.prototype.SetIsNegZForward = function (isNegZForward) {
    this._isNegZForward = isNegZForward;
}
<commit_msg>Fixed Typo EveStretch<commit_after>function EveStretch() {
    this.name = '';
    this.display = true;
    this.update = true;
    this.source = null;
    this.dest = null;
    this.sourceObject = null;
    this.destObject = null;
    this.stretchObject = null;
    this.curveSets = [];
    this.length = new Tw2Float();
    this._time = 0;
    this._useTransformsForStretch = false;
    this._sourcePosition = vec3.create();
    this._destinationPosition = vec3.create();
    this._displaySourceObject = true;
    this._sourceTransform = null;
    this._displayDestObject = true;
    this._useTransformsForStretch = false;
    this._isNegZForward = false;
}

EveStretch._tempVec3 = [vec3.create(), vec3.create(), vec3.create()];
EveStretch._tempMat4 = [mat4.create(), mat4.create()];

EveStretch.prototype.Update = function (dt) {
    for (var i = 0; i < this.curveSets.length; ++i) {
        this.curveSets[i].Update(dt);
    }
    this._time += dt;
    if (this.source) {
        this.source.GetValueAt(this._time, this._sourcePosition);
    }
    else if (this._useTransformsForStretch) {
        this._sourcePosition[0] = this._sourceTransform[12];
        this._sourcePosition[1] = this._sourceTransform[13];
        this._sourcePosition[2] = this._sourceTransform[14];
    }
    if (this.dest) {
        this.source.GetValueAt(this._time, this._destinationPosition);
    }
    var directionVec = vec3.subtract(this._destinationPosition, this._sourcePosition, EveStretch._tempVec3[0]);
    var scalingLength = vec3.length(directionVec);
    this.length.value = scalingLength;
    vec3.normalize(directionVec);
    if (this.sourceObject && this._displaySourceObject) {
        this.sourceObject.Update(dt);
    }
    if (this.stretchObject) {
        this.stretchObject.Update(dt);
    }
    if (this.destObject && this._displayDestObject) {
        this.destObject.Update(dt);
    }
}

EveStretch.prototype.UpdateViewDependentData = function () {
    if (!this.display) {
        return;
    }
    var directionVec = vec3.subtract(this._destinationPosition, this._sourcePosition, EveStretch._tempVec3[0]);
    var scalingLength = vec3.length(directionVec);
    vec3.normalize(directionVec);

    var m = EveStretch._tempMat4[0];
    if (this._useTransformsForStretch) {
        mat4.identity(m);
        mat4.rotateX(m, -Math.PI / 2);
        mat4.multiply(this._sourceTransform, m, m);
    }
    else {
        mat4.identity(m);
        var up = EveStretch._tempVec3[2];
        if (Math.abs(directionVec[1]) > 0.9) {
            up[2] = 1;
        }
        else {
            up[1] = 1;
        }
        var x = vec3.normalize(vec3.cross(up, directionVec, EveStretch._tempVec3[1]));
        vec3.cross(directionVec, x, up);
        m[0] = x[0]; m[1] = x[1]; m[2] = x[2];
        m[4] = -directionVec[0]; m[5] = -directionVec[1]; m[6] = -directionVec[2];
        m[8] = up[0]; m[9] = up[1]; m[10] = up[2];
    }
    if (this.destObject && this._displayDestObject) {
        m[12] = this._destinationPosition[0]; m[13] = this._destinationPosition[1]; m[14] = this._destinationPosition[2];
        this.destObject.UpdateViewDependentData(m);
    }
    if (this.sourceObject && this._displaySourceObject) {
        if (this._useTransformsForStretch) {
            mat4.identity(m);
            mat4.rotateX(m, -Math.PI / 2);
            mat4.multiply(this._sourceTransform, m, m);
        }
        else {
            m[12] = this._sourcePosition[0]; m[13] = this._sourcePosition[1]; m[14] = this._sourcePosition[2];
        }
        this.sourceObject.UpdateViewDependentData(m);
    }
    if (this.stretchObject) {
        if (this._useTransformsForStretch) {
            mat4.identity(m);
            mat4.scale(m, [1, 1, scalingLength]);
            mat4.multiply(this._sourceTransform, m, m);
        }
        else {
            m[0] = x[0]; m[1] = x[1]; m[2] = x[2];
            m[4] = up[0]; m[5] = up[1]; m[6] = up[2];
            m[8] = -directionVec[0]; m[9] = -directionVec[1]; m[10] = -directionVec[2];
            if (this._isNegZForward) {
                scalingLength = -scalingLength;
            }
            var s = mat4.scale(mat4.identity(EveStretch._tempMat4[1]), [1, 1, scalingLength]);
            mat4.multiply(m, s, m);
        }
        this.stretchObject.UpdateViewDependentData(m);
    }
}

EveStretch.prototype.GetBatches = function (mode, accumulator, perObjectData) {
    if (!this.display) {
        return;
    }
    if (this.sourceObject && this._displaySourceObject) {
        this.sourceObject.GetBatches(mode, accumulator, perObjectData);
    }
    if (this.destObject && this._displayDestObject) {
        this.destObject.GetBatches(mode, accumulator, perObjectData);
    }
    if (this.stretchObject) {
        this.stretchObject.GetBatches(mode, accumulator, perObjectData);
    }
};

EveStretch.prototype.SetSourcePosition = function (position) {
    this._useTransformsForStretch = false;
    this._sourcePosition = position;
}

EveStretch.prototype.SetDestinationPosition = function (position) {
    this._destinationPosition = position;
}

EveStretch.prototype.SetSourceTransform = function (transform) {
    this._useTransformsForStretch = true;
    this._sourceTransform = transform;
}

EveStretch.prototype.SetIsNegZForward = function (isNegZForward) {
    this._isNegZForward = isNegZForward;
}
<|endoftext|>


<commit_before>server = 'https://geoserver.threefunkymonkeys.com';
namespace = {
  name: 'geodashboard',
  url: 'http://geodashboard.org'
};

const property_type = {
  property: 'property_type',
  values: {
    'apartment': { color: '#1f78b4' },
    'house': { color: '#a6cee3' },
    'PH': { color: '#02818a' },
    'store': { color: '#41ab5d' },
  },
}

const category = {
  property: 'category',
  values: {
    'A': { color: '#a63603' },
    'B': { color: '#e6550d' },
    'C': { color: '#fd8d3c' },
    'D': { color: '#fdbe85' },
  },
}

const dashboard = new GeoDashboard.Dashboard({
  container: document.getElementsByClassName('dashboard')[0],
  header: {
    title: 'GeoDashboard Demo',
    logo: './geo-dashboard-white.png',
  },
  map: {
    center: [-58.38, -34.61],
    zoom: 15,
  },
  filters: [new GeoDashboard.Filter({
    property: 'surface_total_in_m2',
    operator: '>=',
    value: '100'
  })],
});

dashboard.addBaseLayer(new GeoDashboard.OSMLayer({
  visible: true,
}));
dashboard.addBaseLayer(new GeoDashboard.BingLayer({
  key: 'AlMSfR3F4khtlIefjuE_NYpX403LdlGiod36WLn8HlawywtSud-NSgEklCemD5pR',
}));

dashboard.addOverlayLayer(new GeoDashboard.WFSLayer({
  title: 'Properties by Type',
  server: server,
  layer: 'geodashboard:properati',
  exclusive: true,
  visible: true,
  popup: [{
    property: 'image_thumbnail',
    format: (value) => {
      if (!value) return null;
      return `<a target="_blank" href="${value}"><img src="${value}"/></a>`;
    },
  },{
    title: 'State',
    property: 'state_name',
  },{
    title: 'Place Name',
    property: 'place_name',
  },{
    title: 'Rooms',
    property: 'rooms',
  }, {
    title: 'Price',
    property: 'price',
  },{
    property: 'properati_url',
    format: (value) => {
      if (!value) return null;
      return `<a style="width:100%;display:block;text-align:right;font-size:1.3em;text-decoration:none;" target="_blank" href="${value}">ℹ</a>`;
    },
  }],
  style: property_type,
  attribution: 'Datos provistos por Properati',
}));

dashboard.addOverlayLayer(new GeoDashboard.WFSLayer({
  title: 'Properties by Category',
  server: server,
  layer: 'geodashboard:properati',
  exclusive: true,
  visible: false,
  popup: [{
    property: 'image_thumbnail',
    format: (value) => {
      if (!value) return null;
      return `<a target="_blank" href="${value}"><img src="${value}"/></a>`;
    },
  }, {
    title: 'State',
    property: 'state_name',
  }, {
    title: 'Place Name',
    property: 'place_name',
  }, {
    title: 'Rooms',
    property: 'rooms',
  }, {
    title: 'Price',
    property: 'price_aprox_usd',
  }, {
    property: 'properati_url',
    format: (value) => {
      if (!value) return null;
      return `<a style="width:100%;display:block;text-align:right;font-size:1.3em;text-decoration:none;" target="_blank" href="${value}">ℹ</a>`;
    },
  }],
  style: category,
  attribution: 'Datos provistos por Properati',
}));

dashboard.addOverlayLayer(new GeoDashboard.WMSLayer({
  title: 'Heatmap',
  server: server,
  layer: 'geodashboard:properati',
  style: 'geodashboard:heatmap',
  exclusive: true,
  attribution: 'Datos provistos por Properati',
}));

dashboard.addWidget(new GeoDashboard.AggregateWidget({
  title: 'Average Price (USD)',
  server: server,
  namespace: namespace,
  layer: 'geodashboard:properati',
  property: 'price_aprox_usd',
  function: 'Average',
  format: function(value) {
    return `$${parseInt(value)}`;
  },
}));

dashboard.addWidget(new GeoDashboard.AggregateWidget({
  title: 'Average Total Surface',
  server: server,
  namespace: namespace,
  layer: 'geodashboard:properati',
  property: 'surface_total_in_m2',
  function: 'Average',
  format: function (value) {
    return `${parseInt(value)} m2`;
  },
}));

dashboard.addWidget(new GeoDashboard.CategoryWidget({
  title: 'Properties by Type',
  server: server,
  namespace: namespace,
  layer: 'geodashboard:properati',
  property: 'property_type',
  categories: property_type,
}));

dashboard.addWidget(new GeoDashboard.CategoryWidget({
  title: 'Properties by Category',
  server: server,
  namespace: namespace,
  layer: 'geodashboard:properati',
  property: 'category',
  categories: category,
}));

dashboard.addWidget(new GeoDashboard.ChartWidget({
  title: 'Properties by Type',
  server: server,
  namespace: namespace,
  layer: 'geodashboard:properati',
  property: 'property_type',
  categories: property_type,
  chart: {
    type: 'bar',
  },
}));

dashboard.addWidget(new GeoDashboard.ChartWidget({
  title: 'Properties by Category (%)',
  server: server,
  namespace: namespace,
  layer: 'geodashboard:properati',
  property: 'category',
  categories: category,
  chart: {
    type: 'doughnut',
  },
}));

dashboard.render();
<commit_msg>Update demo<commit_after>server = 'https://geoserver.threefunkymonkeys.com';
namespace = {
  name: 'geodashboard',
  url: 'http://geodashboard.org'
};

const property_type = {
  property: 'property_type',
  values: {
    'apartment': { color: '#1f78b4' },
    'house': { color: '#a6cee3' },
    'PH': { color: '#02818a' },
    'store': { color: '#41ab5d' },
  },
}

const category = {
  property: 'category',
  values: {
    'A': { color: '#a63603' },
    'B': { color: '#e6550d' },
    'C': { color: '#fd8d3c' },
    'D': { color: '#fdbe85' },
  },
}

const dashboard = new GeoDashboard.Dashboard({
  container: document.getElementsByClassName('dashboard')[0],
  header: {
    title: 'GeoDashboard Demo',
    logo: './geo-dashboard-white.png',
  },
  map: {
    center: [-58.40, -34.61],
    zoom: 14,
  },
  filters: [new GeoDashboard.Filter({
    property: 'surface_total_in_m2',
    operator: '>=',
    value: '100'
  })],
});

dashboard.addBaseLayer(new GeoDashboard.OSMLayer({
  visible: true,
}));
dashboard.addBaseLayer(new GeoDashboard.BingLayer({
  key: 'AlMSfR3F4khtlIefjuE_NYpX403LdlGiod36WLn8HlawywtSud-NSgEklCemD5pR',
}));

dashboard.addOverlayLayer(new GeoDashboard.WMSLayer({
  title: 'Properties by Type (WMS)',
  server: server,
  layer: 'geodashboard:properati',
  style: 'geodashboard:property_type',
  exclusive: true,
  visible: true,
  tiled: true,
  attribution: 'Datos provistos por Properati',
}));

dashboard.addOverlayLayer(new GeoDashboard.WFSLayer({
  title: 'Properties by Type (WFS)',
  server: server,
  layer: 'geodashboard:properati',
  exclusive: true,
  visible: false,
  popup: [{
    property: 'image_thumbnail',
    format: (value) => {
      if (!value) return null;
      return `<a target="_blank" href="${value}"><img src="${value}"/></a>`;
    },
  },{
    title: 'State',
    property: 'state_name',
  },{
    title: 'Place Name',
    property: 'place_name',
  },{
    title: 'Rooms',
    property: 'rooms',
  },{
    title: 'Total Surface',
    property: 'surface_total_in_m2',
    format: (value) => `${value}m2`,
  },{
    title: 'Price',
    property: 'price',
    format: (value) => `$${value}`,
  },{
    property: 'properati_url',
    format: (value) => {
      if (!value) return null;
      return `<a style="width:100%;display:block;text-align:right;font-size:1.3em;text-decoration:none;" target="_blank" href="${value}">ℹ</a>`;
    },
  }],
  style: property_type,
  attribution: 'Datos provistos por Properati',
}));

dashboard.addOverlayLayer(new GeoDashboard.WFSLayer({
  title: 'Properties by Category (WFS)',
  server: server,
  layer: 'geodashboard:properati',
  exclusive: true,
  visible: false,
  style: category,
  attribution: 'Datos provistos por Properati',
}));

dashboard.addOverlayLayer(new GeoDashboard.WMSLayer({
  title: 'Heatmap (Slow)',
  server: server,
  layer: 'geodashboard:properati',
  style: 'geodashboard:heatmap',
  exclusive: true,
  attribution: 'Datos provistos por Properati',
}));

dashboard.addWidget(new GeoDashboard.AggregateWidget({
  title: 'Average Price (USD)',
  server: server,
  namespace: namespace,
  layer: 'geodashboard:properati',
  property: 'price_aprox_usd',
  function: 'Average',
  format: (value) => `$${parseInt(value)}`,
}));

dashboard.addWidget(new GeoDashboard.AggregateWidget({
  title: 'Max Price (USD)',
  server: server,
  namespace: namespace,
  layer: 'geodashboard:properati',
  property: 'price_aprox_usd',
  function: 'Max',
  format: (value) => `$${parseInt(value)}`,
}));

dashboard.addWidget(new GeoDashboard.AggregateWidget({
  title: 'Average Total Surface',
  server: server,
  namespace: namespace,
  layer: 'geodashboard:properati',
  property: 'surface_total_in_m2',
  function: 'Average',
  format: (value) => `${parseInt(value)}m2`,
}));

dashboard.addWidget(new GeoDashboard.AggregateWidget({
  title: 'Max Total Surface',
  server: server,
  namespace: namespace,
  layer: 'geodashboard:properati',
  property: 'surface_total_in_m2',
  function: 'Max',
  format: (value) => `${parseInt(value)}m2`,
}));

dashboard.addWidget(new GeoDashboard.CategoryWidget({
  title: 'Properties by Type',
  server: server,
  namespace: namespace,
  layer: 'geodashboard:properati',
  property: 'property_type',
  categories: property_type,
}));

dashboard.addWidget(new GeoDashboard.CategoryWidget({
  title: 'Properties by Category',
  server: server,
  namespace: namespace,
  layer: 'geodashboard:properati',
  property: 'category',
  categories: category,
}));

dashboard.addWidget(new GeoDashboard.ChartWidget({
  title: 'Properties by Type',
  server: server,
  namespace: namespace,
  layer: 'geodashboard:properati',
  property: 'property_type',
  categories: property_type,
  chart: {
    type: 'bar',
  },
}));

dashboard.addWidget(new GeoDashboard.ChartWidget({
  title: 'Properties by Category (%)',
  server: server,
  namespace: namespace,
  layer: 'geodashboard:properati',
  property: 'category',
  categories: category,
  chart: {
    type: 'doughnut',
  },
}));

dashboard.render();
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import classSet from 'classnames';
import Const from '../Const';

const legalComparators = [ '=', '>', '>=', '<', '<=', '!=' ];

class NumberFilter extends Component {
  constructor(props) {
    super(props);
    this.numberComparators = this.props.numberComparators || legalComparators;
    this.timeout = null;
    this.state = {
      isPlaceholderSelected: (this.props.defaultValue === undefined ||
        this.props.defaultValue.number === undefined ||
        (this.props.options &&
          this.props.options.indexOf(this.props.defaultValue.number) === -1))
    };
    this.onChangeNumber = this.onChangeNumber.bind(this);
    this.onChangeNumberSet = this.onChangeNumberSet.bind(this);
    this.onChangeComparator = this.onChangeComparator.bind(this);
  }

  onChangeNumber(event) {
    const comparator = this.refs.numberFilterComparator.value;
    if (comparator === '') {
      return;
    }
    if (this.timeout) {
      clearTimeout(this.timeout);
    }
    const filterValue = event.target.value;
    this.timeout = setTimeout(() => {
      this.props.filterHandler({ number: filterValue, comparator }, Const.FILTER_TYPE.NUMBER);
    }, this.props.delay);
  }

  onChangeNumberSet(event) {
    const comparator = this.refs.numberFilterComparator.value;
    const { value } = event.target;
    this.setState({ isPlaceholderSelected: (value === '') });
    if (comparator === '') {
      return;
    }
    this.props.filterHandler({ number: value, comparator }, Const.FILTER_TYPE.NUMBER);
  }

  onChangeComparator(event) {
    const value = this.refs.numberFilter.value;
    const { comparator } = event.target;
    if (value === '') {
      return;
    }
    this.props.filterHandler({ number: value, comparator }, Const.FILTER_TYPE.NUMBER);
  }

  getComparatorOptions() {
    const optionTags = [];
    optionTags.push(<option key='-1'></option>);
    for (let i = 0; i < this.numberComparators.length; i++) {
      optionTags.push(
        <option key={ i } value={ this.numberComparators[i] }>
          { this.numberComparators[i] }
        </option>
      );
    }
    return optionTags;
  }

  getNumberOptions() {
    const optionTags = [];
    const { options } = this.props;

    optionTags.push(
      <option key='-1' value=''>
        { this.props.placeholder || `Select ${this.props.columnName}...` }
      </option>
    );
    for (let i = 0; i < options.length; i++) {
      optionTags.push(<option key={ i } value={ options[i] }>{ options[i] }</option>);
    }
    return optionTags;
  }

  componentDidMount() {
    const comparator = this.refs.numberFilterComparator.value;
    const number = this.refs.numberFilter.value;
    if (comparator && number) {
      this.props.filterHandler({ number, comparator }, Const.FILTER_TYPE.NUMBER);
    }
  }

  componentWillUnmount() {
    clearTimeout(this.timeout);
  }

  render() {
    const selectClass = classSet(
      'select-filter', 'number-filter-input', 'form-control',
      { 'placeholder-selected': this.state.isPlaceholderSelected });

    return (

        <select ref='numberFilterComparator'
                className='number-filter-comparator form-control'
                onChange={ this.onChangeComparator }
                defaultValue={
                  (this.props.defaultValue) ? this.props.defaultValue.comparator : ''
                }>
          { this.getComparatorOptions() }
        </select>
        {
          (this.props.options) ?
            <select ref='numberFilter'
              className={ selectClass }
              onChange={ this.onChangeNumberSet }
              defaultValue={
                (this.props.defaultValue) ? this.props.defaultValue.number : ''
              }>
              { this.getNumberOptions() }
            </select> :
            <input ref='numberFilter'
                   type='number'
                   className='number-filter-input form-control'
                   placeholder={ this.props.placeholder || `Enter ${this.props.columnName}...` }
                   onChange={ this.onChangeNumber }
                   defaultValue={
                     (this.props.defaultValue) ? this.props.defaultValue.number : ''
                   } />
        }

    );
  }
}

NumberFilter.propTypes = {
  filterHandler: PropTypes.func.isRequired,
  options: PropTypes.arrayOf(PropTypes.number),
  defaultValue: PropTypes.shape({
    number: PropTypes.number,
    comparator: PropTypes.oneOf(legalComparators)
  }),
  delay: PropTypes.number,
  /* eslint consistent-return: 0 */
  numberComparators: function(props, propName) {
    if (!props[propName]) {
      return;
    }
    for (let i = 0; i < props[propName].length; i++) {
      let comparatorIsValid = false;
      for (let j = 0; j < legalComparators.length; j++) {
        if (legalComparators[j] === props[propName][i]) {
          comparatorIsValid = true;
          break;
        }
      }
      if (!comparatorIsValid) {
        return new Error(`Number comparator provided is not supported.
          Use only ${legalComparators}`);
      }
    }
  },
  placeholder: PropTypes.string,
  columnName: PropTypes.string
};

NumberFilter.defaultProps = {
  delay: Const.FILTER_DELAY
};

export default NumberFilter;
<commit_msg>fix got wrong comparator<commit_after>import React, { Component, PropTypes } from 'react';
import classSet from 'classnames';
import Const from '../Const';

const legalComparators = [ '=', '>', '>=', '<', '<=', '!=' ];

class NumberFilter extends Component {
  constructor(props) {
    super(props);
    this.numberComparators = this.props.numberComparators || legalComparators;
    this.timeout = null;
    this.state = {
      isPlaceholderSelected: (this.props.defaultValue === undefined ||
        this.props.defaultValue.number === undefined ||
        (this.props.options &&
          this.props.options.indexOf(this.props.defaultValue.number) === -1))
    };
    this.onChangeNumber = this.onChangeNumber.bind(this);
    this.onChangeNumberSet = this.onChangeNumberSet.bind(this);
    this.onChangeComparator = this.onChangeComparator.bind(this);
  }

  onChangeNumber(event) {
    const comparator = this.refs.numberFilterComparator.value;
    if (comparator === '') {
      return;
    }
    if (this.timeout) {
      clearTimeout(this.timeout);
    }
    const filterValue = event.target.value;
    this.timeout = setTimeout(() => {
      this.props.filterHandler({ number: filterValue, comparator }, Const.FILTER_TYPE.NUMBER);
    }, this.props.delay);
  }

  onChangeNumberSet(event) {
    const comparator = this.refs.numberFilterComparator.value;
    const { value } = event.target;
    this.setState({ isPlaceholderSelected: (value === '') });
    if (comparator === '') {
      return;
    }
    this.props.filterHandler({ number: value, comparator }, Const.FILTER_TYPE.NUMBER);
  }

  onChangeComparator(event) {
    const value = this.refs.numberFilter.value;
    const comparator = event.target.value;
    if (value === '') {
      return;
    }
    this.props.filterHandler({ number: value, comparator }, Const.FILTER_TYPE.NUMBER);
  }

  getComparatorOptions() {
    const optionTags = [];
    optionTags.push(<option key='-1'></option>);
    for (let i = 0; i < this.numberComparators.length; i++) {
      optionTags.push(
        <option key={ i } value={ this.numberComparators[i] }>
          { this.numberComparators[i] }
        </option>
      );
    }
    return optionTags;
  }

  getNumberOptions() {
    const optionTags = [];
    const { options } = this.props;

    optionTags.push(
      <option key='-1' value=''>
        { this.props.placeholder || `Select ${this.props.columnName}...` }
      </option>
    );
    for (let i = 0; i < options.length; i++) {
      optionTags.push(<option key={ i } value={ options[i] }>{ options[i] }</option>);
    }
    return optionTags;
  }

  componentDidMount() {
    const comparator = this.refs.numberFilterComparator.value;
    const number = this.refs.numberFilter.value;
    if (comparator && number) {
      this.props.filterHandler({ number, comparator }, Const.FILTER_TYPE.NUMBER);
    }
  }

  componentWillUnmount() {
    clearTimeout(this.timeout);
  }

  render() {
    const selectClass = classSet(
      'select-filter', 'number-filter-input', 'form-control',
      { 'placeholder-selected': this.state.isPlaceholderSelected });

    return (

        <select ref='numberFilterComparator'
                className='number-filter-comparator form-control'
                onChange={ this.onChangeComparator }
                defaultValue={
                  (this.props.defaultValue) ? this.props.defaultValue.comparator : ''
                }>
          { this.getComparatorOptions() }
        </select>
        {
          (this.props.options) ?
            <select ref='numberFilter'
              className={ selectClass }
              onChange={ this.onChangeNumberSet }
              defaultValue={
                (this.props.defaultValue) ? this.props.defaultValue.number : ''
              }>
              { this.getNumberOptions() }
            </select> :
            <input ref='numberFilter'
                   type='number'
                   className='number-filter-input form-control'
                   placeholder={ this.props.placeholder || `Enter ${this.props.columnName}...` }
                   onChange={ this.onChangeNumber }
                   defaultValue={
                     (this.props.defaultValue) ? this.props.defaultValue.number : ''
                   } />
        }

    );
  }
}

NumberFilter.propTypes = {
  filterHandler: PropTypes.func.isRequired,
  options: PropTypes.arrayOf(PropTypes.number),
  defaultValue: PropTypes.shape({
    number: PropTypes.number,
    comparator: PropTypes.oneOf(legalComparators)
  }),
  delay: PropTypes.number,
  /* eslint consistent-return: 0 */
  numberComparators: function(props, propName) {
    if (!props[propName]) {
      return;
    }
    for (let i = 0; i < props[propName].length; i++) {
      let comparatorIsValid = false;
      for (let j = 0; j < legalComparators.length; j++) {
        if (legalComparators[j] === props[propName][i]) {
          comparatorIsValid = true;
          break;
        }
      }
      if (!comparatorIsValid) {
        return new Error(`Number comparator provided is not supported.
          Use only ${legalComparators}`);
      }
    }
  },
  placeholder: PropTypes.string,
  columnName: PropTypes.string
};

NumberFilter.defaultProps = {
  delay: Const.FILTER_DELAY
};

export default NumberFilter;
<|endoftext|>


<commit_before>// TODO: WIP
/* eslint-disable no-console */
import gcloud from 'gcloud';
import conf from './config';

const gcs = gcloud.storage({
  projectId: conf.get('firebase:projectId'),
  keyFilename: conf.get('firebase:keyFilename'),
});

export default function uploadFile(fileName) {
  const bucket = gcs.bucket(conf.get('firebase:storageBucket'));
  bucket.upload(`captures/${fileName.replace('~', '')}`, (err, file) => {
    if (!err) {
      console.log(`File saved as ${file}`);
    } else {
      console.warn(`Error: ${err}`);
    }
  });
}
<commit_msg>view file<commit_after>// TODO: WIP
/* eslint-disable no-console */
import gcloud from 'gcloud';
import conf from './config';

const gcs = gcloud.storage({
  projectId: conf.get('firebase:projectId'),
  keyFilename: conf.get('firebase:keyFilename'),
});

export default function uploadFile(fileName) {
  const bucket = gcs.bucket(conf.get('firebase:storageBucket'));
  bucket.upload(`captures/${fileName.replace('~', '')}`, (err, file) => {
    if (!err) {
      console.log('File saved');
      console.log(file);
    } else {
      console.warn(`Error: ${err}`);
    }
  });
}
<|endoftext|>