<commit_before>const {Node, TextNode} = require("./node")
const {Fragment} = require("./fragment")
const {Mark} = require("./mark")
const {ContentExpr} = require("./content")

const {copyObj} = require("../util/obj")
const {OrderedMap} = require("../util/orderedmap")

// For node types where all attrs have a default value (or which don't
// have any attributes), build up a single reusable default attribute
// object, and use it for all nodes that don't specify specific
// attributes.
function defaultAttrs(attrs) {
  let defaults = Object.create(null)
  for (let attrName in attrs) {
    let attr = attrs[attrName]
    if (attr.default === undefined) return null
    defaults[attrName] = attr.default
  }
  return defaults
}

function computeAttrs(attrs, value) {
  let built = Object.create(null)
  for (let name in attrs) {
    let given = value && value[name]
    if (given == null) {
      let attr = attrs[name]
      if (attr.default !== undefined)
        given = attr.default
      else if (attr.compute)
        given = attr.compute()
      else
        throw new RangeError("No value supplied for attribute " + name)
    }
    built[name] = given
  }
  return built
}

// ;; Node types are objects allocated once per `Schema`
// and used to tag `Node` instances with a type. They are
// instances of sub-types of this class, and contain information about
// the node type (its name, its allowed attributes, methods for
// serializing it to various formats, information to guide
// deserialization, and so on).
class NodeType {
  constructor(name, schema) {
    // :: string
    // The name the node type has in this schema.
    this.name = name
    // Freeze the attributes, to avoid calling a potentially expensive
    // getter all the time.
    Object.defineProperty(this, "attrs", {value: copyObj(this.attrs)})
    this.defaultAttrs = defaultAttrs(this.attrs)
    this.contentExpr = null
    // :: Schema
    // A link back to the `Schema` the node type belongs to.
    this.schema = schema
  }

  // :: bool
  // True if this is a block type.
  get isBlock() { return false }

  // :: bool
  // True if this is a textblock type, a block that contains inline
  // content.
  get isTextblock() { return false }

  // :: bool
  // True if this is an inline type.
  get isInline() { return false }

  // :: bool
  // True if this is the text node type.
  get isText() { return false }

  // :: bool
  // Controls whether nodes of this type can be selected (as a user
  // node selection).
  get selectable() { return true }

  // :: bool
  // Determines whether nodes of this type can be dragged. Enabling it
  // causes ProseMirror to set a `draggable` attribute on its DOM
  // representation, and to put its HTML serialization into the drag
  // event's [data
  // transfer](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer)
  // when dragged.
  get draggable() { return false }

  // :: bool
  // Controls whether this node type is locked.
  get locked() { return false }

  // :: bool
  // True for node types that allow no content.
  get isLeaf() { return this.contentExpr.isLeaf }

  hasRequiredAttrs(ignore) {
    for (let n in this.attrs)
      if (this.attrs[n].isRequired && (!ignore || !(n in ignore))) return true
    return false
  }

  compatibleContent(other) {
    return this == other || this.contentExpr.compatible(other.contentExpr)
  }

  computeAttrs(attrs) {
    if (!attrs && this.defaultAttrs) return this.defaultAttrs
    else return computeAttrs(this.attrs, attrs)
  }

  // :: (?Object, ?union<Fragment, Node, [Node]>, ?[Mark]) → Node
  // Create a `Node` of this type. The given attributes are
  // checked and defaulted (you can pass `null` to use the type's
  // defaults entirely, if no required attributes exist). `content`
  // may be a `Fragment`, a node, an array of nodes, or
  // `null`. Similarly `marks` may be `null` to default to the empty
  // set of marks.
  create(attrs, content, marks) {
    return new Node(this, this.computeAttrs(attrs), Fragment.from(content), Mark.setFrom(marks))
  }

  // :: (?Object, ?union<Fragment, Node, [Node]>, ?[Mark]) → Node
  // Like [`create`](NodeType.create), but check the given content
  // against the node type's content restrictions, and throw an error
  // if it doesn't match.
  createChecked(attrs, content, marks) {
    attrs = this.computeAttrs(attrs)
    content = Fragment.from(content)
    if (!this.validContent(content, attrs))
      throw new RangeError("Invalid content for node " + this.name)
    return new Node(this, attrs, content, Mark.setFrom(marks))
  }

  // :: (?Object, ?union<Fragment, Node, [Node]>, ?[Mark]) → ?Node
  // Like [`create`](NodeType.create), but see if it is necessary to
  // add nodes to the start or end of the given fragment to make it
  // fit the node. If no fitting wrapping can be found, return null.
  // Note that, due to the fact that required nodes can always be
  // created, this will always succeed if you pass null or
  // `Fragment.empty` as content.
  createAndFill(attrs, content, marks) {
    attrs = this.computeAttrs(attrs)
    content = Fragment.from(content)
    if (content.size) {
      let before = this.contentExpr.start(attrs).fillBefore(content)
      if (!before) return null
      content = before.append(content)
    }
    let after = this.contentExpr.getMatchAt(attrs, content).fillBefore(Fragment.empty, true)
    if (!after) return null
    return new Node(this, attrs, content.append(after), Mark.setFrom(marks))
  }

  // :: (Fragment, ?Object) → bool
  // Returns true if the given fragment is valid content for this node
  // type with the given attributes.
  validContent(content, attrs) {
    return this.contentExpr.matches(attrs, content)
  }

  get ephemeral() { return false }

  static compile(nodes, schema) {
    let result = Object.create(null)
    nodes.forEach((name, spec) => result[name] = new spec.type(name, schema))

    if (!result.doc) throw new RangeError("Every schema needs a 'doc' type")
    if (!result.text) throw new RangeError("Every schema needs a 'text' type")

    return result
  }
}
exports.NodeType = NodeType

// ;; Base type for block nodetypes.
class Block extends NodeType {
  get isBlock() { return true }
  get isTextblock() { return this.contentExpr.inlineContent }
}
exports.Block = Block

// ;; Base type for inline node types.
class Inline extends NodeType {
  get isInline() { return true }
}
exports.Inline = Inline

// ;; The text node type.
class Text extends Inline {
  get selectable() { return false }
  get isText() { return true }

  create(attrs, content, marks) {
    return new TextNode(this, this.computeAttrs(attrs), content, marks)
  }
  toDOM(node) { return node.text }
}
exports.Text = Text

// Attribute descriptors

// ;; Attributes are named values associated with nodes and marks.
// Each node type or mark type has a fixed set of attributes, which
// instances of this class are used to control. Attribute values must
// be JSON-serializable.
class Attribute {
  // :: (Object)
  // Create an attribute. `options` is an object containing the
  // settings for the attributes. The following settings are
  // supported:
  //
  // **`default`**`: ?any`
  //   : The default value for this attribute, to choose when no
  //     explicit value is provided.
  //
  // **`compute`**`: ?() → any`
  //   : A function that computes a default value for the attribute.
  //
  // Attributes that have no default or compute property must be
  // provided whenever a node or mark of a type that has them is
  // created.
  constructor(options = {}) {
    this.default = options.default
    this.compute = options.compute
  }

  get isRequired() {
    return this.default === undefined && !this.compute
  }
}
exports.Attribute = Attribute

// Marks

// ;; Like nodes, marks (which are associated with nodes to signify
// things like emphasis or being part of a link) are tagged with type
// objects, which are instantiated once per `Schema`.
class MarkType {
  constructor(name, rank, schema) {
    // :: string
    // The name of the mark type.
    this.name = name
    Object.defineProperty(this, "attrs", {value: copyObj(this.attrs)})
    this.rank = rank
    // :: Schema
    // The schema that this mark type instance is part of.
    this.schema = schema
    let defaults = defaultAttrs(this.attrs)
    this.instance = defaults && new Mark(this, defaults)
  }

  // :: bool
  // Whether this mark should be active when the cursor is positioned
  // at the end of the mark.
  get inclusiveRight() { return true }

  // :: (?Object) → Mark
  // Create a mark of this type. `attrs` may be `null` or an object
  // containing only some of the mark's attributes. The others, if
  // they have defaults, will be added.
  create(attrs) {
    if (!attrs && this.instance) return this.instance
    return new Mark(this, computeAttrs(this.attrs, attrs))
  }

  static compile(marks, schema) {
    let result = Object.create(null), rank = 0
    marks.forEach((name, markType) => result[name] = new markType(name, rank++, schema))
    return result
  }

  // :: ([Mark]) → [Mark]
  // When there is a mark of this type in the given set, a new set
  // without it is returned. Otherwise, the input set is returned.
  removeFromSet(set) {
    for (var i = 0; i < set.length; i++)
      if (set[i].type == this)
        return set.slice(0, i).concat(set.slice(i + 1))
    return set
  }

  // :: ([Mark]) → ?Mark
  // Tests whether there is a mark of this type in the given set.
  isInSet(set) {
    for (let i = 0; i < set.length; i++)
      if (set[i].type == this) return set[i]
  }
}
exports.MarkType = MarkType

// ;; #path=SchemaSpec #kind=interface
// An object describing a schema, as passed to the `Schema`
// constructor.

// :: union<Object<NodeSpec>, OrderedMap<NodeSpec>> #path=SchemaSpec.nodes
// The node types in this schema. Maps names to `NodeSpec` objects
// describing the node to be associated with that name. Their order is significant

// :: ?union<Object<constructor<MarkType>>, OrderedMap<constructor<MarkType>>> #path=SchemaSpec.marks
// The mark types that exist in this schema.

// ;; #path=NodeSpec #kind=interface

// :: constructor<NodeType> #path=NodeSpec.type
// The `NodeType` class to be used for this node.

// :: ?string #path=NodeSpec.content
// The content expression for this node, as parsed by
// `ContentExpr.parse`. When not given, the node does not allow any
// content.

// :: ?string #path=NodeSpec.group
// The group or space-separated groups to which this node belongs, as
// referred to in the content expressions for the schema.

// ;; Each document is based on a single schema, which provides the
// node and mark types that it is made up of (which, in turn,
// determine the structure it is allowed to have).
class Schema {
  // :: (SchemaSpec)
  // Construct a schema from a specification.
  constructor(spec) {
    // :: OrderedMap<NodeSpec> The node specs that the schema is based on.
    this.nodeSpec = OrderedMap.from(spec.nodes)
    // :: OrderedMap<constructor<MarkType>> The mark spec that the schema is based on.
    this.markSpec = OrderedMap.from(spec.marks)

    // :: Object<NodeType>
    // An object mapping the schema's node names to node type objects.
    this.nodes = NodeType.compile(this.nodeSpec, this)
    // :: Object<MarkType>
    // A map from mark names to mark type objects.
    this.marks = MarkType.compile(this.markSpec, this)
    for (let prop in this.nodes) {
      if (prop in this.marks)
        throw new RangeError(prop + " can not be both a node and a mark")
      let type = this.nodes[prop]
      type.contentExpr = ContentExpr.parse(type, this.nodeSpec.get(prop).content || "", this.nodeSpec)
    }

    // :: Object
    // An object for storing whatever values modules may want to
    // compute and cache per schema. (If you want to store something
    // in it, try to use property names unlikely to clash.)
    this.cached = Object.create(null)
    this.cached.wrappings = Object.create(null)

    this.node = this.node.bind(this)
    this.text = this.text.bind(this)
    this.nodeFromJSON = this.nodeFromJSON.bind(this)
    this.markFromJSON = this.markFromJSON.bind(this)
  }

  // :: (union<string, NodeType>, ?Object, ?union<Fragment, Node, [Node]>, ?[Mark]) → Node
  // Create a node in this schema. The `type` may be a string or a
  // `NodeType` instance. Attributes will be extended
  // with defaults, `content` may be a `Fragment`,
  // `null`, a `Node`, or an array of nodes.
  //
  // When creating a text node, `content` should be a string and is
  // interpreted as the node's text.
  //
  // This method is bound to the Schema, meaning you don't have to
  // call it as a method, but can pass it to higher-order functions
  // and such.
  node(type, attrs, content, marks) {
    if (typeof type == "string")
      type = this.nodeType(type)
    else if (!(type instanceof NodeType))
      throw new RangeError("Invalid node type: " + type)
    else if (type.schema != this)
      throw new RangeError("Node type from different schema used (" + type.name + ")")

    return type.createChecked(attrs, content, marks)
  }

  // :: (string, ?[Mark]) → Node
  // Create a text node in the schema. This method is bound to the
  // Schema. Empty text nodes are not allowed.
  text(text, marks) {
    return this.nodes.text.create(null, text, Mark.setFrom(marks))
  }

  // :: (string, ?Object) → Mark
  // Create a mark with the named type
  mark(name, attrs) {
    let spec = this.marks[name]
    if (!spec) throw new RangeError("No mark named " + name)
    return spec.create(attrs)
  }

  // :: (Object) → Node
  // Deserialize a node from its JSON representation. This method is
  // bound.
  nodeFromJSON(json) {
    return Node.fromJSON(this, json)
  }

  // :: (Object) → Mark
  // Deserialize a mark from its JSON representation. This method is
  // bound.
  markFromJSON(json) {
    let type = this.marks[json._]
    let attrs = null
    for (let prop in json) if (prop != "_") {
      if (!attrs) attrs = Object.create(null)
      attrs[prop] = json[prop]
    }
    return attrs ? type.create(attrs) : type.instance
  }

  // :: (string) → NodeType
  // Get the `NodeType` associated with the given name in
  // this schema, or raise an error if it does not exist.
  nodeType(name) {
    let found = this.nodes[name]
    if (!found) throw new RangeError("Unknown node type: " + name)
    return found
  }
}
exports.Schema = Schema
<commit_msg>Add a `data` field to Schema objects<commit_after>const {Node, TextNode} = require("./node")
const {Fragment} = require("./fragment")
const {Mark} = require("./mark")
const {ContentExpr} = require("./content")

const {copyObj} = require("../util/obj")
const {OrderedMap} = require("../util/orderedmap")

// For node types where all attrs have a default value (or which don't
// have any attributes), build up a single reusable default attribute
// object, and use it for all nodes that don't specify specific
// attributes.
function defaultAttrs(attrs) {
  let defaults = Object.create(null)
  for (let attrName in attrs) {
    let attr = attrs[attrName]
    if (attr.default === undefined) return null
    defaults[attrName] = attr.default
  }
  return defaults
}

function computeAttrs(attrs, value) {
  let built = Object.create(null)
  for (let name in attrs) {
    let given = value && value[name]
    if (given == null) {
      let attr = attrs[name]
      if (attr.default !== undefined)
        given = attr.default
      else if (attr.compute)
        given = attr.compute()
      else
        throw new RangeError("No value supplied for attribute " + name)
    }
    built[name] = given
  }
  return built
}

// ;; Node types are objects allocated once per `Schema`
// and used to tag `Node` instances with a type. They are
// instances of sub-types of this class, and contain information about
// the node type (its name, its allowed attributes, methods for
// serializing it to various formats, information to guide
// deserialization, and so on).
class NodeType {
  constructor(name, schema) {
    // :: string
    // The name the node type has in this schema.
    this.name = name
    // Freeze the attributes, to avoid calling a potentially expensive
    // getter all the time.
    Object.defineProperty(this, "attrs", {value: copyObj(this.attrs)})
    this.defaultAttrs = defaultAttrs(this.attrs)
    this.contentExpr = null
    // :: Schema
    // A link back to the `Schema` the node type belongs to.
    this.schema = schema
  }

  // :: bool
  // True if this is a block type.
  get isBlock() { return false }

  // :: bool
  // True if this is a textblock type, a block that contains inline
  // content.
  get isTextblock() { return false }

  // :: bool
  // True if this is an inline type.
  get isInline() { return false }

  // :: bool
  // True if this is the text node type.
  get isText() { return false }

  // :: bool
  // Controls whether nodes of this type can be selected (as a user
  // node selection).
  get selectable() { return true }

  // :: bool
  // Determines whether nodes of this type can be dragged. Enabling it
  // causes ProseMirror to set a `draggable` attribute on its DOM
  // representation, and to put its HTML serialization into the drag
  // event's [data
  // transfer](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer)
  // when dragged.
  get draggable() { return false }

  // :: bool
  // Controls whether this node type is locked.
  get locked() { return false }

  // :: bool
  // True for node types that allow no content.
  get isLeaf() { return this.contentExpr.isLeaf }

  hasRequiredAttrs(ignore) {
    for (let n in this.attrs)
      if (this.attrs[n].isRequired && (!ignore || !(n in ignore))) return true
    return false
  }

  compatibleContent(other) {
    return this == other || this.contentExpr.compatible(other.contentExpr)
  }

  computeAttrs(attrs) {
    if (!attrs && this.defaultAttrs) return this.defaultAttrs
    else return computeAttrs(this.attrs, attrs)
  }

  // :: (?Object, ?union<Fragment, Node, [Node]>, ?[Mark]) → Node
  // Create a `Node` of this type. The given attributes are
  // checked and defaulted (you can pass `null` to use the type's
  // defaults entirely, if no required attributes exist). `content`
  // may be a `Fragment`, a node, an array of nodes, or
  // `null`. Similarly `marks` may be `null` to default to the empty
  // set of marks.
  create(attrs, content, marks) {
    return new Node(this, this.computeAttrs(attrs), Fragment.from(content), Mark.setFrom(marks))
  }

  // :: (?Object, ?union<Fragment, Node, [Node]>, ?[Mark]) → Node
  // Like [`create`](NodeType.create), but check the given content
  // against the node type's content restrictions, and throw an error
  // if it doesn't match.
  createChecked(attrs, content, marks) {
    attrs = this.computeAttrs(attrs)
    content = Fragment.from(content)
    if (!this.validContent(content, attrs))
      throw new RangeError("Invalid content for node " + this.name)
    return new Node(this, attrs, content, Mark.setFrom(marks))
  }

  // :: (?Object, ?union<Fragment, Node, [Node]>, ?[Mark]) → ?Node
  // Like [`create`](NodeType.create), but see if it is necessary to
  // add nodes to the start or end of the given fragment to make it
  // fit the node. If no fitting wrapping can be found, return null.
  // Note that, due to the fact that required nodes can always be
  // created, this will always succeed if you pass null or
  // `Fragment.empty` as content.
  createAndFill(attrs, content, marks) {
    attrs = this.computeAttrs(attrs)
    content = Fragment.from(content)
    if (content.size) {
      let before = this.contentExpr.start(attrs).fillBefore(content)
      if (!before) return null
      content = before.append(content)
    }
    let after = this.contentExpr.getMatchAt(attrs, content).fillBefore(Fragment.empty, true)
    if (!after) return null
    return new Node(this, attrs, content.append(after), Mark.setFrom(marks))
  }

  // :: (Fragment, ?Object) → bool
  // Returns true if the given fragment is valid content for this node
  // type with the given attributes.
  validContent(content, attrs) {
    return this.contentExpr.matches(attrs, content)
  }

  get ephemeral() { return false }

  static compile(nodes, schema) {
    let result = Object.create(null)
    nodes.forEach((name, spec) => result[name] = new spec.type(name, schema))

    if (!result.doc) throw new RangeError("Every schema needs a 'doc' type")
    if (!result.text) throw new RangeError("Every schema needs a 'text' type")

    return result
  }
}
exports.NodeType = NodeType

// ;; Base type for block nodetypes.
class Block extends NodeType {
  get isBlock() { return true }
  get isTextblock() { return this.contentExpr.inlineContent }
}
exports.Block = Block

// ;; Base type for inline node types.
class Inline extends NodeType {
  get isInline() { return true }
}
exports.Inline = Inline

// ;; The text node type.
class Text extends Inline {
  get selectable() { return false }
  get isText() { return true }

  create(attrs, content, marks) {
    return new TextNode(this, this.computeAttrs(attrs), content, marks)
  }
  toDOM(node) { return node.text }
}
exports.Text = Text

// Attribute descriptors

// ;; Attributes are named values associated with nodes and marks.
// Each node type or mark type has a fixed set of attributes, which
// instances of this class are used to control. Attribute values must
// be JSON-serializable.
class Attribute {
  // :: (Object)
  // Create an attribute. `options` is an object containing the
  // settings for the attributes. The following settings are
  // supported:
  //
  // **`default`**`: ?any`
  //   : The default value for this attribute, to choose when no
  //     explicit value is provided.
  //
  // **`compute`**`: ?() → any`
  //   : A function that computes a default value for the attribute.
  //
  // Attributes that have no default or compute property must be
  // provided whenever a node or mark of a type that has them is
  // created.
  constructor(options = {}) {
    this.default = options.default
    this.compute = options.compute
  }

  get isRequired() {
    return this.default === undefined && !this.compute
  }
}
exports.Attribute = Attribute

// Marks

// ;; Like nodes, marks (which are associated with nodes to signify
// things like emphasis or being part of a link) are tagged with type
// objects, which are instantiated once per `Schema`.
class MarkType {
  constructor(name, rank, schema) {
    // :: string
    // The name of the mark type.
    this.name = name
    Object.defineProperty(this, "attrs", {value: copyObj(this.attrs)})
    this.rank = rank
    // :: Schema
    // The schema that this mark type instance is part of.
    this.schema = schema
    let defaults = defaultAttrs(this.attrs)
    this.instance = defaults && new Mark(this, defaults)
  }

  // :: bool
  // Whether this mark should be active when the cursor is positioned
  // at the end of the mark.
  get inclusiveRight() { return true }

  // :: (?Object) → Mark
  // Create a mark of this type. `attrs` may be `null` or an object
  // containing only some of the mark's attributes. The others, if
  // they have defaults, will be added.
  create(attrs) {
    if (!attrs && this.instance) return this.instance
    return new Mark(this, computeAttrs(this.attrs, attrs))
  }

  static compile(marks, schema) {
    let result = Object.create(null), rank = 0
    marks.forEach((name, markType) => result[name] = new markType(name, rank++, schema))
    return result
  }

  // :: ([Mark]) → [Mark]
  // When there is a mark of this type in the given set, a new set
  // without it is returned. Otherwise, the input set is returned.
  removeFromSet(set) {
    for (var i = 0; i < set.length; i++)
      if (set[i].type == this)
        return set.slice(0, i).concat(set.slice(i + 1))
    return set
  }

  // :: ([Mark]) → ?Mark
  // Tests whether there is a mark of this type in the given set.
  isInSet(set) {
    for (let i = 0; i < set.length; i++)
      if (set[i].type == this) return set[i]
  }
}
exports.MarkType = MarkType

// ;; #path=SchemaSpec #kind=interface
// An object describing a schema, as passed to the `Schema`
// constructor.

// :: union<Object<NodeSpec>, OrderedMap<NodeSpec>> #path=SchemaSpec.nodes
// The node types in this schema. Maps names to `NodeSpec` objects
// describing the node to be associated with that name. Their order is significant

// :: ?union<Object<constructor<MarkType>>, OrderedMap<constructor<MarkType>>> #path=SchemaSpec.marks
// The mark types that exist in this schema.

// ;; #path=NodeSpec #kind=interface

// :: constructor<NodeType> #path=NodeSpec.type
// The `NodeType` class to be used for this node.

// :: ?string #path=NodeSpec.content
// The content expression for this node, as parsed by
// `ContentExpr.parse`. When not given, the node does not allow any
// content.

// :: ?string #path=NodeSpec.group
// The group or space-separated groups to which this node belongs, as
// referred to in the content expressions for the schema.

// ;; Each document is based on a single schema, which provides the
// node and mark types that it is made up of (which, in turn,
// determine the structure it is allowed to have).
class Schema {
  // :: (SchemaSpec, any)
  // Construct a schema from a specification.
  constructor(spec, data) {
    // :: OrderedMap<NodeSpec> The node specs that the schema is based on.
    this.nodeSpec = OrderedMap.from(spec.nodes)
    // :: OrderedMap<constructor<MarkType>> The mark spec that the schema is based on.
    this.markSpec = OrderedMap.from(spec.marks)

    // :: any A generic field that you can use (by passing a value to
    // the constructor) to store arbitrary data or references in your
    // schema object, for use by node- or mark- methods.
    this.data = data

    // :: Object<NodeType>
    // An object mapping the schema's node names to node type objects.
    this.nodes = NodeType.compile(this.nodeSpec, this)
    // :: Object<MarkType>
    // A map from mark names to mark type objects.
    this.marks = MarkType.compile(this.markSpec, this)
    for (let prop in this.nodes) {
      if (prop in this.marks)
        throw new RangeError(prop + " can not be both a node and a mark")
      let type = this.nodes[prop]
      type.contentExpr = ContentExpr.parse(type, this.nodeSpec.get(prop).content || "", this.nodeSpec)
    }

    // :: Object
    // An object for storing whatever values modules may want to
    // compute and cache per schema. (If you want to store something
    // in it, try to use property names unlikely to clash.)
    this.cached = Object.create(null)
    this.cached.wrappings = Object.create(null)

    this.node = this.node.bind(this)
    this.text = this.text.bind(this)
    this.nodeFromJSON = this.nodeFromJSON.bind(this)
    this.markFromJSON = this.markFromJSON.bind(this)
  }

  // :: (union<string, NodeType>, ?Object, ?union<Fragment, Node, [Node]>, ?[Mark]) → Node
  // Create a node in this schema. The `type` may be a string or a
  // `NodeType` instance. Attributes will be extended
  // with defaults, `content` may be a `Fragment`,
  // `null`, a `Node`, or an array of nodes.
  //
  // When creating a text node, `content` should be a string and is
  // interpreted as the node's text.
  //
  // This method is bound to the Schema, meaning you don't have to
  // call it as a method, but can pass it to higher-order functions
  // and such.
  node(type, attrs, content, marks) {
    if (typeof type == "string")
      type = this.nodeType(type)
    else if (!(type instanceof NodeType))
      throw new RangeError("Invalid node type: " + type)
    else if (type.schema != this)
      throw new RangeError("Node type from different schema used (" + type.name + ")")

    return type.createChecked(attrs, content, marks)
  }

  // :: (string, ?[Mark]) → Node
  // Create a text node in the schema. This method is bound to the
  // Schema. Empty text nodes are not allowed.
  text(text, marks) {
    return this.nodes.text.create(null, text, Mark.setFrom(marks))
  }

  // :: (string, ?Object) → Mark
  // Create a mark with the named type
  mark(name, attrs) {
    let spec = this.marks[name]
    if (!spec) throw new RangeError("No mark named " + name)
    return spec.create(attrs)
  }

  // :: (Object) → Node
  // Deserialize a node from its JSON representation. This method is
  // bound.
  nodeFromJSON(json) {
    return Node.fromJSON(this, json)
  }

  // :: (Object) → Mark
  // Deserialize a mark from its JSON representation. This method is
  // bound.
  markFromJSON(json) {
    let type = this.marks[json._]
    let attrs = null
    for (let prop in json) if (prop != "_") {
      if (!attrs) attrs = Object.create(null)
      attrs[prop] = json[prop]
    }
    return attrs ? type.create(attrs) : type.instance
  }

  // :: (string) → NodeType
  // Get the `NodeType` associated with the given name in
  // this schema, or raise an error if it does not exist.
  nodeType(name) {
    let found = this.nodes[name]
    if (!found) throw new RangeError("Unknown node type: " + name)
    return found
  }
}
exports.Schema = Schema
<|endoftext|>


<commit_before>
'use strict';

const _ = require('lodash');
const Script = require('smooch-bot').Script;

const scriptRules = require('./script.json');

var myDate = new Date();
var groet = '';

/* hour is before noon */
if ( myDate.getHours() < 12 )
{
groet = "Goeiemorgen 🌝";
}
else  /* Hour is from noon to 5pm (actually to 5:59 pm) */
if ( myDate.getHours() >= 12 && myDate.getHours() <= 17 )
{
groet = "Goedendag 🌞";
}
else  /* the hour is after 5pm, so it is between 6pm and midnight */
if ( myDate.getHours() > 17 && myDate.getHours() <= 24 )
{
groet = "Goedenavond 🌙";
}
else
if (myDate.getHours() > 20  || myDate.getHours() <= 8 )
{
  groet = "Goedenavond 🌙  Bedankt voor je bezoek maar op dit moment is Independer echter gesloten. 🕘 Uiteraard kun je met onze IndyBot verder praten maar er is helaas niemand die jouw vraag specifiek kan beantwoorden. Je kan je vraag ook doormailen 📩 naar info@independer. Dan komt het altijd goed.";
}
else  /* the hour is not between 0 and 24, so something is wrong */
{
groet = "Welkom. ";
}

function wait(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}

module.exports = new Script({

 processing: {
prompt: (bot) => bot.say('![](http://www.bieg.nl/beeld/speechbubble.gif)'),
 receive: () => 'processing'
 },

start: {
    receive: (bot,message) => {
        const opening = message.text.trim().toUpperCase();
        return bot.say(`${groet}... Wat voor soort hypotheek zoek je? `)
        .then(() => bot.say(`![](http://www.bieg.nl/beeld/woningen.jpg)`))
        .then(() => bot.say(`%[Starters Hypotheek](postback:hypotheektype_starter) %[Nieuwe hypotheek](postback:hypotheektype_nieuw) %[Hypotheek oversluiten](postback:hypotheektype_oversluiten)`))
        .then(() => 'selecteerHypotheek');
    }
},

selecteerHypotheek: {
    receive: (bot, message) => {
      switch(message.text) {
      case 'Hoi':
            return bot.say(`${groet} waar ben je naar op zoek? %[Starters hypotheek](postback:hypotheektype_starter) %[Nieuwe hypotheek](postback:hypotheektype_nieuw) %[Hypotheek oversluiten](postback:hypotheektype_oversluiten)`)
            .then(() => 'askName')
          break;
      case 'Starters Hypotheek':
          return bot.say(`Wat voor type woning zoek je? `)
          .then(() => bot.say(`%[🏬 Appartement](postback:hypotheekkeuze_appartement) %[🏠 Huis](postback:hypotheekkeuze_huis) %[📭 Vakantiewoning](postback:hypotheekkeuze_vakantiewoning)`))
          .then(() => 'woningType')
              break;
        case 'Nieuwe hypotheek':
          return bot.say(`😞 Helaas biedt Independer momenteel alleen Starters een hypotheek aan.`)
          .then(() => bot.say(`Via onderstaande link kun je de beste hypotheekadviseur voor jou vinden. %[💼 Zoek Hypotheek Adviseur](https://www.independer.nl/hypotheekadviseur/jelocatie.aspx)`))
            .then(() => 'finish')
          break;
        case 'Hypotheek oversluiten':
          return bot.say(`😟 Het spijt me maar op dit moment biedt Independer alleen  Starters een hypotheek.`)
          .then(()=> bot.say(`Als het allemaal wel zo ver is, wil je dan een update ontvangen? %[Ja, dat wil ik wel](postback:update_ja) %[Nee, dat hoeft niet](postback:update_nee)`))
            .then(() => 'updateOntvangen')
          break;
        default:
          return bot.say(`![](http://www.bieg.nl/beeld/speechbubble.gif)`)
            .then(() => 'processing')
          break;
      }
    }
},

updateOntvangen: {
    receive: (bot, message) => {
      switch(message.text) {
        case 'Ja, dat wil ik wel':
            return bot.say(`👍 Leuk, dan houd ik  je op de hoogte zodra er weer nieuws is.`)
            .then(() => 'update_ja');
          break;
          case 'Nee, dat hoeft niet':
              receive: () => 'bye'
              break;
          default:
            receive => 'processing'
            break;
}
}
},

update_ja: {
  prompt: (bot) => bot.say('Wat is je email adres?'),
      receive: (bot, message) => {
          const emailer=message.text.trim();
          return bot.setProp('emailer', emailer)
              .then(()  => bot.say(`Ok - ✉  dan hou ik je via ${emailer} op de hoogte.`))
              .then(()  =>'lastCheck')
    }
},
update_nee: {
  receive: () => 'bye'
},

hypotheekkeuze_appartement: {
receive: (bot,message)  => {
    //  prompt: (bot) => bot.say(`![](http:www.bieg.nl/beeld/appartement.jpg)`)
    receive: () => 'askName'
}
},
hypotheekkeuze_huis: {
//    prompt: (bot) => bot.say(`![](http:www.bieg.nl/beeld/appartement.jpg)`)
  receive: () => 'askName'
},
hypotheekkeuze_vakantiewoning: {
//    prompt: (bot) => bot.say(`![](http:www.bieg.nl/beeld/appartement.jpg)`)
    receive: () => 'askName'
},
hypotheekStarter: {
    receive: () => 'askName'
},
hypotheektype_nieuw: {
    prompt: (bot) => bot.say('Independer biedt momenteel alleen voor Starters een hypotheek. Onderstaande link bied je meer informatie %[Hypotheek Adviseur](https://www.independer.nl/hypotheekadviseur/intro.aspx)'),
    receive: () => 'processing'
},

woningType: {
  receive: (bot, message) => {
    switch(message.text) {
        case '🏬 Appartement':
          return bot.say(`Nice!`)
          .then(() => 'vervolgVragen')
          break;
      case '🏠 Huis':
          return bot.say(`Leuk`)
          .then(() => 'vervolgVragen')
          break;
    case '📭 Vakantiewoning':
          return bot.say(`Gezellig`)
          .then(() => 'vervolgVragen')
          break;
      default:
        receive => 'processing'
        break;
        }
    }
},

vervolgVragen: {
  prompt: (bot) => bot.say('Hoe heet je eigelijk? Dat maakt het praten een stuk makkelijker...'),
  receive: (bot, message) => {
      const name = message.text;
      return bot.setProp('name', name)
          .then(() => bot.say('Hoi ${name}. 📋 Ik heb nog wat vragen voor je om verder te kunnen.'))
          .then(() => 'processing');
    }
},

lastCheck: {
    prompt: (bot) => bot.say(' Is er nog iets waar ik  je bij kan helpen?  🚦  %[Ja, nou je het zegt ](postback:verzoekPDF) %[Nee hoor](postback:bye)'),
          receive: (bot, message) => {
            switch(message.text) {
              case 'Nee hoor':
                  receive: ()  => 'bye'
                  break;
             case 'Ja':
                  receive: () => 'speak'
                  break;
            case 'Nee':
                  receive: () => 'bye'
                  break;
            default:
              return bot.say('![](http://www.bieg.nl/beeld/speechbubble.gif)')
                .then(() => 'processing')
              break;
            }
        }
},

verzoekPDF: {
    //bot.sendMessage  = "Heb je wat algemene informatie voor me?";
    return bot.say('![](http://www.bieg.nl/beeld/info.pdf)')
      .then(() => 'processing')
},

bye: {
    prompt: (bot) => bot.say('Fijn je gesproken te hebben. Bedankt voor je tijd ⏲'),
    receive: ()  => 'finish'
},

// error: {
// prompt: (bot) => bot.say('Sorry - kun je dat nog eens zeggen?  Er ging iets mis...'),
// receive: () => ''
// },

finish: {
receive: () => 'finish'
},

  speak: {
          receive: (bot, message) => {

              let upperText = message.text.trim().toUpperCase();

              function updateSilent() {
                  switch (upperText) {
                      case "CONNECT ME":
                          return bot.setProp("silent", true);
                      case "DISCONNECT":
                          return bot.setProp("silent", false);
                      default:
                          return Promise.resolve();
                  }
              }

              function getSilent() {
                  return bot.getProp("silent");
              }

              function processMessage(isSilent) {
                  if (isSilent) {
                      return Promise.resolve("speak");
                  }

                  if (!_.has(scriptRules, upperText)) {
                      return bot.say(`So, I'm good at structured conversations but stickers, emoji and sentences still confuse me. Say 'more' to chat about something else.`).then(() => 'speak');
                  }

                  var response = scriptRules[upperText];
                  var lines = response.split('\n');

                  var p = Promise.resolve();
                  _.each(lines, function(line) {
                      line = line.trim();
                      p = p.then(function() {
                          console.log(line);
                          return wait(50).then(function() {
                              return bot.say(line);
                          });
                      });
                  });

                  return p.then(() => 'speak');
              }

              return updateSilent()
                  .then(getSilent)
                  .then(processMessage);
          }
      }
});
<commit_msg>rwr<commit_after>
'use strict';

const _ = require('lodash');
const Script = require('smooch-bot').Script;

const scriptRules = require('./script.json');

var myDate = new Date();
var groet = '';

/* hour is before noon */
if ( myDate.getHours() < 12 )
{
groet = "Goeiemorgen 🌝";
}
else  /* Hour is from noon to 5pm (actually to 5:59 pm) */
if ( myDate.getHours() >= 12 && myDate.getHours() <= 17 )
{
groet = "Goedendag 🌞";
}
else  /* the hour is after 5pm, so it is between 6pm and midnight */
if ( myDate.getHours() > 17 && myDate.getHours() <= 24 )
{
groet = "Goedenavond 🌙";
}
else
if (myDate.getHours() > 20  || myDate.getHours() <= 8 )
{
  groet = "Goedenavond 🌙  Bedankt voor je bezoek maar op dit moment is Independer echter gesloten. 🕘 Uiteraard kun je met onze IndyBot verder praten maar er is helaas niemand die jouw vraag specifiek kan beantwoorden. Je kan je vraag ook doormailen 📩 naar info@independer. Dan komt het altijd goed.";
}
else  /* the hour is not between 0 and 24, so something is wrong */
{
groet = "Welkom. ";
}

function wait(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}

module.exports = new Script({

 processing: {
prompt: (bot) => bot.say('![](http://www.bieg.nl/beeld/speechbubble.gif)'),
 receive: () => 'processing'
 },

start: {
    receive: (bot,message) => {
        const opening = message.text.trim().toUpperCase();
        return bot.say(`${groet}... Wat voor soort hypotheek zoek je? `)
        .then(() => bot.say(`![](http://www.bieg.nl/beeld/woningen.jpg)`))
        .then(() => bot.say(`%[Starters Hypotheek](postback:hypotheektype_starter) %[Nieuwe hypotheek](postback:hypotheektype_nieuw) %[Hypotheek oversluiten](postback:hypotheektype_oversluiten)`))
        .then(() => 'selecteerHypotheek');
    }
},

selecteerHypotheek: {
    receive: (bot, message) => {
      switch(message.text) {
      case 'Hoi':
            return bot.say(`${groet} waar ben je naar op zoek? %[Starters hypotheek](postback:hypotheektype_starter) %[Nieuwe hypotheek](postback:hypotheektype_nieuw) %[Hypotheek oversluiten](postback:hypotheektype_oversluiten)`)
            .then(() => 'askName')
          break;
      case 'Starters Hypotheek':
          return bot.say(`Wat voor type woning zoek je? `)
          .then(() => bot.say(`%[🏬 Appartement](postback:hypotheekkeuze_appartement) %[🏠 Huis](postback:hypotheekkeuze_huis) %[📭 Vakantiewoning](postback:hypotheekkeuze_vakantiewoning)`))
          .then(() => 'woningType')
              break;
        case 'Nieuwe hypotheek':
          return bot.say(`😞 Helaas biedt Independer momenteel alleen Starters een hypotheek aan.`)
          .then(() => bot.say(`Via onderstaande link kun je de beste hypotheekadviseur voor jou vinden. %[💼 Zoek Hypotheek Adviseur](https://www.independer.nl/hypotheekadviseur/jelocatie.aspx)`))
            .then(() => 'finish')
          break;
        case 'Hypotheek oversluiten':
          return bot.say(`😟 Het spijt me maar op dit moment biedt Independer alleen  Starters een hypotheek.`)
          .then(()=> bot.say(`Als het allemaal wel zo ver is, wil je dan een update ontvangen? %[Ja, dat wil ik wel](postback:update_ja) %[Nee, dat hoeft niet](postback:update_nee)`))
            .then(() => 'updateOntvangen')
          break;
        default:
          return bot.say(`![](http://www.bieg.nl/beeld/speechbubble.gif)`)
            .then(() => 'processing')
          break;
      }
    }
},

updateOntvangen: {
    receive: (bot, message) => {
      switch(message.text) {
        case 'Ja, dat wil ik wel':
            return bot.say(`👍 Leuk, dan houd ik  je op de hoogte zodra er weer nieuws is.`)
            .then(() => 'update_ja');
          break;
          case 'Nee, dat hoeft niet':
              receive: () => 'bye'
              break;
          default:
            receive => 'processing'
            break;
}
}
},

update_ja: {
  prompt: (bot) => bot.say('Wat is je email adres?'),
      receive: (bot, message) => {
          const emailer=message.text.trim();
          return bot.setProp('emailer', emailer)
              .then(()  => bot.say(`Ok - ✉  dan hou ik je via ${emailer} op de hoogte.`))
              .then(()  =>'lastCheck')
    }
},
update_nee: {
  receive: () => 'bye'
},

hypotheekkeuze_appartement: {
receive: (bot,message)  => {
    //  prompt: (bot) => bot.say(`![](http:www.bieg.nl/beeld/appartement.jpg)`)
    receive: () => 'askName'
}
},
hypotheekkeuze_huis: {
//    prompt: (bot) => bot.say(`![](http:www.bieg.nl/beeld/appartement.jpg)`)
  receive: () => 'askName'
},
hypotheekkeuze_vakantiewoning: {
//    prompt: (bot) => bot.say(`![](http:www.bieg.nl/beeld/appartement.jpg)`)
    receive: () => 'askName'
},
hypotheekStarter: {
    receive: () => 'askName'
},
hypotheektype_nieuw: {
    prompt: (bot) => bot.say('Independer biedt momenteel alleen voor Starters een hypotheek. Onderstaande link bied je meer informatie %[Hypotheek Adviseur](https://www.independer.nl/hypotheekadviseur/intro.aspx)'),
    receive: () => 'processing'
},

woningType: {
  receive: (bot, message) => {
    switch(message.text) {
        case '🏬 Appartement':
          return bot.say(`Nice!`)
          .then(() => 'vervolgVragen')
          break;
      case '🏠 Huis':
          return bot.say(`Leuk`)
          .then(() => 'vervolgVragen')
          break;
    case '📭 Vakantiewoning':
          return bot.say(`Gezellig`)
          .then(() => 'vervolgVragen')
          break;
      default:
        receive => 'processing'
        break;
        }
    }
},

vervolgVragen: {
  prompt: (bot) => bot.say('Hoe heet je eigelijk? Dat maakt het praten een stuk makkelijker...'),
  receive: (bot, message) => {
      const name = message.text;
      return bot.setProp('name', name)
          .then(() => bot.say('Hoi ${name}. 📋 Ik heb nog wat vragen voor je om verder te kunnen.'))
          .then(() => 'processing');
    }
},

lastCheck: {
    prompt: (bot) => bot.say(' Is er nog iets waar ik  je bij kan helpen?  🚦  %[Ja, nou je het zegt ](postback:verzoekPDF) %[Nee hoor](postback:bye)'),
          receive: (bot, message) => {
            switch(message.text) {
              case 'Nee hoor':
                  receive: ()  => 'bye'
                  break;
            case 'Ja, nou je het zegt':
                  receive: () => 'verzoekPDF'
                  break;
             case 'Ja':
                  receive: () => 'speak'
                  break;
            case 'Nee':
                  receive: () => 'bye'
                  break;
            default:
              return bot.say('![](http://www.bieg.nl/beeld/speechbubble.gif)')
                .then(() => 'processing')
              break;
            }
        }
},

verzoekPDF: {
    //bot.sendMessage  = "Heb je wat algemene informatie voor me?";
    return bot.say('![](http://www.bieg.nl/beeld/info.pdf)')
      .then(() => 'processing')
},

bye: {
    prompt: (bot) => bot.say('Fijn je gesproken te hebben. Bedankt voor je tijd ⏲'),
    receive: ()  => 'finish'
},

// error: {
// prompt: (bot) => bot.say('Sorry - kun je dat nog eens zeggen?  Er ging iets mis...'),
// receive: () => ''
// },

finish: {
receive: () => 'finish'
},

  speak: {
          receive: (bot, message) => {

              let upperText = message.text.trim().toUpperCase();

              function updateSilent() {
                  switch (upperText) {
                      case "CONNECT ME":
                          return bot.setProp("silent", true);
                      case "DISCONNECT":
                          return bot.setProp("silent", false);
                      default:
                          return Promise.resolve();
                  }
              }

              function getSilent() {
                  return bot.getProp("silent");
              }

              function processMessage(isSilent) {
                  if (isSilent) {
                      return Promise.resolve("speak");
                  }

                  if (!_.has(scriptRules, upperText)) {
                      return bot.say(`So, I'm good at structured conversations but stickers, emoji and sentences still confuse me. Say 'more' to chat about something else.`).then(() => 'speak');
                  }

                  var response = scriptRules[upperText];
                  var lines = response.split('\n');

                  var p = Promise.resolve();
                  _.each(lines, function(line) {
                      line = line.trim();
                      p = p.then(function() {
                          console.log(line);
                          return wait(50).then(function() {
                              return bot.say(line);
                          });
                      });
                  });

                  return p.then(() => 'speak');
              }

              return updateSilent()
                  .then(getSilent)
                  .then(processMessage);
          }
      }
});
<|endoftext|>


<commit_before>/*
 * Websensor video stabilization project
 * https://github.com/jessenie-intel/websensor-video
 *
 * Copyright (c) 2017 Jesse Nieminen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/

'use strict';

var constraints = {audio: true,video: {  width: { min: 640, ideal: 640, max: 640 },  height: { min: 480, ideal: 480, max: 480 }}};
var mediaRecorder = null;
var chunks = [];
var videoData = null;
var interval = null;

var accel_sensor = null;
var orientation_sensor = null;
var sensorfreq = 60;

//var roll = null;
//var pitch = null;
//var yaw = null;
var accel = {"x": null, "y": null, "z": null};
var ori = {"roll": null, "pitch": null, "yaw": null, "time": null};
var oriInitial = {"roll": null, "pitch": null, "yaw": null, "time": null};
var initialoriobtained = false;
var orientationData = [];       //array to store all the orientation data
var frameData = {"data": null, "time": null, "ori": null};
var dataArray = [];     //array to store all the combined data
var dataArray2 = [];     //array to store all the combined data

var time = null;
var timestamps = [];
var nFrame = 0; //frame number with which we can combine timestamp and frame data

//canvas
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');

//video element
var videoElement = document.querySelector('video');
videoElement.controls = false;

var ref = null;

class AbsOriSensor {
        constructor() {
        const sensor = new AbsoluteOrientationSensor({ frequency: sensorfreq });
        const mat4 = new Float32Array(16);
        const euler = new Float32Array(3);
        sensor.onreading = () => {
                sensor.populateMatrix(mat4);
                toEulerianAngle(sensor.quaternion, euler);      //From quaternion to Eulerian angles
                this.roll = euler[0];
                this.pitch = euler[1];
                this.yaw = euler[2];
                this.timestamp = sensor.timestamp;
                if (this.onreading) this.onreading();
        };
        sensor.onactivate = () => {
                if (this.onactivate) this.onactivate();
        };
        const start = () => sensor.start();
        Object.assign(this, { start });
        }
}

//WINDOWS 10 HAS DIFFERENT CONVENTION: Yaw z, pitch x, roll y
function toEulerianAngle(quat, out)
{
        const ysqr = quat[1] ** 2;

        // Roll (x-axis rotation).
        const t0 = 2 * (quat[3] * quat[0] + quat[1] * quat[2]);
        const t1 = 1 - 2 * (ysqr + quat[0] ** 2);
        out[0] = Math.atan2(t0, t1);
        // Pitch (y-axis rotation).
        let t2 = 2 * (quat[3] * quat[1] - quat[2] * quat[0]);
        t2 = t2 > 1 ? 1 : t2;
        t2 = t2 < -1 ? -1 : t2;
        out[1] = Math.asin(t2);
        // Yaw (z-axis rotation).
        const t3 = 2 * (quat[3] * quat[2] + quat[0] * quat[1]);
        const t4 = 1 - 2 * (ysqr + quat[2] ** 2);
        out[2] = Math.atan2(t3, t4);
        return out;
}

function errorCallback(error){
	console.log("error: ", error);
}

function startDemo () {
		navigator.getUserMedia(constraints, startRecording, errorCallback);
}

function startRecording(stream) {

                try {
                //Initialize sensors
                /*accel_sensor = new Accelerometer();
                accel_sensor.onreading = () => {
                        accel = {"x": accel_sensor.x, "y": accel_sensor.y, "z": accel_sensor.z};
                };
                accel_sensor.onactivate = () => {
                };
                accel_sensor.start();*/
                orientation_sensor = new AbsOriSensor();
                orientation_sensor.onreading = () => {
                        let roll = orientation_sensor.roll;
                        let pitch = orientation_sensor.pitch;
                        let yaw = orientation_sensor.yaw;
                        time = orientation_sensor.timestamp;
                        if(!initialoriobtained) //obtain initial orientation
                        {
                                oriInitial = {"roll": orientation_sensor.roll, "pitch": orientation_sensor.pitch, "yaw": orientation_sensor.yaw, "time": orientation_sensor.timestamp};
                                initialoriobtained = true;
                        }
                        ori = {"roll": orientation_sensor.roll, "pitch": orientation_sensor.pitch, "yaw": orientation_sensor.yaw, "time": orientation_sensor.timestamp};
                };
                orientation_sensor.onactivate = () => {
                };
                orientation_sensor.start();
                }
                catch(err) {
                        console.log(err.message);
                        console.log("Your browser doesn't seem to support generic sensors. If you are running Chrome, please enable it in about:flags.");
                        //this.innerHTML = "Your browser doesn't seem to support generic sensors. If you are running Chrome, please enable it in about:flags";
                }
	        //var options = {mimeType: 'video/webm;codecs=vp9'};
		//mediaRecorder = new MediaRecorder(stream, options);
		mediaRecorder = new MediaRecorder(stream);

	        mediaRecorder.start(10);

	        var url = window.URL;
	        videoElement.src = url ? url.createObjectURL(stream) : stream;
                //videoElement.play();

	        mediaRecorder.ondataavailable = function(e) {
                        //console.log("Data available", e);
                        //console.log(time);
                        timestamps.push(time);
                        frameData.time = time;
		        chunks.push(e.data);
                        frameData.data = e.data;
                        orientationData.push(ori);
                        frameData.ori = ori;
                        var b = new Object;     //need to push by value
                        Object.assign(b, frameData);
                        dataArray.push(b);
	        };

	        mediaRecorder.onerror = function(e){
		        console.log('Error: ', e);
	        };

	        mediaRecorder.onstart = function(){
                        console.log("Recording started", mediaRecorder.state);
	        };

	        mediaRecorder.onstop = function(){
		        var blob = new Blob(chunks, {type: "video/webm"});
		        chunks = [];

		        var videoURL = window.URL.createObjectURL(blob);

		        videoElement.src = videoURL;
                        videoElement.load();

                        //resize canvas
videoElement.addEventListener('loadedmetadata', function() {
  canvas.width = videoElement.videoWidth;
  canvas.height = videoElement.videoHeight;
});

                        /*//Read blob data so we can stabilize the video
                        var reader = new FileReader();
                         reader.onload = function(event){
                                let text = reader.result;
                                console.log(text);
                          };
                        reader.readAsText(blob, "video/webm");*/
                        //console.log(orientationData);
                        //console.log(timestamps);
                        //console.log(dataArray);
/*
var blobUrl = URL.createObjectURL(blob);
var x = new XMLHttpRequest();
// set x.responseType = 'blob' if you want to get a Blob object:
// x.responseType = 'blob';
x.onload = function() {
    alert(x.responseText);
};
console.log(x.open('get', blobUrl));*/
                        readFrameData(blob, orientationData);    //reads the video into dataArray2
                        videoElement.onended = function() {
                                alert("The video has ended");
                                cancelAnimationFrame(ref);
                        };
                        //ctx.clearRect(0, 0, canvas.width, canvas.height);
                        //stabilize(dataArray2);        //now we can operate on it
                        //interval=window.setInterval(stabilize,20);
	        };

	        mediaRecorder.onpause = function(){
	        }

	        mediaRecorder.onresume = function(){
	        }

	        mediaRecorder.onwarning = function(e){
	        };
}

function stopRecording(){
	mediaRecorder.stop();
        //Now stabilize
	videoElement.controls = true;
}
//Idea: copy video to canvas, operate on the video, and then use the canvas with the stabilized video as source for the video element
function readFrameData(blob, oriArray) {     //Read video data from blob to object form with pixel data we can operate on
        //console.log(orientationData);
        //console.log(oriInitial);
        //console.log(timestamps[nFrame] - timestamps[0], videoElement.currentTime);
        //while(!videoElement.ended)
        //{
                let ori = orientationData[nFrame];
                let oriDiff = {"roll": ori.roll-oriInitial.roll, "pitch": ori.pitch-oriInitial.pitch, "yaw": ori.yaw-oriInitial.yaw};
                let x = 100*oriDiff.yaw;
                let y = 100*oriDiff.roll;
                let widthR = 100;
                let heightR = 100;
                ctx.drawImage(videoElement,0,0, videoElement.videoWidth, videoElement.videoHeight);
                ctx.beginPath();
                ctx.rect(x,y,widthR,heightR);
                ctx.stroke();

                //ctx.drawImage(videoElement, 0, 0);
                let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                let pixeldataArray = [];
                //loop through every pixel
                for(let i=0; i<imageData.data.length; i = i+4)
                {
                        let pixeldata = {"red": imageData.data[i], "green":imageData.data[i+1], "blue": imageData.data[i+2], "alpha":imageData.data[i+3]};
                        //pixeldataArray.push(pixeldata);
                }
                //pixeldataArray.push(imageData);
                //if(nFrame < dataArray.length) {
                        let timestamp = dataArray[nFrame].time;
                        let frameData2 = {"imagedata": imageData, "time": timestamp, "oridiff": oriDiff};
                        //var b = new Object;     //need to push by value
                        //Object.assign(b, frameData2);
                        //dataArray2.push(b);
                        dataArray2.push(frameData2);
                        //console.log(pixeldataArray);
                        //newImageData.data = data;
                    // Draw the pixels onto the visible canvas
                    //ctx.putImageData(newImageData,0,0);
                        ctx.putImageData(imageData, 0, 0)
                        //xD
                        nFrame = nFrame + 1;
                //}
        //}
        /*
        if(dataArray2.length === timestamps.length)     //now we have read the whole blob - should use callback here instead of if condition
        {
                console.log("ended");
                cancelAnimationFrame(ref);
                console.log(dataArray2);
                stabilize(dataArray2);
        } */
        ref = requestAnimationFrame(readFrameData);
}

function stabilize(dataArrayArg) { //Create a stabilized video from the pixel data given as input
        let frame = dataArrayArg[0];      //first frame
        console.log(frame);
        //ctx.drawImage(frame.imagedata,0,0, videoElement.videoWidth, videoElement.videoHeight);
        //ctx.putImageData(frame.imagedata, 0, 0);
}
<commit_msg>Working on reacting to sensor input<commit_after>/*
 * Websensor video stabilization project
 * https://github.com/jessenie-intel/websensor-video
 *
 * Copyright (c) 2017 Jesse Nieminen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/

'use strict';

var constraints = {audio: true,video: {  width: { min: 640, ideal: 640, max: 640 },  height: { min: 480, ideal: 480, max: 480 }}};
var mediaRecorder = null;
var chunks = [];
var videoData = null;
var interval = null;

var accel_sensor = null;
var orientation_sensor = null;
var sensorfreq = 60;

//var roll = null;
//var pitch = null;
//var yaw = null;
var accel = {"x": null, "y": null, "z": null};
var ori = {"roll": null, "pitch": null, "yaw": null, "time": null};
var oriInitial = {"roll": null, "pitch": null, "yaw": null, "time": null};
var initialoriobtained = false;
var orientationData = [];       //array to store all the orientation data
var frameData = {"data": null, "time": null, "ori": null};
var dataArray = [];     //array to store all the combined data
var dataArray2 = [];     //array to store all the combined data

var time = null;
var timestamps = [];
var nFrame = 0; //frame number with which we can combine timestamp and frame data

//canvas
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');

//video element
var videoElement = document.querySelector('video');
videoElement.controls = false;

var ref = null;

class AbsOriSensor {
        constructor() {
        const sensor = new AbsoluteOrientationSensor({ frequency: sensorfreq });
        const mat4 = new Float32Array(16);
        const euler = new Float32Array(3);
        sensor.onreading = () => {
                sensor.populateMatrix(mat4);
                toEulerianAngle(sensor.quaternion, euler);      //From quaternion to Eulerian angles
                this.roll = euler[0];
                this.pitch = euler[1];
                this.yaw = euler[2];
                this.timestamp = sensor.timestamp;
                if (this.onreading) this.onreading();
        };
        sensor.onactivate = () => {
                if (this.onactivate) this.onactivate();
        };
        const start = () => sensor.start();
        Object.assign(this, { start });
        }
}

//WINDOWS 10 HAS DIFFERENT CONVENTION: Yaw z, pitch x, roll y
function toEulerianAngle(quat, out)
{
        const ysqr = quat[1] ** 2;

        // Roll (x-axis rotation).
        const t0 = 2 * (quat[3] * quat[0] + quat[1] * quat[2]);
        const t1 = 1 - 2 * (ysqr + quat[0] ** 2);
        out[0] = Math.atan2(t0, t1);
        // Pitch (y-axis rotation).
        let t2 = 2 * (quat[3] * quat[1] - quat[2] * quat[0]);
        t2 = t2 > 1 ? 1 : t2;
        t2 = t2 < -1 ? -1 : t2;
        out[1] = Math.asin(t2);
        // Yaw (z-axis rotation).
        const t3 = 2 * (quat[3] * quat[2] + quat[0] * quat[1]);
        const t4 = 1 - 2 * (ysqr + quat[2] ** 2);
        out[2] = Math.atan2(t3, t4);
        return out;
}

function errorCallback(error){
	console.log("error: ", error);
}

function startDemo () {
		navigator.getUserMedia(constraints, startRecording, errorCallback);
}

function startRecording(stream) {

                try {
                //Initialize sensors
                /*accel_sensor = new Accelerometer();
                accel_sensor.onreading = () => {
                        accel = {"x": accel_sensor.x, "y": accel_sensor.y, "z": accel_sensor.z};
                };
                accel_sensor.onactivate = () => {
                };
                accel_sensor.start();*/
                orientation_sensor = new AbsOriSensor();
                orientation_sensor.onreading = () => {
                        let roll = orientation_sensor.roll;
                        let pitch = orientation_sensor.pitch;
                        let yaw = orientation_sensor.yaw;
                        time = orientation_sensor.timestamp;
                        if(!initialoriobtained) //obtain initial orientation
                        {
                                oriInitial = {"roll": orientation_sensor.roll, "pitch": orientation_sensor.pitch, "yaw": orientation_sensor.yaw, "time": orientation_sensor.timestamp};
                                initialoriobtained = true;
                        }
                        ori = {"roll": orientation_sensor.roll, "pitch": orientation_sensor.pitch, "yaw": orientation_sensor.yaw, "time": orientation_sensor.timestamp};
                };
                orientation_sensor.onactivate = () => {
                };
                orientation_sensor.start();
                }
                catch(err) {
                        console.log(err.message);
                        console.log("Your browser doesn't seem to support generic sensors. If you are running Chrome, please enable it in about:flags.");
                        //this.innerHTML = "Your browser doesn't seem to support generic sensors. If you are running Chrome, please enable it in about:flags";
                }
	        //var options = {mimeType: 'video/webm;codecs=vp9'};
		//mediaRecorder = new MediaRecorder(stream, options);
		mediaRecorder = new MediaRecorder(stream);

	        mediaRecorder.start(10);

	        var url = window.URL;
	        videoElement.src = url ? url.createObjectURL(stream) : stream;
                //videoElement.play();

	        mediaRecorder.ondataavailable = function(e) {
                        //console.log("Data available", e);
                        //console.log(time);
                        timestamps.push(time);
                        frameData.time = time;
		        chunks.push(e.data);
                        frameData.data = e.data;
                        orientationData.push(ori);
                        frameData.ori = ori;
                        var b = new Object;     //need to push by value
                        Object.assign(b, frameData);
                        dataArray.push(b);
	        };

	        mediaRecorder.onerror = function(e){
		        console.log('Error: ', e);
	        };

	        mediaRecorder.onstart = function(){
                        console.log("Recording started", mediaRecorder.state);
	        };

	        mediaRecorder.onstop = function(){
		        var blob = new Blob(chunks, {type: "video/webm"});
		        chunks = [];

		        var videoURL = window.URL.createObjectURL(blob);

		        videoElement.src = videoURL;
                        videoElement.load();

                        //resize canvas
videoElement.addEventListener('loadedmetadata', function() {
  canvas.width = videoElement.videoWidth;
  canvas.height = videoElement.videoHeight;
});

                        /*//Read blob data so we can stabilize the video
                        var reader = new FileReader();
                         reader.onload = function(event){
                                let text = reader.result;
                                console.log(text);
                          };
                        reader.readAsText(blob, "video/webm");*/
                        //console.log(orientationData);
                        //console.log(timestamps);
                        //console.log(dataArray);
/*
var blobUrl = URL.createObjectURL(blob);
var x = new XMLHttpRequest();
// set x.responseType = 'blob' if you want to get a Blob object:
// x.responseType = 'blob';
x.onload = function() {
    alert(x.responseText);
};
console.log(x.open('get', blobUrl));*/
                        readFrameData(blob, orientationData);    //reads the video into dataArray2
                        videoElement.onended = function() {
                                alert("The video has ended");
                                cancelAnimationFrame(ref);
                        };
                        //ctx.clearRect(0, 0, canvas.width, canvas.height);
                        //stabilize(dataArray2);        //now we can operate on it
                        //interval=window.setInterval(stabilize,20);
	        };

	        mediaRecorder.onpause = function(){
	        }

	        mediaRecorder.onresume = function(){
	        }

	        mediaRecorder.onwarning = function(e){
	        };
}

function stopRecording(){
	mediaRecorder.stop();
        //Now stabilize
	videoElement.controls = true;
}
//Idea: copy video to canvas, operate on the video, and then use the canvas with the stabilized video as source for the video element
function readFrameData(blob, oriArray) {     //Read video data from blob to object form with pixel data we can operate on
        //console.log(orientationData);
        //console.log(oriInitial);
        //console.log(timestamps[nFrame] - timestamps[0], videoElement.currentTime);
        //while(!videoElement.ended)
        //{
                let ori = orientationData[nFrame];
                let oriDiff = {"roll": ori.roll-oriInitial.roll, "pitch": ori.pitch-oriInitial.pitch, "yaw": ori.yaw-oriInitial.yaw};
                let x = 100*oriDiff.yaw;
                let y = 100*oriDiff.roll;
                let widthR = 100;
                let heightR = 100;
                ctx.drawImage(videoElement,0,0, videoElement.videoWidth, videoElement.videoHeight);
                ctx.beginPath();
                ctx.rect(x,y,widthR,heightR);
                ctx.stroke();

                ctx.drawImage(videoElement, 0, 0);
                let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                let pixeldataArray = [];
                //loop through every pixel
                for(let i=0; i<imageData.data.length; i = i+4)
                {
                        let pixeldata = {"red": imageData.data[i], "green":imageData.data[i+1], "blue": imageData.data[i+2], "alpha":imageData.data[i+3]};
                        //pixeldataArray.push(pixeldata);
                }
                //pixeldataArray.push(imageData);
                //if(nFrame < dataArray.length) {
                        let timestamp = dataArray[nFrame].time;
                        let frameData2 = {"imagedata": imageData, "time": timestamp, "oridiff": oriDiff};
                        //var b = new Object;     //need to push by value
                        //Object.assign(b, frameData2);
                        //dataArray2.push(b);
                        dataArray2.push(frameData2);
                        //console.log(pixeldataArray);
                        //newImageData.data = data;
                    // Draw the pixels onto the visible canvas
                    //ctx.putImageData(newImageData,0,0);
                        //ctx.putImageData(imageData, 0, 0)
                        //xD
                        nFrame = nFrame + 1;
                //}
        //}
        /*
        if(dataArray2.length === timestamps.length)     //now we have read the whole blob - should use callback here instead of if condition
        {
                console.log("ended");
                cancelAnimationFrame(ref);
                console.log(dataArray2);
                stabilize(dataArray2);
        } */
        ref = requestAnimationFrame(readFrameData);
}

function stabilize(dataArrayArg) { //Create a stabilized video from the pixel data given as input
        let frame = dataArrayArg[0];      //first frame
        console.log(frame);
        //ctx.drawImage(frame.imagedata,0,0, videoElement.videoWidth, videoElement.videoHeight);
        //ctx.putImageData(frame.imagedata, 0, 0);
}
<|endoftext|>


<commit_before>function renderMenu() {
	$('#content').empty();
	$('#list').empty();

	$('#list').append('
Delicious.com Profile
');
	$('#list').append('
Browse by tag
');
	$('#list').append('
Browse by stack
');
	$('#list').append('
');
	$('#list').append('
Add to my bookmarks
');

	openLinksNewTab('.openable');
}

function renderTags() {
	clear();
	$('#list').append('
Menu
');
	$('#list').append('
');

	api('https://api.del.icio.us/v1/tags/get', function(xml) {
		$(xml).find('tag').each(function() {
			var tag = $(this).attr('tag');
			var link = $(''+tag+'').click(function() {
				var tags = [$(this).text()];
				renderBookmarks(tags);
				return false;
			});
			var item = $('

');
			item.append(link);
			$('#list').append(item);
		});
	});
}

function renderBookmarks(tags, stack) {
	clear();
	if(stack === undefined || stack == false) {
		$('#list').append('
Back
');
	} else {
		$('#list').append('
Back
');
	}
	$('#list').append('
');

	say('Loading...');

	for(var i in tags) {
		api('https://api.del.icio.us/v1/posts/all?&tag='+tags[i], function(xml) {
			$(xml).find('post').each(function() {
				var link = $(''+$(this).attr('description')+'').click(function() {
					chrome.tabs.create({'url' : $(this).attr('href')});
					return false;
				});
				var item = $('

');
				item.append(link);
				$('#list').append(item);
			});
		});
		delay(5000);
	}

	$('#content').empty().append('<input type="text" id="search" />');
	$('#search').keypress(function(event) {
		if ( event.which == 13 ) {
    		event.preventDefault();
    		quickSearch($('#search').val());
		}
	});
}

// HELPERS

function openLinksNewTab(selector) {
	$(selector).click(function(){
		chrome.tabs.create({'url' : $(this).attr('href')});
		return false;
	});
}

function clear() {
	$('#content').empty();
	$('#list').empty();
}

function api(url, success, error) {
	var user = localStorage["username"];
	var pass = localStorage["password"];

	$.ajax({
		url : url,
		contentType: 'application/xml; charset=utf-8',
		dataType : 'xml',
		beforeSend : function(xhr) {
			xhr.setRequestHeader('Authorization', 'Basic ' + window.btoa(unescape(encodeURIComponent(user+':'+pass))));
		},
		success : success,
		error : function(xml, msg, error) {
			$('#content').empty().append('
An unexpected failure! '+msg+' - '+error+'.
');
			$('#container').css({'min-width' : '300px'});
		}
	});
}

function say(str) {
	$('#content').empty();
	$('#content').empty().append('
'+str+'.
');
}

function delay(ms) {
	var wait = true;
	setTimeout(function(wait){
		wait = false;
	}, ms);
	while(wait) { break; }
}<commit_msg>Add quicksearch through bookmarks.<commit_after>function renderMenu() {
	$('#content').empty();
	$('#list').empty();

	$('#list').append('
Delicious.com Profile
');
	$('#list').append('
Browse by tag
');
	$('#list').append('
Browse by stack
');
	$('#list').append('
');
	$('#list').append('
Add to my bookmarks
');

	openLinksNewTab('.openable');
}

function renderTags() {
	clear();
	$('#list').append('
Menu
');
	$('#list').append('
');

	api('https://api.del.icio.us/v1/tags/get', function(xml) {
		$(xml).find('tag').each(function() {
			var tag = $(this).attr('tag');
			var link = $(''+tag+'').click(function() {
				var tags = [$(this).text()];
				renderBookmarks(tags);
				return false;
			});
			var item = $('

');
			item.append(link);
			$('#list').append(item);
		});
	});
}

function renderBookmarks(tags, stack) {
	clear();
	if(stack === undefined || stack == false) {
		$('#list').append('
Back
');
	} else {
		$('#list').append('
Back
');
	}
	$('#list').append('
');

	say('Loading...');

	for(var i in tags) {
		api('https://api.del.icio.us/v1/posts/all?&tag='+tags[i], function(xml) {
			$(xml).find('post').each(function() {
				var link = $(''+$(this).attr('description')+'').click(function() {
					chrome.tabs.create({'url' : $(this).attr('href')});
					return false;
				});
				var item = $('

');
				item.append(link);
				$('#list').append(item);
			});
		});
		delay(5000);
	}

	$('#content').empty().append('<input type="text" id="search" />');
	$('#search').keypress(function(event) {
		if ( event.which == 13 ) {
    		event.preventDefault();
    		quickSearch($('#search').val());
		}
	});
}

function quickSearch(query) {
	$(".bookmark").each(function() {
		var title = $(this).find('a').text();
		var re = new RegExp(query, 'i');
		if(title.search(re) != -1) {
			$(this).show();
		} else {
			$(this).hide();
		}
	});
}

// HELPERS

function openLinksNewTab(selector) {
	$(selector).click(function(){
		chrome.tabs.create({'url' : $(this).attr('href')});
		return false;
	});
}

function clear() {
	$('#content').empty();
	$('#list').empty();
}

function api(url, success, error) {
	var user = localStorage["username"];
	var pass = localStorage["password"];

	$.ajax({
		url : url,
		contentType: 'application/xml; charset=utf-8',
		dataType : 'xml',
		beforeSend : function(xhr) {
			xhr.setRequestHeader('Authorization', 'Basic ' + window.btoa(unescape(encodeURIComponent(user+':'+pass))));
		},
		success : success,
		error : function(xml, msg, error) {
			$('#content').empty().append('
An unexpected failure! '+msg+' - '+error+'.
');
			$('#container').css({'min-width' : '300px'});
		}
	});
}

function say(str) {
	$('#content').empty();
	$('#content').empty().append('
'+str+'.
');
}

function delay(ms) {
	var wait = true;
	setTimeout(function(wait){
		wait = false;
	}, ms);
	while(wait) { break; }
}<|endoftext|>


<commit_before>//var imageLocation = './firefox.png';
var imageLocation = './the-scream.png';

var timeoutId;
var timeoutDelay = 0;
var timeoutLikenessDelay = 100;

var modelCanvas, genCanvas;
var modelCtx, genCtx;
var modelImage;
var modelImageData;

var imageWidth;
var imageHeight;

var pausePlayButton;
var resetButton;
var play;

var likeness;

var lessDiff;
var maxDiffPossible;

var maxLineWidth;
var maxRadius;

var drawManager;

function initialize()
{
  modelCanvas = document.getElementById('model-canvas');
  genCanvas = document.getElementById('generate-canvas');
  pausePlayButton = document.getElementById('pause-play-button');
  resetButton = document.getElementById('reset-button');
  likeness = document.getElementById('likeness');

  modelCtx = modelCanvas.getContext('2d');
  genCtx = genCanvas.getContext('2d');

  drawManager = new DrawManager();

  modelImage = new Image();
  modelImage.onload = function() {
    imageWidth = modelImage.width;
    imageHeight = modelImage.height;

    maxDiffPossible = imageWidth * imageHeight * 3 * 256;
    maxRadius = (imageWidth + imageHeight) / 2 / 2;
    maxLineWidth = (imageWidth + imageHeight) / 2 / 20;

    modelCanvas.width = modelImage.width;
    modelCanvas.height = modelImage.height;
    modelCtx.drawImage(modelImage, 0, 0)
    modelImageData = modelCtx.getImageData(0, 0, imageWidth, imageHeight);

    genCanvas.width = modelImage.width;
    genCanvas.height = modelImage.height;
    genCtx.beginPath();
    genCtx.rect(0, 0, imageWidth, imageHeight);
    genCtx.fillStyle = 'white';
    genCtx.fill();

    play = true;
    setMainTimeout();

    setTimeout(displayLikeness, timeoutLikenessDelay);

    pausePlayButton.value = 'pause';
    resetButton.value = 'reset';
    pausePlayButton.addEventListener('click', playPause);
    resetButton.addEventListener('click', reset);

  }
  modelImage.src = imageLocation;
}

function DrawManager()
{
  var that = this;

  this.methods = [
    { name: 'circle', func: randomCircle },
    { name: 'triangle', func: randomTriangle },
    { name: 'line', func: randomLine },

  ];

  for (var i = 0; i < this.methods.length; i++)
  {
    var name = this.methods[i].name;
    var elt = document.getElementById('enable-' + name)
    this.methods[i].element = elt;
    elt.addEventListener('CheckboxStateChange', function() { that.updateFuncTable() });
  }

  this.funcTable = [];

  this.updateFuncTable();
}

DrawManager.prototype.updateFuncTable = function()
{
  this.funcTable = []
  for (var i = 0; i < this.methods.length; i++)
  {
    var method = this.methods[i];
    if (method.element.checked)
    {
      this.funcTable.push(method.func);
    }
  }
}

DrawManager.prototype.draw = function()
{
  var length = this.funcTable.length;
  if (length > 0)
  {
    var i = Math.floor(Math.random() * length)
    var f = this.funcTable[i];
    f();
  }
};

function playPause()
{
  if (play == false)
  {
    pausePlayButton.value = 'pause';
    play = true;
    setMainTimeout();
  }
  else
  {
    play = false;
    pausePlayButton.value = 'play';
    clearTimeout(timeoutId);
    timeoutId = undefined;
  }
}

function reset()
{
  lessDiff = undefined;
  genCtx.beginPath();
  genCtx.rect(0, 0, imageWidth, imageHeight);
  genCtx.fillStyle = 'white';
  genCtx.fill();
}

function getPixel(data, x, y)
{
  var start_index = (y * imageWidth + x) * 4;
  var r = data[start_index];
  var g = data[start_index + 1];
  var b = data[start_index + 2];
  return [r, g, b]
}

function pixelDiff(p1, p2)
{
  var delta_r = Math.abs(p1[0] - p2[0])
  var delta_g = Math.abs(p1[1] - p2[1])
  var delta_b = Math.abs(p1[2] - p2[2])
  var delta_sum = delta_r + delta_g + delta_b;
  return delta_sum;
}

function imageDiff(imageData1, imageData2)
{
  var totalDiff = 0;
  for (var y = 0; y < imageData1.height; y++)
  {
    for (var x = 0; x < imageData1.width; x++)
    {
      var p1 = getPixel(imageData1.data,x, y);
      var p2 = getPixel(imageData2.data,x, y);
      //console.log(p1);
      //console.log(p2);
      //console.log(pixelDiff(p1, p2))
      totalDiff += pixelDiff(p1, p2);
    }
  }
  return totalDiff;
}

function getRandomCoord(max){
    return Math.floor(Math.random() * max);
}

// copy-paste from stack-overflow
function getRandomColor() {
    var letters = '0123456789ABCDEF'.split('');
    var color = '#';
    for (var i = 0; i < 6; i++ )
    {
        color += letters[Math.floor(Math.random() * 16)];
    }
    return color;
}

function getRandomLineWidth()
{
  return Math.floor(Math.random() * maxLineWidth);
}

function stroke()
{
  genCtx.lineWidth = getRandomLineWidth();
  genCtx.strokeStyle = getRandomColor();
  genCtx.stroke();
}

function randomLine()
{
  x1 = getRandomCoord(imageWidth);
  x2 = getRandomCoord(imageWidth);

  y1 = getRandomCoord(imageHeight);
  y2 = getRandomCoord(imageHeight);

  genCtx.beginPath();
  genCtx.moveTo(x1, y1);
  genCtx.lineTo(x2, y2);

  stroke();
}

function randomCircle()
{
  x = getRandomCoord(imageWidth);
  y = getRandomCoord(imageHeight);

  radius = Math.floor(Math.random() * maxRadius);

  genCtx.beginPath();
  genCtx.arc(x, y, radius, 0, Math.PI * 2);

  stroke();
}

function randomTriangle()
{
  x1 = getRandomCoord(imageWidth);
  x2 = getRandomCoord(imageWidth);
  x3 = getRandomCoord(imageWidth);

  y1 = getRandomCoord(imageHeight);
  y2 = getRandomCoord(imageHeight);
  y3 = getRandomCoord(imageHeight);

  genCtx.beginPath();
  genCtx.moveTo(x1, y1);
  genCtx.lineTo(x2, y2);
  genCtx.lineTo(x3, y3);
  genCtx.lineTo(x1, y1);

  stroke();
}

function randomDraw()
{
  if (Math.random() < 0.5)
  {
    randomCircle();
  }
  else
  {
    randomTriangle();
  }
}

function generateOnce()
{
  if (play == false)
    return ;

  savedImage = genCanvas.toDataURL();

  drawManager.draw();
  genImageData = genCtx.getImageData(0, 0, imageWidth, imageHeight);

  var diff = imageDiff(modelImageData, genImageData);
  if (lessDiff == undefined || diff <= lessDiff)
  {
    lessDiff = diff;
    setMainTimeout();
  }
  else
  {
    var image = new Image();
    image.onload = function() {
      genCtx.drawImage(image, 0, 0);
      setMainTimeout();
    }
    image.src = savedImage;
  }
}

function displayLikeness()
{
  var value = (lessDiff == undefined ?
    0 : Math.round((1 - lessDiff / maxDiffPossible) * 100));
  var text = "likeness : " + value + "%";
  likeness.firstChild.nodeValue = text;
  setTimeout(displayLikeness, timeoutLikenessDelay);
}

function setMainTimeout()
{
  timeoutId = setTimeout(generateOnce, timeoutDelay);
}

window.onload = initialize;
<commit_msg>small update<commit_after>//var imageLocation = './firefox.png';
var imageLocation = './the-scream.png';

var timeoutId;
var timeoutDelay = 0;
var timeoutLikenessDelay = 100;

var modelCanvas, genCanvas;
var modelCtx, genCtx;
var modelImage;
var modelImageData;

var imageWidth;
var imageHeight;

var pausePlayButton;
var resetButton;
var play;

var likeness;

var lessDiff;
var maxDiffPossible;

var maxLineWidth;
var maxRadius;

var drawManager;

function initialize()
{
  modelCanvas = document.getElementById('model-canvas');
  genCanvas = document.getElementById('generate-canvas');
  pausePlayButton = document.getElementById('pause-play-button');
  resetButton = document.getElementById('reset-button');
  likeness = document.getElementById('likeness');

  modelCtx = modelCanvas.getContext('2d');
  genCtx = genCanvas.getContext('2d');

  drawManager = new DrawManager();

  modelImage = new Image();
  modelImage.onload = function() {
    imageWidth = modelImage.width;
    imageHeight = modelImage.height;

    maxDiffPossible = imageWidth * imageHeight * 3 * 256;
    maxRadius = (imageWidth + imageHeight) / 2 / 2;
    maxLineWidth = (imageWidth + imageHeight) / 2 / 20;

    modelCanvas.width = modelImage.width;
    modelCanvas.height = modelImage.height;
    modelCtx.drawImage(modelImage, 0, 0)
    modelImageData = modelCtx.getImageData(0, 0, imageWidth, imageHeight);

    genCanvas.width = modelImage.width;
    genCanvas.height = modelImage.height;
    genCtx.beginPath();
    genCtx.rect(0, 0, imageWidth, imageHeight);
    genCtx.fillStyle = 'white';
    genCtx.fill();

    play = true;
    setMainTimeout();

    setTimeout(displayLikeness, timeoutLikenessDelay);

    pausePlayButton.value = 'pause';
    resetButton.value = 'reset';
    pausePlayButton.addEventListener('click', playPause);
    resetButton.addEventListener('click', reset);

  }
  modelImage.src = imageLocation;
}

function DrawManager()
{
  var that = this;

  this.methods = [
    { name: 'circle', func: randomCircle },
    { name: 'triangle', func: randomTriangle },
    { name: 'line', func: randomLine },

  ];

  for (var i = 0; i < this.methods.length; i++)
  {
    var name = this.methods[i].name;
    var elt = document.getElementById('enable-' + name)
    this.methods[i].element = elt;
    elt.checked = true;
    elt.addEventListener('CheckboxStateChange', function() { that.updateFuncTable() });
  }

  this.funcTable = [];

  this.updateFuncTable();
}

DrawManager.prototype.updateFuncTable = function()
{
  this.funcTable = []
  for (var i = 0; i < this.methods.length; i++)
  {
    var method = this.methods[i];
    if (method.element.checked)
    {
      this.funcTable.push(method.func);
    }
  }
}

DrawManager.prototype.draw = function()
{
  var length = this.funcTable.length;
  if (length > 0)
  {
    var i = Math.floor(Math.random() * length)
    var f = this.funcTable[i];
    f();
  }
};

function playPause()
{
  if (play == false)
  {
    pausePlayButton.value = 'pause';
    play = true;
    setMainTimeout();
  }
  else
  {
    play = false;
    pausePlayButton.value = 'play';
    clearTimeout(timeoutId);
    timeoutId = undefined;
  }
}

function reset()
{
  lessDiff = undefined;
  genCtx.beginPath();
  genCtx.rect(0, 0, imageWidth, imageHeight);
  genCtx.fillStyle = 'white';
  genCtx.fill();
}

function getPixel(data, x, y)
{
  var start_index = (y * imageWidth + x) * 4;
  var r = data[start_index];
  var g = data[start_index + 1];
  var b = data[start_index + 2];
  return [r, g, b]
}

function pixelDiff(p1, p2)
{
  var delta_r = Math.abs(p1[0] - p2[0])
  var delta_g = Math.abs(p1[1] - p2[1])
  var delta_b = Math.abs(p1[2] - p2[2])
  var delta_sum = delta_r + delta_g + delta_b;
  return delta_sum;
}

function imageDiff(imageData1, imageData2)
{
  var totalDiff = 0;
  for (var y = 0; y < imageData1.height; y++)
  {
    for (var x = 0; x < imageData1.width; x++)
    {
      var p1 = getPixel(imageData1.data,x, y);
      var p2 = getPixel(imageData2.data,x, y);
      //console.log(p1);
      //console.log(p2);
      //console.log(pixelDiff(p1, p2))
      totalDiff += pixelDiff(p1, p2);
    }
  }
  return totalDiff;
}

function getRandomCoord(max){
    return Math.floor(Math.random() * max);
}

// copy-paste from stack-overflow
function getRandomColor() {
    var letters = '0123456789ABCDEF'.split('');
    var color = '#';
    for (var i = 0; i < 6; i++ )
    {
        color += letters[Math.floor(Math.random() * 16)];
    }
    return color;
}

function getRandomLineWidth()
{
  return Math.floor(Math.random() * maxLineWidth);
}

function stroke()
{
  genCtx.lineWidth = getRandomLineWidth();
  genCtx.strokeStyle = getRandomColor();
  genCtx.stroke();
}

function randomLine()
{
  x1 = getRandomCoord(imageWidth);
  x2 = getRandomCoord(imageWidth);

  y1 = getRandomCoord(imageHeight);
  y2 = getRandomCoord(imageHeight);

  genCtx.beginPath();
  genCtx.moveTo(x1, y1);
  genCtx.lineTo(x2, y2);

  stroke();
}

function randomCircle()
{
  x = getRandomCoord(imageWidth);
  y = getRandomCoord(imageHeight);

  radius = Math.floor(Math.random() * maxRadius);

  genCtx.beginPath();
  genCtx.arc(x, y, radius, 0, Math.PI * 2);

  stroke();
}

function randomTriangle()
{
  x1 = getRandomCoord(imageWidth);
  x2 = getRandomCoord(imageWidth);
  x3 = getRandomCoord(imageWidth);

  y1 = getRandomCoord(imageHeight);
  y2 = getRandomCoord(imageHeight);
  y3 = getRandomCoord(imageHeight);

  genCtx.beginPath();
  genCtx.moveTo(x1, y1);
  genCtx.lineTo(x2, y2);
  genCtx.lineTo(x3, y3);
  genCtx.lineTo(x1, y1);

  stroke();
}

function randomDraw()
{
  if (Math.random() < 0.5)
  {
    randomCircle();
  }
  else
  {
    randomTriangle();
  }
}

function generateOnce()
{
  if (play == false)
    return ;

  savedImage = genCanvas.toDataURL();

  drawManager.draw();
  genImageData = genCtx.getImageData(0, 0, imageWidth, imageHeight);

  var diff = imageDiff(modelImageData, genImageData);
  if (lessDiff == undefined || diff <= lessDiff)
  {
    lessDiff = diff;
    setMainTimeout();
  }
  else
  {
    var image = new Image();
    image.onload = function() {
      genCtx.drawImage(image, 0, 0);
      setMainTimeout();
    }
    image.src = savedImage;
  }
}

function displayLikeness()
{
  var value = (lessDiff == undefined ?
    0 : Math.round((1 - lessDiff / maxDiffPossible) * 100));
  var text = "likeness : " + value + "%";
  likeness.firstChild.nodeValue = text;
  setTimeout(displayLikeness, timeoutLikenessDelay);
}

function setMainTimeout()
{
  timeoutId = setTimeout(generateOnce, timeoutDelay);
}

window.onload = initialize;
<|endoftext|>


<commit_before>(() => {
    'use strict';

    var player = "0";
    var moves = [];
    var winningCombos = [
        ["1", "2", "3"],
        ["4", "5", "6"],
        ["7", "8", "9"],
        ["1", "5", "9"],
        ["3", "5", "7"],
        ["1", "4", "7"],
        ["2", "5", "8"],
        ["3", "6", "9"]];

    window.play = (sq) => {
        if (sq.innerHTML !== "0" && sq.innerHTML !== "X") {
            drawMark(sq);
            saveMove(sq);
            colourWinningCombo();
            changePlayer();
        }
    };

    var drawMark = td => td.innerHTML = player;

    var saveMove = td => moves[td.id] = player;

    var colourWinningCombo = () => (winningCombos
        .filter(isWinningCombo)
        .map(colourCombo));

    var isWinningCombo = combo => (combo
        .filter(sq => moves[sq] === player)
        .length === 3);

    var colourCombo = combo => (combo
        .map(sq => document.getElementById(sq + "").style.backgroundColor = "green"));

    var changePlayer = () => (player === "0") ? player = "X" : player = "0";

})();<commit_msg>Rename function expression<commit_after>(() => {
    'use strict';

    var player = "0";
    var moves = [];
    var winningCombos = [
        ["1", "2", "3"],
        ["4", "5", "6"],
        ["7", "8", "9"],
        ["1", "5", "9"],
        ["3", "5", "7"],
        ["1", "4", "7"],
        ["2", "5", "8"],
        ["3", "6", "9"]];

    window.play = (sq) => {
        if (sq.innerHTML !== "0" && sq.innerHTML !== "X") {
            drawMark(sq);
            saveMove(sq);
            colourWinningCombos();
            changePlayer();
        }
    };

    var drawMark = td => td.innerHTML = player;

    var saveMove = td => moves[td.id] = player;

    var colourWinningCombos = () => (winningCombos
        .filter(ownedByPlayer)
        .map(colourCombo));

    var ownedByPlayer = combo => (combo
        .filter(sq => moves[sq] === player)
        .length === 3);

    var colourCombo = combo => (combo
        .map(sq => document.getElementById(sq + "").style.backgroundColor = "green"));

    var changePlayer = () => (player === "0") ? player = "X" : player = "0";

})();<|endoftext|>


<commit_before>var effects = {}

effects.panner = new Tone.AutoPanner({
  "frequency" : .5,
  "amount" : 0
}).toMaster();

effects.feedbackDelay = new Tone.PingPongDelay({
  "delayTime" : "8n",
  "feedback" : 0.6,
  "wet" : 0.5
}).toMaster();

var effectsArray = d3.entries(effects)

//play a snare sound through it
var snare = function(){ return new Tone.Player("snare.mp3") }

var players = [
    'snare',
    // 'agogoHigh',
    // 'agogoLow',
    // 'B1',
    'hh',
    // 'hho',
    'kick'
  ]
  .map(function(str){
    var rv = {str: str}
    rv.effects = {}
    effectsArray.forEach(function(e){
      rv.effects[e.key] = (new Tone.Player('sounds/' + str + '.mp3')).connect(e.value)
    })
    return rv
  })

var s = 540
var margin = 20

var iR = s/5
var oR = s/3

var svg = d3.select('body')
  .append('svg')
    .attr({height: s + margin*2, width: s + margin*2})
  .append('g')
    .translate([margin + s/2, margin + s/2])

var sounds = d3.range(0, 2*Math.PI, .3).map(function(d){
  var rv = {θ: d}
  rv.player = players[~~(Math.random()*players.length)]
  return rv
})

var color = d3.scale.category10()
var circles = svg.dataAppend(sounds, 'circle')
    .attr('r', 10)
    .attr('fill', ƒ('player', 'str', color))

effectsArray.forEach(function(d, i){
  d.θ = 2*Math.PI*i/effectsArray.length
})

var pairs = []
sounds.forEach(function(s){
  effectsArray.forEach(function(e){
    pairs.push({s: s, e: e, lastPlay: -10000, active: false})
  })
})

var lines = svg.dataAppend(pairs, 'path')
    .style({'stroke': 'black'})

var shapes = svg.dataAppend(effectsArray, 'path')
    .attr('d', ['M', [-5,-5], 'L', [5, -5], 'L', [5,5], 'L', [-5,5]].join(''))

d3.timer(function(t){

  sounds.forEach(function(d){
    d.curθ = (d.θ + t/2000)
    d.pos = [Math.cos(d.curθ)*iR, Math.sin(d.curθ)*iR]
  })

  effectsArray.forEach(function(d){
    d.pos = [Math.cos(d.θ + -t*0.0001)*oR, Math.sin(d.θ + -t*0.0001)*oR]
  })

  circles.translate(ƒ('pos'))
  shapes.translate(ƒ('pos'))

  pairs
    .filter(function(d){
      return !d.active && dist(d) })
    .forEach(function(d){
      d.active = true

      try{
        d.s.player.effects[d.e.key].start()
      } catch(e){

      }
    })

  pairs
    .filter(function(d){
      return d.active && !dist(d) })
    .forEach(function(d){
      d.active = false
    })

  lines
      // .style('stroke', function(d){ return d.active ? 'green' : 'blue' })
      .style('opacity', function(d){ return d.active ? 1 : .05 })
      .attr('d', function(d){
        return ['M', d.e.pos, 'L', d.s.pos].join('') })

})

function dist(d){
  // return Math.abs((d.s.curθ - d.e.θ) % Math.PI*2) < .3

  var dx = d.s.pos[0] - d.e.pos[0]
  var dy = d.s.pos[1] - d.e.pos[1]

  return dx*dx + dy*dy < 10000
}

<commit_msg>adds stop<commit_after>var effects = {}

// effects.panner = new Tone.AutoPanner({
//   "frequency" : .5,
//   "amount" : 0
// }).toMaster();

effects.feedbackDelay = new Tone.PingPongDelay({
  "delayTime" : "8n",
  "feedback" : 0.6,
  "wet" : 0.5
}).toMaster();

var effectsArray = d3.entries(effects)

//play a snare sound through it
var snare = function(){ return new Tone.Player("snare.mp3") }

var players = [
    'snare',
    // 'agogoHigh',
    // 'agogoLow',
    'B1',
    'D2',
    'G2',
    // 'hh',
    // 'hho',
    'kick'
  ]
  .map(function(str){
    var rv = {str: str}
    rv.effects = {}
    effectsArray.forEach(function(e){
      rv.effects[e.key] = (new Tone.Player('sounds/' + str + '.mp3')).connect(e.value)
    })
    return rv
  })

var s = 540
var margin = 20

var iR = s/5
var oR = s/3

var svg = d3.select('body')
  .append('svg')
    .attr({height: s + margin*2, width: s + margin*2})
  .append('g')
    .translate([margin + s/2, margin + s/2])

var sounds = d3.range(0, 2*Math.PI, .3).map(function(d){
  var rv = {θ: d}
  rv.player = players[~~(Math.random()*players.length)]
  return rv
})

var color = d3.scale.category10()
var circles = svg.dataAppend(sounds, 'circle')
    .attr('r', 10)
    .attr('fill', ƒ('player', 'str', color))

effectsArray.forEach(function(d, i){
  d.θ = 2*Math.PI*i/effectsArray.length
})

var pairs = []
sounds.forEach(function(s){
  effectsArray.forEach(function(e){
    pairs.push({s: s, e: e, lastPlay: -10000, active: false})
  })
})

var lines = svg.dataAppend(pairs, 'path')
    .style({'stroke': 'black'})

var shapes = svg.dataAppend(effectsArray, 'path')
    .attr('d', ['M', [-5,-5], 'L', [3, -5], 'L', [5,5], 'L', [-5,5], 'L', [-10, 5]].join(''))

d3.timer(function(t){

  sounds.forEach(function(d){
    d.curθ = (d.θ + t/2000)
    d.pos = [Math.cos(d.curθ)*iR, Math.sin(d.curθ)*iR]
  })

  effectsArray.forEach(function(d){
    d.pos = [Math.cos(d.θ + -t*0.0001)*oR, Math.sin(d.θ + -t*0.0001)*oR]
  })

  circles.translate(ƒ('pos'))
  shapes.translate(ƒ('pos'))

  pairs
    .filter(function(d){
      return !d.active && dist(d) })
    .forEach(function(d){
      d.active = true

      try{
        d.s.player.effects[d.e.key].stop()
        d.s.player.effects[d.e.key].start()
      } catch(e){
        console.log('e')

      }
    })

  pairs
    .filter(function(d){
      return d.active && !dist(d) })
    .forEach(function(d){
      d.active = false
    })

  lines
      // .style('stroke', function(d){ return d.active ? 'green' : 'blue' })
      .style('opacity', function(d){ return d.active ? 1 : .05 })
      .attr('d', function(d){
        return ['M', d.e.pos, 'L', d.s.pos].join('') })

})

function dist(d){
  // return Math.abs((d.s.curθ - d.e.θ) % Math.PI*2) < .3

  var dx = d.s.pos[0] - d.e.pos[0]
  var dy = d.s.pos[1] - d.e.pos[1]

  return dx*dx + dy*dy < 10000
}

<|endoftext|>


<commit_before>$(document).ready(function ($) {
    $.fn.mood = function () {
        var i, moods = ['happy', 'neutral', 'sad'];
        for (i = 0; i < moods.length; i++) {
            if (this.hasClass(moods[i])) { return moods[i]; }
        }
        return null;
    }

    $.fn.updateIndicator = function () {
        var mood = this.mood();
        if (mood) { $('link[rel="icon"]').attr('href', chrome.extension.getURL(mood + '.png')); }
        return this;
    }

		// create an observer
		var
		updateIndicatorOnClassMutation = function (mutation) {
			if (mutation.type === 'attributes' && mutation.attributeName === 'class' ) {
				$(mutation.target).updateIndicator();
			}
		},
		observer = new MutationObserver(function(mutations) {
			mutations.forEach(this);
		}.bind(updateIndicatorOnClassMutation));
		// observe happiness
		observer.observe(document.getElementById('happiness'), {attributes: true});
		$('#happiness').updateIndicator();

});
<commit_msg>replace jquery by vanilla js for updating icon mood<commit_after>$(document).ready(function ($) {
    $.fn.mood = function () {
        var i, moods = ['happy', 'neutral', 'sad'];
        for (i = 0; i < moods.length; i++) {
            if (this.hasClass(moods[i])) { return moods[i]; }
        }
        return null;
    }

    $.fn.updateIndicator = function () {
        var mood = this.mood();
        if (mood) { document.querySelector('link[rel="icon"]').setAttribute('href', chrome.extension.getURL(mood + '.png')); }
        return this;
    }

		// create an observer
		var
		updateIndicatorOnClassMutation = function (mutation) {
			if (mutation.type === 'attributes' && mutation.attributeName === 'class' ) {
				$(mutation.target).updateIndicator();
			}
		},
		observer = new MutationObserver(function(mutations) {
			mutations.forEach(this);
		}.bind(updateIndicatorOnClassMutation));
		// observe happiness
		observer.observe(document.getElementById('happiness'), {attributes: true});
		$('#happiness').updateIndicator();

});
<|endoftext|>