<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * 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.
 */

CLASS({
  package: 'foam.demos',
  name: 'ClockView',
  extends: 'foam.graphics.Circle',

  traits: [ 'com.google.misc.Colors' ],

  requires: [
    'foam.graphics.Circle',
    'foam.ui.IntFieldView'
  ],

  properties: [
    {
      type: 'Boolean',
      name: 'drawTicks',
    },
    {
      type: 'Float',
      name: 'r',
      label: 'Radius',
      defaultValue: 100
    },
    {
      name: 'color',
      type: 'String',
      factory: function() { return this.BLUE; }
    },
    {
      name: 'minuteHand',
      factory: function() {
        return this.Hand.create({r:this.r-6, width:5, color: this.GREEN});
      }
    },
    {
      name: 'hourHand',
      factory: function() {
        return this.Hand.create({r:this.r-15, width:7, color: this.YELLOW});
      }
    },
    {
      name: 'secondHand',
      factory: function() {
        return this.Hand.create({r:this.r-6, width:3, color: this.RED});
      }
    }
  ],

  methods: {
    init: function() {
      this.SUPER();

      this.border = this.color;
      this.borderWidth = 5;
      this.color = 'white';

      this.addChild(this.hourHand);
      this.addChild(this.minuteHand);
      this.addChild(this.secondHand);
    },

    paintSelf: function(c) {
      this.SUPER(c);

      var date = new Date();

      this.secondHand.a = Math.PI/2 - Math.PI*2 * date.getSeconds() / 60 ;
      this.minuteHand.a = Math.PI/2 - Math.PI*2 * date.getMinutes() / 60 ;
      this.hourHand.a   = Math.PI/2 - Math.PI*2 * (date.getHours() % 12) / 12 + this.minuteHand.a / 12;

      if ( ! this.drawTicks ) return;

      for ( var i = 0 ; i < 12 ; i++ ) {
        var a = Math.PI*2/12*i;
        var l = i % 3 ? 6 : 10;
        var w = i % 3 ? 2 : 3;
        c.beginPath();
        c.moveTo((this.r-l)*Math.cos(a),-(this.r-l)*Math.sin(a));
        c.lineTo((this.r)*Math.cos(a),-(this.r)*Math.sin(a));
        c.closePath();

        c.lineWidth = w;
        c.strokeStyle = this.BLUE;
        c.stroke();
      }
    }
  },

  models: [
    {
      name: 'Hand',
      label: 'Clock Hand',
      extends: 'foam.graphics.CView',

      properties:
      [
        {
          model_: 'Property',
          name: 'color',
          type: 'String',
          defaultValue: 'blue'
        },
        {
          type: 'Int',
          name: 'width',
          defaultValue: 5
        },
        {
          type: 'Int',
          name: 'r',
          label: 'Radius',
          defaultValue: 100
        },
        {
          type: 'Int',
          name: 'a',
          label: 'Rotation',
          defaultValue: 100
        }
      ],
      methods:
      [
        function paint(canvas) {
          canvas.beginPath();
          canvas.moveTo(0,0);
          canvas.lineTo(this.r*Math.cos(this.a),-this.r*Math.sin(this.a));
          canvas.closePath();

          canvas.lineWidth = this.width;
          canvas.strokeStyle = this.color;
          canvas.stroke();
        }
      ]
    }
  ]
});
<commit_msg>Fix for ClockView.<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * 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.
 */

CLASS({
  package: 'foam.demos',
  name: 'ClockView',
  extends: 'foam.graphics.Circle',

  traits: [ 'com.google.misc.Colors' ],

  requires: [
    'foam.graphics.Circle',
    'foam.ui.IntFieldView'
  ],

  properties: [
    {
      type: 'Boolean',
      name: 'drawTicks',
    },
    {
      type: 'Float',
      name: 'r',
      label: 'Radius',
      defaultValue: 100
    },
    {
      name: 'color',
      type: 'String',
      factory: function() { return this.BLUE; }
    },
    {
      name: 'minuteHand',
      factory: function() {
        return this.Hand.create({r:this.r-6, width:5, color: this.GREEN});
      }
    },
    {
      name: 'hourHand',
      factory: function() {
        return this.Hand.create({r:this.r-15, width:7, color: this.YELLOW});
      }
    },
    {
      name: 'secondHand',
      factory: function() {
        return this.Hand.create({r:this.r-6, width:3, color: this.RED});
      }
    }
  ],

  methods: {
    init: function() {
      this.SUPER();

      this.border = this.color;
      this.borderWidth = 5;
      this.color = 'white';

      this.addChild(this.hourHand);
      this.addChild(this.minuteHand);
      this.addChild(this.secondHand);
    },

    paintSelf: function(c) {
      this.SUPER(c);

      var date = new Date();

      this.secondHand.a = Math.PI/2 - Math.PI*2 * date.getSeconds() / 60 ;
      this.minuteHand.a = Math.PI/2 - Math.PI*2 * date.getMinutes() / 60 ;
      this.hourHand.a   = Math.PI/2 - Math.PI*2 * (date.getHours() % 12) / 12 + this.minuteHand.a / 12;

      if ( ! this.drawTicks ) return;

      for ( var i = 0 ; i < 12 ; i++ ) {
        var a = Math.PI*2/12*i;
        var l = i % 3 ? 6 : 10;
        var w = i % 3 ? 2 : 3;
        c.beginPath();
        c.moveTo((this.r-l)*Math.cos(a),-(this.r-l)*Math.sin(a));
        c.lineTo((this.r)*Math.cos(a),-(this.r)*Math.sin(a));
        c.closePath();

        c.lineWidth = w;
        c.strokeStyle = this.BLUE;
        c.stroke();
      }
    }
  },

  models: [
    {
      name: 'Hand',
      label: 'Clock Hand',
      extends: 'foam.graphics.CView',

      properties:
      [
        {
          model_: 'Property',
          name: 'color',
          type: 'String',
          defaultValue: 'blue'
        },
        {
          type: 'Int',
          name: 'width',
          defaultValue: 5
        },
        {
          type: 'Int',
          name: 'r',
          label: 'Radius',
          defaultValue: 100
        },
        {
          name: 'a',
          label: 'Rotation',
          defaultValue: 100
        }
      ],
      methods:
      [
        function paint(canvas) {
          canvas.beginPath();
          canvas.moveTo(0,0);
          canvas.lineTo(this.r*Math.cos(this.a),-this.r*Math.sin(this.a));
          canvas.closePath();

          canvas.lineWidth = this.width;
          canvas.strokeStyle = this.color;
          canvas.stroke();
        }
      ]
    }
  ]
});
<|endoftext|>


<commit_before>/**
 * Copyright 2015 Wouter van Heeswijk
 */

define(
    ["gl", "shaders/manager", "geometries/buffer-binder"],
    function (gl, shaderManager, bufferBinder) {
        return {
            verify: function (asset) {
                // For the verification of the number of triangles
                var expectedTrianglesCount = asset.count;

                // Verify vertices (if present)
                if (asset.hasOwnProperty("vertexSize")) {
                    var actualTrianglesCount = asset.vertices.length;
                    if (expectedTrianglesCount !== actualTrianglesCount) {
                        throw new Error("Expected " + expectedTrianglesCount + " triangles in vertices property but found " + actualTrianglesCount);
                    }

                    var expectedVertexSize = asset.vertexSize;
                    for (var i = 0; i < actualTrianglesCount; i++) {
                        var triangle = asset.vertices[i];

                        if (triangle.length !== 3) {
                            throw new Error("Expected triangle with 3 vertices but found " + triangle.length + " vertices");
                        }

                        for (var j = 0; j < 3; j++) {
                            var vertex = triangle[j];
                            var actualVertexSize = vertex.length;

                            if (expectedVertexSize !== actualVertexSize) {
                                throw new Error("Expected vertex of size " + expectedVertexSize + " but was " + actualVertexSize);
                            }

                            for (var k = 0; k < actualVertexSize; k++) {
                                if (isNaN(vertex[k])) {
                                    throw new Error("Expected number in vertex but found '" + vertex[k] + "'");
                                }
                            }
                        }
                    }
                }

                // Verify colors (if present)
                if (asset.hasOwnProperty("colorSize")) {
                    var actualTrianglesCount = asset.colors.length;
                    if (expectedTrianglesCount !== actualTrianglesCount) {
                        throw new Error("Expected " + expectedTrianglesCount + " triangles in colors property but found " + actualTrianglesCount);
                    }

                    var expectedColorSize = asset.colorSize;
                    for (var i = 0; i < actualTrianglesCount; i++) {
                        var triangle = asset.colors[i];

                        if (triangle.length !== 3) {
                            throw new Error("Expected triangle with 3 colors but found " + triangle.length + " colors");
                        }

                        for (var j = 0; j < 3; j++) {
                            var color = triangle[j];
                            var actualColorSize = color.length;

                            if (expectedColorSize !== actualColorSize) {
                                throw new Error("Expected color of size " + expectedColorSize + " but was " + actualColorSize);
                            }

                            for (var k = 0; k < actualColorSize; k++) {
                                if (isNaN(color[k])) {
                                    throw new Error("Expected number in color but found '" + color[k] + "'");
                                }
                            }
                        }
                    }
                }
            },

            create: function (asset) {
                // Configuration
                var shader = shaderManager.getByName(asset.shader);
                var trianglesCount = asset.count;
                var binder = bufferBinder.create(shader);

                // Flatten vertices and add bind function (if present)
                if (asset.hasOwnProperty("vertexSize")) {
                    var vertices = [].concat.apply([], asset.vertices.map(function (triangle) {
                        return [].concat.apply([], triangle);
                    }));
                    binder.add(vertices, asset.vertexSize, "position");
                }

                // Flatten colors and add bind function (if present)
                if (asset.hasOwnProperty("colorSize")) {
                    var colors = [].concat.apply([], asset.colors.map(function (triangle) {
                        return [].concat.apply([], triangle);
                    }));
                    binder.add(colors, asset.colorSize, "color");
                }

                return {
                    shader: shader,

                    draw: function (shaderInfo) {
                        binder.bindAll();
                        gl.drawArrays(gl.TRIANGLES, 0, trianglesCount * 3);
                    }
                };
            }
        };
    }
);
<commit_msg>Removed unused shaderInfo parameter<commit_after>/**
 * Copyright 2015 Wouter van Heeswijk
 */

define(
    ["gl", "shaders/manager", "geometries/buffer-binder"],
    function (gl, shaderManager, bufferBinder) {
        return {
            verify: function (asset) {
                // For the verification of the number of triangles
                var expectedTrianglesCount = asset.count;

                // Verify vertices (if present)
                if (asset.hasOwnProperty("vertexSize")) {
                    var actualTrianglesCount = asset.vertices.length;
                    if (expectedTrianglesCount !== actualTrianglesCount) {
                        throw new Error("Expected " + expectedTrianglesCount + " triangles in vertices property but found " + actualTrianglesCount);
                    }

                    var expectedVertexSize = asset.vertexSize;
                    for (var i = 0; i < actualTrianglesCount; i++) {
                        var triangle = asset.vertices[i];

                        if (triangle.length !== 3) {
                            throw new Error("Expected triangle with 3 vertices but found " + triangle.length + " vertices");
                        }

                        for (var j = 0; j < 3; j++) {
                            var vertex = triangle[j];
                            var actualVertexSize = vertex.length;

                            if (expectedVertexSize !== actualVertexSize) {
                                throw new Error("Expected vertex of size " + expectedVertexSize + " but was " + actualVertexSize);
                            }

                            for (var k = 0; k < actualVertexSize; k++) {
                                if (isNaN(vertex[k])) {
                                    throw new Error("Expected number in vertex but found '" + vertex[k] + "'");
                                }
                            }
                        }
                    }
                }

                // Verify colors (if present)
                if (asset.hasOwnProperty("colorSize")) {
                    var actualTrianglesCount = asset.colors.length;
                    if (expectedTrianglesCount !== actualTrianglesCount) {
                        throw new Error("Expected " + expectedTrianglesCount + " triangles in colors property but found " + actualTrianglesCount);
                    }

                    var expectedColorSize = asset.colorSize;
                    for (var i = 0; i < actualTrianglesCount; i++) {
                        var triangle = asset.colors[i];

                        if (triangle.length !== 3) {
                            throw new Error("Expected triangle with 3 colors but found " + triangle.length + " colors");
                        }

                        for (var j = 0; j < 3; j++) {
                            var color = triangle[j];
                            var actualColorSize = color.length;

                            if (expectedColorSize !== actualColorSize) {
                                throw new Error("Expected color of size " + expectedColorSize + " but was " + actualColorSize);
                            }

                            for (var k = 0; k < actualColorSize; k++) {
                                if (isNaN(color[k])) {
                                    throw new Error("Expected number in color but found '" + color[k] + "'");
                                }
                            }
                        }
                    }
                }
            },

            create: function (asset) {
                // Configuration
                var shader = shaderManager.getByName(asset.shader);
                var trianglesCount = asset.count;
                var binder = bufferBinder.create(shader);

                // Flatten vertices and add bind function (if present)
                if (asset.hasOwnProperty("vertexSize")) {
                    var vertices = [].concat.apply([], asset.vertices.map(function (triangle) {
                        return [].concat.apply([], triangle);
                    }));
                    binder.add(vertices, asset.vertexSize, "position");
                }

                // Flatten colors and add bind function (if present)
                if (asset.hasOwnProperty("colorSize")) {
                    var colors = [].concat.apply([], asset.colors.map(function (triangle) {
                        return [].concat.apply([], triangle);
                    }));
                    binder.add(colors, asset.colorSize, "color");
                }

                return {
                    shader: shader,

                    draw: function () {
                        binder.bindAll();
                        gl.drawArrays(gl.TRIANGLES, 0, trianglesCount * 3);
                    }
                };
            }
        };
    }
);
<|endoftext|>


<commit_before>// Copyright 2015-2019, University of Colorado Boulder

/**
 * This grunt task generates the README.md file for a simulation.
 * Placeholders in a template file are replaced with values specific to the simulation.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */

'use strict';

const ChipperStringUtils = require( '../common/ChipperStringUtils' );
const getPhetLibs = require( './getPhetLibs' );
const getTitleStringKey = require( './getTitleStringKey' );
const grunt = require( 'grunt' );

/**
 * @param {string} repo - name of the repository
 * @param {boolean} published - has the sim been published?
 */
module.exports = function( repo, published ) {

  // Read the title from the English strings file.
  const simTitleStringKey = getTitleStringKey( repo );
  const strings = grunt.file.readJSON( `../${repo}/${repo}-strings_en.json` );
  const titleKey = simTitleStringKey.split( '/' ).pop(); // eg. 'EXAMPLE_SIM/example-sim.title' -> 'example-sim.title'
  const title = strings[ titleKey ].value;
  const phetLibs = getPhetLibs( repo, 'phet' );

  // Commands for cloning all required repositories
  let cloneCommands = '';
  for ( let i = 0; i < phetLibs.length; i++ ) {
    cloneCommands = cloneCommands + 'git clone https://github.com/phetsims/' + phetLibs[ i ] + '.git';
    if ( i !== phetLibs.length - 1 ) {
      cloneCommands += '\n';
    }
  }

  // Read the template.
  const templateFile = published ? 'README-published.md' : 'README-unpublished.md';
  let readme = grunt.file.read( '../chipper/templates/' + templateFile );

  // Replace placeholders in the template.
  readme = ChipperStringUtils.replaceAll( readme, '{{REPOSITORY}}', repo );
  readme = ChipperStringUtils.replaceAll( readme, '{{TITLE}}', title );
  readme = ChipperStringUtils.replaceAll( readme, '{{CLONE_COMMANDS}}', cloneCommands );

  // Write to the repository's root directory.
  grunt.file.write( `../${repo}/README.md`, readme );
};

<commit_msg>Simplify generation of clone commands, see https://github.com/phetsims/utterance-queue/issues/5<commit_after>// Copyright 2015-2019, University of Colorado Boulder

/**
 * This grunt task generates the README.md file for a simulation.
 * Placeholders in a template file are replaced with values specific to the simulation.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */

'use strict';

const ChipperStringUtils = require( '../common/ChipperStringUtils' );
const getPhetLibs = require( './getPhetLibs' );
const getTitleStringKey = require( './getTitleStringKey' );
const grunt = require( 'grunt' );

/**
 * @param {string} repo - name of the repository
 * @param {boolean} published - has the sim been published?
 */
module.exports = function( repo, published ) {

  // Read the title from the English strings file.
  const simTitleStringKey = getTitleStringKey( repo );
  const strings = grunt.file.readJSON( `../${repo}/${repo}-strings_en.json` );
  const titleKey = simTitleStringKey.split( '/' ).pop(); // eg. 'EXAMPLE_SIM/example-sim.title' -> 'example-sim.title'
  const title = strings[ titleKey ].value;
  const phetLibs = getPhetLibs( repo, 'phet' );

  // Commands for cloning all required repositories
  const cloneCommands = phetLibs.map( phetLib => `git clone https://github.com/phetsims/${phetLib}.git` ).join( '\n' );

  // Read the template.
  const templateFile = published ? 'README-published.md' : 'README-unpublished.md';
  let readme = grunt.file.read( '../chipper/templates/' + templateFile );

  // Replace placeholders in the template.
  readme = ChipperStringUtils.replaceAll( readme, '{{REPOSITORY}}', repo );
  readme = ChipperStringUtils.replaceAll( readme, '{{TITLE}}', title );
  readme = ChipperStringUtils.replaceAll( readme, '{{CLONE_COMMANDS}}', cloneCommands );

  // Write to the repository's root directory.
  grunt.file.write( `../${repo}/README.md`, readme );
};<|endoftext|>


<commit_before>iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) {
    var way = context.entity(wayId),
        isArea = way.geometry() === 'area',
        finished = false,
        annotation = t((way.isDegenerate() ?
            'operations.start.annotation.' :
            'operations.continue.annotation.') + context.geometry(wayId)),
        draw = iD.behavior.Draw(context);

    var startIndex = typeof index === 'undefined' ? way.nodes.length - 1 : 0,
        start = iD.Node({loc: context.graph().entity(way.nodes[startIndex]).loc}),
        end = iD.Node({loc: context.map().mouseCoordinates()}),
        segment = iD.Way({
            nodes: [start.id, end.id],
            tags: _.clone(way.tags)
        });

    var f = context[way.isDegenerate() ? 'replace' : 'perform'];
    if (isArea) {
        f(iD.actions.AddEntity(end),
            iD.actions.AddVertex(wayId, end.id, index));
    } else {
        f(iD.actions.AddEntity(start),
            iD.actions.AddEntity(end),
            iD.actions.AddEntity(segment));
    }

    function move(datum) {
        var loc = context.map().mouseCoordinates();

        if (datum.id === end.id || datum.id === segment.id) {
            context.surface().selectAll('.way, .node')
                .filter(function(d) {
                    return d.id === end.id || d.id === segment.id;
                })
                .classed('active', true);
        } else if (datum.type === 'node') {
            loc = datum.loc;
        } else if (datum.type === 'way') {
            loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc;
        }

        context.replace(iD.actions.MoveNode(end.id, loc));
    }

    function undone() {
        finished = true;
        context.enter(iD.modes.Browse(context));
    }

    function lineActives(d) {
        return d.id === segment.id || d.id === start.id || d.id === end.id;
    }

    function areaActives(d) {
        return d.id === wayId || d.id === end.id;
    }

    var drawWay = function(surface) {
        draw.on('move', move)
            .on('click', drawWay.add)
            .on('clickWay', drawWay.addWay)
            .on('clickNode', drawWay.addNode)
            .on('undo', context.undo)
            .on('cancel', drawWay.cancel)
            .on('finish', drawWay.finish);

        context.map()
            .minzoom(16)
            .dblclickEnable(false);

        surface.call(draw)
          .selectAll('.way, .node')
            .filter(isArea ? areaActives : lineActives)
            .classed('active', true);

        context.history()
            .on('undone.draw', undone);
    };

    drawWay.off = function(surface) {
        if (!finished)
            context.pop();

        context.map()
            .minzoom(0)
            .tail(false);

        surface.call(draw.off)
          .selectAll('.way, .node')
            .classed('active', false);

        context.history()
            .on('undone.draw', null);
    };

    function ReplaceTemporaryNode(newNode) {
        return function(graph) {
            if (isArea) {
                return graph
                    .replace(way.removeNode(end.id).addNode(newNode.id, index))
                    .remove(end);

            } else {
                return graph
                    .replace(graph.entity(wayId).addNode(newNode.id, index))
                    .remove(end)
                    .remove(segment)
                    .remove(start);
            }
        };
    }

    // Accept the current position of the temporary node and continue drawing.
    drawWay.add = function(loc) {
        var newNode = iD.Node({loc: loc});

        context.replace(
            iD.actions.AddEntity(newNode),
            ReplaceTemporaryNode(newNode),
            annotation);

        finished = true;
        context.enter(mode);
    };

    // Connect the way to an existing way.
    drawWay.addWay = function(loc, edge) {
        var newNode = iD.Node({ loc: loc });

        context.perform(
            iD.actions.AddMidpoint({ loc: loc, edge: edge}, newNode),
            ReplaceTemporaryNode(newNode),
            annotation);

        finished = true;
        context.enter(mode);
    };

    // Connect the way to an existing node and continue drawing.
    drawWay.addNode = function(node) {

        // Avoid creating duplicate segments
        if (way.areAdjacent(node.id, way.nodes[way.nodes.length - 1])) return;

        context.perform(
            ReplaceTemporaryNode(node),
            annotation);

        finished = true;
        context.enter(mode);
    };

    // Finish the draw operation, removing the temporary node. If the way has enough
    // nodes to be valid, it's selected. Otherwise, return to browse mode.
    drawWay.finish = function() {
        context.pop();
        finished = true;

        window.setTimeout(function() {
            context.map().dblclickEnable(true);
        }, 1000);

        var way = context.entity(wayId);
        if (way) {
            context.enter(iD.modes.Select(context, [way.id], true));
        } else {
            context.enter(iD.modes.Browse(context));
        }
    };

    // Cancel the draw operation and return to browse, deleting everything drawn.
    drawWay.cancel = function() {
        context.perform(
            d3.functor(baseGraph),
            t('operations.cancel_draw.annotation'));

        window.setTimeout(function() {
            context.map().dblclickEnable(true);
        }, 1000);

        finished = true;
        context.enter(iD.modes.Browse(context));
    };

    return drawWay;
};
<commit_msg>Prevent creation of ways with duplicate nodes<commit_after>iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) {
    var way = context.entity(wayId),
        isArea = way.geometry() === 'area',
        finished = false,
        annotation = t((way.isDegenerate() ?
            'operations.start.annotation.' :
            'operations.continue.annotation.') + context.geometry(wayId)),
        draw = iD.behavior.Draw(context);

    var startIndex = typeof index === 'undefined' ? way.nodes.length - 1 : 0,
        start = iD.Node({loc: context.graph().entity(way.nodes[startIndex]).loc}),
        end = iD.Node({loc: context.map().mouseCoordinates()}),
        segment = iD.Way({
            nodes: [start.id, end.id],
            tags: _.clone(way.tags)
        });

    var f = context[way.isDegenerate() ? 'replace' : 'perform'];
    if (isArea) {
        f(iD.actions.AddEntity(end),
            iD.actions.AddVertex(wayId, end.id, index));
    } else {
        f(iD.actions.AddEntity(start),
            iD.actions.AddEntity(end),
            iD.actions.AddEntity(segment));
    }

    function move(datum) {
        var loc = context.map().mouseCoordinates();

        if (datum.id === end.id || datum.id === segment.id) {
            context.surface().selectAll('.way, .node')
                .filter(function(d) {
                    return d.id === end.id || d.id === segment.id;
                })
                .classed('active', true);
        } else if (datum.type === 'node') {
            loc = datum.loc;
        } else if (datum.type === 'way') {
            loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc;
        }

        context.replace(iD.actions.MoveNode(end.id, loc));
    }

    function undone() {
        finished = true;
        context.enter(iD.modes.Browse(context));
    }

    function lineActives(d) {
        return d.id === segment.id || d.id === start.id || d.id === end.id;
    }

    function areaActives(d) {
        return d.id === wayId || d.id === end.id;
    }

    var drawWay = function(surface) {
        draw.on('move', move)
            .on('click', drawWay.add)
            .on('clickWay', drawWay.addWay)
            .on('clickNode', drawWay.addNode)
            .on('undo', context.undo)
            .on('cancel', drawWay.cancel)
            .on('finish', drawWay.finish);

        context.map()
            .minzoom(16)
            .dblclickEnable(false);

        surface.call(draw)
          .selectAll('.way, .node')
            .filter(isArea ? areaActives : lineActives)
            .classed('active', true);

        context.history()
            .on('undone.draw', undone);
    };

    drawWay.off = function(surface) {
        if (!finished)
            context.pop();

        context.map()
            .minzoom(0)
            .tail(false);

        surface.call(draw.off)
          .selectAll('.way, .node')
            .classed('active', false);

        context.history()
            .on('undone.draw', null);
    };

    function ReplaceTemporaryNode(newNode) {
        return function(graph) {
            if (isArea) {
                return graph
                    .replace(way.removeNode(end.id).addNode(newNode.id, index))
                    .remove(end);

            } else {
                return graph
                    .replace(graph.entity(wayId).addNode(newNode.id, index))
                    .remove(end)
                    .remove(segment)
                    .remove(start);
            }
        };
    }

    // Accept the current position of the temporary node and continue drawing.
    drawWay.add = function(loc) {

        // prevent duplicate nodes
        var last = context.entity(way.nodes[way.nodes.length - (isArea ? 2 : 1)]);
        if (last && last.loc[0] === loc[0] && last.loc[1] === loc[1]) return;

        var newNode = iD.Node({loc: loc});

        context.replace(
            iD.actions.AddEntity(newNode),
            ReplaceTemporaryNode(newNode),
            annotation);

        finished = true;
        context.enter(mode);
    };

    // Connect the way to an existing way.
    drawWay.addWay = function(loc, edge) {
        var newNode = iD.Node({ loc: loc });

        context.perform(
            iD.actions.AddMidpoint({ loc: loc, edge: edge}, newNode),
            ReplaceTemporaryNode(newNode),
            annotation);

        finished = true;
        context.enter(mode);
    };

    // Connect the way to an existing node and continue drawing.
    drawWay.addNode = function(node) {

        // Avoid creating duplicate segments
        if (way.areAdjacent(node.id, way.nodes[way.nodes.length - 1])) return;

        context.perform(
            ReplaceTemporaryNode(node),
            annotation);

        finished = true;
        context.enter(mode);
    };

    // Finish the draw operation, removing the temporary node. If the way has enough
    // nodes to be valid, it's selected. Otherwise, return to browse mode.
    drawWay.finish = function() {
        context.pop();
        finished = true;

        window.setTimeout(function() {
            context.map().dblclickEnable(true);
        }, 1000);

        var way = context.entity(wayId);
        if (way) {
            context.enter(iD.modes.Select(context, [way.id], true));
        } else {
            context.enter(iD.modes.Browse(context));
        }
    };

    // Cancel the draw operation and return to browse, deleting everything drawn.
    drawWay.cancel = function() {
        context.perform(
            d3.functor(baseGraph),
            t('operations.cancel_draw.annotation'));

        window.setTimeout(function() {
            context.map().dblclickEnable(true);
        }, 1000);

        finished = true;
        context.enter(iD.modes.Browse(context));
    };

    return drawWay;
};
<|endoftext|>


<commit_before>// Example menu.
document.write(
	'
'
	+	''

	+	'Hello World
'
	+	'Cordova Accelerometer
'
	+	'Philips Hue Demo
'

	+	'BLE Scan
'
	+	'Eddystone Scan
'
	+	'iBeacon Scan
'
	+	'Estimote Beacons
'
	+	'Estimote Nearables
'
	+	'BLE Discovery
'

	+	'TI SensorTag Accelerometer
'
	+	'TI SensorTag Sensors
'
	+	'TI SensorTag CC2650 Demo
'
	+	'TI SensorTag CC2541 Demo
'

	+	'mbed Custom GAP
'
	+	'mbed Custom GATT
'
	+	'Nordic nRF51 BLE
'

	+	'Arduino LED On/Off TCP
'
	+	'Arduino LED On/Off BLE
'
	+	'Arduino BLE
'
	+	'Arduino Scriptable TCP
'
	+	'Arduino 101 LED On/Off BLE
'

	+	'RedBearLab Simple Control
'
	+	'RedBearLab Simple Chat
'

	+	'MediaTek Linkit ONE Position
'
	+	'MediaTek Linkit Connect 7681
'
	+	'MediaTek LinkIt Smart 7688 Duo
'
	+	'ESP8266
'
	+	'LightBlue Bean Basic
'
	+	'RFduino Led On/Off
'
	+	'Bluno Hello World
'
	+	'RPi 3 - System Information
'
	+	'Dialog - Blinky
'
	+	'Dialog - IoT Sensor
'
	+	'Dialog - Beacons
'

	+ '
')
<commit_msg>Added link to ECMAScript6 example, typo on MediaTek7688 example<commit_after>// Example menu.
document.write(
	'
'
	+	''

	+	'Hello World
'
        +       'Hello ECMAScript6
'
	+	'Cordova Accelerometer
'
	+	'Philips Hue Demo
'

	+	'BLE Scan
'
	+	'Eddystone Scan
'
	+	'iBeacon Scan
'
	+	'Estimote Beacons
'
	+	'Estimote Nearables
'
	+	'BLE Discovery
'

	+	'TI SensorTag Accelerometer
'
	+	'TI SensorTag Sensors
'
	+	'TI SensorTag CC2650 Demo
'
	+	'TI SensorTag CC2541 Demo
'

	+	'mbed Custom GAP
'
	+	'mbed Custom GATT
'
	+	'Nordic nRF51 BLE
'

	+	'Arduino LED On/Off TCP
'
	+	'Arduino LED On/Off BLE
'
	+	'Arduino BLE
'
	+	'Arduino Scriptable TCP
'
	+	'Arduino 101 LED On/Off BLE
'

	+	'RedBearLab Simple Control
'
	+	'RedBearLab Simple Chat
'

	+	'MediaTek Linkit ONE Position
'
	+	'MediaTek Linkit Connect 7681
'
	+	'MediaTek LinkIt Smart 7688 Duo
'
	+	'ESP8266
'
	+	'LightBlue Bean Basic
'
	+	'RFduino Led On/Off
'
	+	'Bluno Hello World
'
	+	'RPi 3 - System Information
'
	+	'Dialog - Blinky
'
	+	'Dialog - IoT Sensor
'
	+	'Dialog - Beacons
'

	+ '
')
<|endoftext|>


<commit_before>/*
 * vim: ts=4:sw=4:expandtab
 */
(function () {
  'use strict';
   window.Whisper = window.Whisper || {};

   // TODO: Factor out private and group subclasses of Conversation

   var COLORS = [
        "#EF5350", // red
        "#EC407A", // pink
        "#AB47BC", // purple
        "#7E57C2", // deep purple
        "#5C6BC0", // indigo
        "#2196F3", // blue
        "#03A9F4", // light blue
        "#00BCD4", // cyan
        "#009688", // teal
        "#4CAF50", // green
        "#7CB342", // light green
        "#FF9800", // orange
        "#FF5722", // deep orange
        "#FFB300", // amber
        "#607D8B", // blue grey
    ];

  Whisper.Conversation = Backbone.Model.extend({
    database: Whisper.Database,
    storeName: 'conversations',
    defaults: function() {
      var timestamp = new Date().getTime();
      return {
        unreadCount : 0,
        timestamp   : timestamp,
      };
    },

    initialize: function() {
        this.contactCollection = new Backbone.Collection();
        this.messageCollection = new Whisper.MessageCollection([], {
            conversation: this
        });

        this.on('change:avatar', this.updateAvatarUrl);
        this.on('destroy', this.revokeAvatarUrl);
    },

    validate: function(attributes, options) {
        var required = ['id', 'type'];
        var missing = _.filter(required, function(attr) { return !attributes[attr]; });
        if (missing.length) { return "Conversation must have " + missing; }

        if (attributes.type !== 'private' && attributes.type !== 'group') {
            return "Invalid conversation type: " + attributes.type;
        }

        // hack
        if (this.isPrivate()) {
            try {
                this.id = libphonenumber.util.verifyNumber(this.id);
                var number = libphonenumber.util.splitCountryCode(this.id);

                this.set({
                    e164_number: this.id,
                    national_number: '' + number.national_number,
                    international_number: '' + number.country_code + number.national_number
                });
            } catch(ex) {
                return ex;
            }
        }
    },

    sendMessage: function(body, attachments) {
        var now = Date.now();
        var message = this.messageCollection.add({
            body           : body,
            conversationId : this.id,
            type           : 'outgoing',
            attachments    : attachments,
            sent_at        : now,
            received_at    : now
        });
        message.save();

        this.save({
            unreadCount : 0,
            active_at   : now,
            timestamp   : now,
            lastMessage : body
        });

        var sendFunc;
        if (this.get('type') == 'private') {
            sendFunc = textsecure.messaging.sendMessageToNumber;
        }
        else {
            sendFunc = textsecure.messaging.sendMessageToGroup;
        }
        sendFunc(this.get('id'), body, attachments, now).then(function() {
            message.save({'sent': true});
        }.bind(this)).catch(function(errors) {
            if (errors instanceof Error) {
                errors = [errors];
            }
            var keyErrors = [];
            _.each(errors, function(e) {
                console.log(e);
                if (e.error.name === 'OutgoingIdentityKeyError') {
                    keyErrors.push(e.error);
                }
            });
            if (keyErrors.length) {
                message.save({ errors : keyErrors });
            } else {
                if (!(errors instanceof Array)) {
                    errors = [errors];
                }
                errors.map(function(e) {
                    if (e.error && e.error.stack) {
                        console.error(e.error.stack);
                    }
                });
                throw errors;
            }
        });
    },

    endSession: function() {
        if (this.isPrivate()) {
            var now = Date.now();
            var message = this.messageCollection.add({
                conversationId : this.id,
                type           : 'outgoing',
                sent_at        : now,
                received_at    : now,
                flags          : textsecure.protobuf.DataMessage.Flags.END_SESSION
            });
            message.save();
            textsecure.messaging.closeSession(this.id).then(function() {
                message.save({sent: true});
            });
        }

    },

    leaveGroup: function() {
        var now = Date.now();
        if (this.get('type') === 'group') {
            var message = this.messageCollection.add({
                group_update: { left: 'You' },
                conversationId : this.id,
                type           : 'outgoing',
                sent_at        : now,
                received_at    : now
            });
            message.save();
            textsecure.messaging.leaveGroup(this.id).then(function() {
                message.save({sent: true});
            });
        }
    },

    markRead: function() {
        if (this.get('unreadCount') > 0) {
            this.save({unreadCount: 0});
        }
    },

    fetchMessages: function() {
        if (!this.id) { return false; }
        return this.messageCollection.fetchConversation(this.id);
    },

    fetchContacts: function(options) {
        return new Promise(function(resolve) {
            if (this.isPrivate()) {
                this.contactCollection.reset([this]);
                resolve();
            } else {
                var promises = [];
                var members = this.get('members') || [];
                this.contactCollection.reset(
                    members.map(function(number) {
                        var c = ConversationController.create({
                            id   : number,
                            type : 'private'
                        });
                        promises.push(new Promise(function(resolve) {
                            c.fetch().always(resolve);
                        }));
                        return c;
                    }.bind(this))
                );
                resolve(Promise.all(promises));
            }
        }.bind(this));
    },

    destroyMessages: function() {
        var models = this.messageCollection.models;
        this.messageCollection.reset([]);
        _.each(models, function(message) { message.destroy(); });
        this.save({active_at: null}); // archive
    },

    getTitle: function() {
        if (this.isPrivate()) {
            return this.get('name') || this.id;
        } else {
            return this.get('name') || 'Unknown group';
        }
    },

    getNumber: function() {
        if (this.isPrivate()) {
            return this.id;
        } else {
            return '';
        }
    },

    isPrivate: function() {
        return this.get('type') === 'private';
    },

    revokeAvatarUrl: function() {
        if (this.avatarUrl) {
            URL.revokeObjectURL(this.avatarUrl);
            this.avatarUrl = null;
        }
    },

    getNotificationIcon: function() {
        return new Promise(function(resolve) {
            var avatar = this.getAvatar();
            if (avatar.url) {
                resolve(avatar.url);
            } else {
                resolve(new Whisper.IdenticonSVGView(avatar).getDataUrl());
            }
        }.bind(this));
    },

    updateAvatarUrl: function(silent) {
        this.revokeAvatarUrl();
        var avatar = this.get('avatar');
        if (avatar) {
            this.avatarUrl = URL.createObjectURL(
                new Blob([avatar.data], {type: avatar.contentType})
            );
        } else {
            this.avatarUrl = null;
        }
        if (!silent) {
            this.trigger('change');
        }
    },

    getAvatar: function() {
        if (this.avatarUrl === undefined) {
            this.updateAvatarUrl(true);
        }
        if (this.avatarUrl) {
            return { url: this.avatarUrl };
        } else if (this.isPrivate()) {
            var title = this.get('name');
            if (!title) {
                return { content: '#', color: '#999999' };
            }
            var initials = title.trim()[0];
            return {
                color: COLORS[Math.abs(this.hashCode()) % 15],
                content: initials
            };
        } else {
            return { url: '/images/group_default.png', color: 'gray' };
        }
    },

    resolveConflicts: function(conflict) {
        var number = conflict.number;
        var identityKey = conflict.identityKey;
        if (this.isPrivate()) {
            number = this.id;
        } else if (!_.include(this.get('members'), number)) {
            throw 'Tried to resolve conflicts for a unknown group member';
        }

        if (!this.messageCollection.hasKeyConflicts()) {
            throw 'No conflicts to resolve';
        }

        return textsecure.storage.axolotl.removeIdentityKey(number).then(function() {
            return textsecure.storage.axolotl.putIdentityKey(number, identityKey).then(function() {
                var promises = [];
                this.messageCollection.each(function(message) {
                    if (message.hasKeyConflict(number)) {
                        promises.push(new Promise(function(resolve) {
                            resolve(message.resolveConflict(number));
                        }));
                    }
                });
                return promises;
            }.bind(this));
        }.bind(this));
    },
    hashCode: function() {
        if (this.hash === undefined) {
            var string = this.getTitle() || '';
            if (string.length === 0) {
                return 0;
            }
            var hash = 0;
            for (var i = 0; i < string.length; i++) {
                hash = ((hash<<5)-hash) + string.charCodeAt(i);
                hash = hash & hash; // Convert to 32bit integer
            }

            this.hash = hash;
        }
        return this.hash;
    }
  });

  Whisper.ConversationCollection = Backbone.Collection.extend({
    database: Whisper.Database,
    storeName: 'conversations',
    model: Whisper.Conversation,

    comparator: function(m) {
      return -m.get('timestamp');
    },

    destroyAll: function () {
        return Promise.all(this.models.map(function(m) {
            return new Promise(function(resolve, reject) {
                m.destroy().then(resolve).fail(reject);
            });
        }));
    },

    fetchGroups: function(number) {
        return this.fetch({
            index: {
                name: 'group',
                only: number
            }
        });
    },

    fetchActive: function() {
        // Ensures all active conversations are included in this collection,
        // and updates their attributes, but removes nothing.
        return this.fetch({
            index: {
                name: 'inbox', // 'inbox' index on active_at
                order: 'desc'  // ORDER timestamp DESC
                // TODO pagination/infinite scroll
                // limit: 10, offset: page*10,
            },
            remove: false
        });
    }
  });
})();
<commit_msg>Reorder functions<commit_after>/*
 * vim: ts=4:sw=4:expandtab
 */
(function () {
  'use strict';
   window.Whisper = window.Whisper || {};

   // TODO: Factor out private and group subclasses of Conversation

   var COLORS = [
        "#EF5350", // red
        "#EC407A", // pink
        "#AB47BC", // purple
        "#7E57C2", // deep purple
        "#5C6BC0", // indigo
        "#2196F3", // blue
        "#03A9F4", // light blue
        "#00BCD4", // cyan
        "#009688", // teal
        "#4CAF50", // green
        "#7CB342", // light green
        "#FF9800", // orange
        "#FF5722", // deep orange
        "#FFB300", // amber
        "#607D8B", // blue grey
    ];

  Whisper.Conversation = Backbone.Model.extend({
    database: Whisper.Database,
    storeName: 'conversations',
    defaults: function() {
      var timestamp = new Date().getTime();
      return {
        unreadCount : 0,
        timestamp   : timestamp,
      };
    },

    initialize: function() {
        this.contactCollection = new Backbone.Collection();
        this.messageCollection = new Whisper.MessageCollection([], {
            conversation: this
        });

        this.on('change:avatar', this.updateAvatarUrl);
        this.on('destroy', this.revokeAvatarUrl);
    },

    validate: function(attributes, options) {
        var required = ['id', 'type'];
        var missing = _.filter(required, function(attr) { return !attributes[attr]; });
        if (missing.length) { return "Conversation must have " + missing; }

        if (attributes.type !== 'private' && attributes.type !== 'group') {
            return "Invalid conversation type: " + attributes.type;
        }

        // hack
        if (this.isPrivate()) {
            try {
                this.id = libphonenumber.util.verifyNumber(this.id);
                var number = libphonenumber.util.splitCountryCode(this.id);

                this.set({
                    e164_number: this.id,
                    national_number: '' + number.national_number,
                    international_number: '' + number.country_code + number.national_number
                });
            } catch(ex) {
                return ex;
            }
        }
    },

    sendMessage: function(body, attachments) {
        var now = Date.now();
        var message = this.messageCollection.add({
            body           : body,
            conversationId : this.id,
            type           : 'outgoing',
            attachments    : attachments,
            sent_at        : now,
            received_at    : now
        });
        message.save();

        this.save({
            unreadCount : 0,
            active_at   : now,
            timestamp   : now,
            lastMessage : body
        });

        var sendFunc;
        if (this.get('type') == 'private') {
            sendFunc = textsecure.messaging.sendMessageToNumber;
        }
        else {
            sendFunc = textsecure.messaging.sendMessageToGroup;
        }
        sendFunc(this.get('id'), body, attachments, now).then(function() {
            message.save({'sent': true});
        }.bind(this)).catch(function(errors) {
            if (errors instanceof Error) {
                errors = [errors];
            }
            var keyErrors = [];
            _.each(errors, function(e) {
                console.log(e);
                if (e.error.name === 'OutgoingIdentityKeyError') {
                    keyErrors.push(e.error);
                }
            });
            if (keyErrors.length) {
                message.save({ errors : keyErrors });
            } else {
                if (!(errors instanceof Array)) {
                    errors = [errors];
                }
                errors.map(function(e) {
                    if (e.error && e.error.stack) {
                        console.error(e.error.stack);
                    }
                });
                throw errors;
            }
        });
    },

    endSession: function() {
        if (this.isPrivate()) {
            var now = Date.now();
            var message = this.messageCollection.add({
                conversationId : this.id,
                type           : 'outgoing',
                sent_at        : now,
                received_at    : now,
                flags          : textsecure.protobuf.DataMessage.Flags.END_SESSION
            });
            message.save();
            textsecure.messaging.closeSession(this.id).then(function() {
                message.save({sent: true});
            });
        }

    },

    leaveGroup: function() {
        var now = Date.now();
        if (this.get('type') === 'group') {
            var message = this.messageCollection.add({
                group_update: { left: 'You' },
                conversationId : this.id,
                type           : 'outgoing',
                sent_at        : now,
                received_at    : now
            });
            message.save();
            textsecure.messaging.leaveGroup(this.id).then(function() {
                message.save({sent: true});
            });
        }
    },

    markRead: function() {
        if (this.get('unreadCount') > 0) {
            this.save({unreadCount: 0});
        }
    },

    fetchMessages: function() {
        if (!this.id) { return false; }
        return this.messageCollection.fetchConversation(this.id);
    },

    fetchContacts: function(options) {
        return new Promise(function(resolve) {
            if (this.isPrivate()) {
                this.contactCollection.reset([this]);
                resolve();
            } else {
                var promises = [];
                var members = this.get('members') || [];
                this.contactCollection.reset(
                    members.map(function(number) {
                        var c = ConversationController.create({
                            id   : number,
                            type : 'private'
                        });
                        promises.push(new Promise(function(resolve) {
                            c.fetch().always(resolve);
                        }));
                        return c;
                    }.bind(this))
                );
                resolve(Promise.all(promises));
            }
        }.bind(this));
    },

    destroyMessages: function() {
        var models = this.messageCollection.models;
        this.messageCollection.reset([]);
        _.each(models, function(message) { message.destroy(); });
        this.save({active_at: null}); // archive
    },

    getTitle: function() {
        if (this.isPrivate()) {
            return this.get('name') || this.id;
        } else {
            return this.get('name') || 'Unknown group';
        }
    },

    getNumber: function() {
        if (this.isPrivate()) {
            return this.id;
        } else {
            return '';
        }
    },

    isPrivate: function() {
        return this.get('type') === 'private';
    },

    revokeAvatarUrl: function() {
        if (this.avatarUrl) {
            URL.revokeObjectURL(this.avatarUrl);
            this.avatarUrl = null;
        }
    },

    updateAvatarUrl: function(silent) {
        this.revokeAvatarUrl();
        var avatar = this.get('avatar');
        if (avatar) {
            this.avatarUrl = URL.createObjectURL(
                new Blob([avatar.data], {type: avatar.contentType})
            );
        } else {
            this.avatarUrl = null;
        }
        if (!silent) {
            this.trigger('change');
        }
    },

    getAvatar: function() {
        if (this.avatarUrl === undefined) {
            this.updateAvatarUrl(true);
        }
        if (this.avatarUrl) {
            return { url: this.avatarUrl };
        } else if (this.isPrivate()) {
            var title = this.get('name');
            if (!title) {
                return { content: '#', color: '#999999' };
            }
            var initials = title.trim()[0];
            return {
                color: COLORS[Math.abs(this.hashCode()) % 15],
                content: initials
            };
        } else {
            return { url: '/images/group_default.png', color: 'gray' };
        }
    },

    getNotificationIcon: function() {
        return new Promise(function(resolve) {
            var avatar = this.getAvatar();
            if (avatar.url) {
                resolve(avatar.url);
            } else {
                resolve(new Whisper.IdenticonSVGView(avatar).getDataUrl());
            }
        }.bind(this));
    },

    resolveConflicts: function(conflict) {
        var number = conflict.number;
        var identityKey = conflict.identityKey;
        if (this.isPrivate()) {
            number = this.id;
        } else if (!_.include(this.get('members'), number)) {
            throw 'Tried to resolve conflicts for a unknown group member';
        }

        if (!this.messageCollection.hasKeyConflicts()) {
            throw 'No conflicts to resolve';
        }

        return textsecure.storage.axolotl.removeIdentityKey(number).then(function() {
            return textsecure.storage.axolotl.putIdentityKey(number, identityKey).then(function() {
                var promises = [];
                this.messageCollection.each(function(message) {
                    if (message.hasKeyConflict(number)) {
                        promises.push(new Promise(function(resolve) {
                            resolve(message.resolveConflict(number));
                        }));
                    }
                });
                return promises;
            }.bind(this));
        }.bind(this));
    },
    hashCode: function() {
        if (this.hash === undefined) {
            var string = this.getTitle() || '';
            if (string.length === 0) {
                return 0;
            }
            var hash = 0;
            for (var i = 0; i < string.length; i++) {
                hash = ((hash<<5)-hash) + string.charCodeAt(i);
                hash = hash & hash; // Convert to 32bit integer
            }

            this.hash = hash;
        }
        return this.hash;
    }
  });

  Whisper.ConversationCollection = Backbone.Collection.extend({
    database: Whisper.Database,
    storeName: 'conversations',
    model: Whisper.Conversation,

    comparator: function(m) {
      return -m.get('timestamp');
    },

    destroyAll: function () {
        return Promise.all(this.models.map(function(m) {
            return new Promise(function(resolve, reject) {
                m.destroy().then(resolve).fail(reject);
            });
        }));
    },

    fetchGroups: function(number) {
        return this.fetch({
            index: {
                name: 'group',
                only: number
            }
        });
    },

    fetchActive: function() {
        // Ensures all active conversations are included in this collection,
        // and updates their attributes, but removes nothing.
        return this.fetch({
            index: {
                name: 'inbox', // 'inbox' index on active_at
                order: 'desc'  // ORDER timestamp DESC
                // TODO pagination/infinite scroll
                // limit: 10, offset: page*10,
            },
            remove: false
        });
    }
  });
})();
<|endoftext|>


<commit_before>/* vim: ts=4:sw=4:expandtab
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function () {
  'use strict';
   window.Whisper = window.Whisper || {};

   // TODO: Factor out private and group subclasses of Conversation

  Whisper.Conversation = Backbone.Model.extend({
    database: Whisper.Database,
    storeName: 'conversations',
    defaults: function() {
      var timestamp = new Date().getTime();
      return {
        unreadCount : 0,
        timestamp   : timestamp,
        active_at   : timestamp
      };
    },

    initialize: function() {
        this.contactCollection = new Whisper.ConversationCollection();
        this.messageCollection = new Whisper.MessageCollection([], {
            conversation: this
        });

        this.on('change:avatar', this.updateAvatarUrl);
        this.on('destroy', this.revokeAvatarUrl);
    },

    validate: function(attributes, options) {
        var required = ['id', 'type', 'timestamp'];
        var missing = _.filter(required, function(attr) { return !attributes[attr]; });
        if (missing.length) { return "Conversation must have " + missing; }

        if (attributes.type !== 'private' && attributes.type !== 'group') {
            return "Invalid conversation type: " + attributes.type;
        }

        // hack
        if (this.get('type') === 'private') {
            try {
                this.id = libphonenumber.util.verifyNumber(this.id);
                var number = libphonenumber.util.splitCountryCode(this.id);

                this.set({
                    e164_number: this.id,
                    national_number: '' + number.national_number,
                    international_number: '' + number.country_code + number.national_number
                });
            } catch(ex) {
                return ex;
            }
        }
    },

    sendMessage: function(body, attachments) {
        var now = Date.now();
        var message = this.messageCollection.add({
            body           : body,
            conversationId : this.id,
            type           : 'outgoing',
            attachments    : attachments,
            sent_at        : now,
            received_at    : now
        });
        message.save();

        this.save({
            unreadCount : 0,
            active_at   : now,
            timestamp   : now,
            lastMessage : body
        });

        var sendFunc;
        if (this.get('type') == 'private') {
            sendFunc = textsecure.messaging.sendMessageToNumber;
        }
        else {
            sendFunc = textsecure.messaging.sendMessageToGroup;
        }
        sendFunc(this.get('id'), body, attachments, now).catch(function(errors) {
            var keyErrors = [];
            _.each(errors, function(e) {
                if (e.error.name === 'OutgoingIdentityKeyError') {
                    keyErrors.push(e.error);
                }
            });
            if (keyErrors.length) {
                message.save({ errors : keyErrors }).then(function() {
                    extension.trigger('message', message); // notify frontend listeners
                });
            } else {
                throw errors;
            }
        });
    },

    endSession: function() {
        if (this.get('type') === 'private') {
            textsecure.messaging.closeSession(this.id);
        }

    },

    leaveGroup: function() {
        if (this.get('type') === 'group') {
            textsecure.messaging.leaveGroup(this.id);
        }
    },

    receiveMessage: function(decrypted) {
        var conversation = this;
        var timestamp = decrypted.pushMessage.timestamp.toNumber();
        var m = this.messageCollection.add({
            body           : decrypted.message.body,
            timestamp      : timestamp,
            conversationId : this.id,
            attachments    : decrypted.message.attachments,
            type           : 'incoming',
            sender         : decrypted.pushMessage.source
        });

        if (timestamp > this.get('timestamp')) {
          this.set('timestamp', timestamp);
        }
        this.save({unreadCount: this.get('unreadCount') + 1, active: true});

        return new Promise(function (resolve) { m.save().then(resolve(m)); });
    },

    markRead: function() {
        if (this.get('unreadCount') > 0) {
            this.save({unreadCount: 0});
        }
    },

    fetchMessages: function(options) {
        return this.messageCollection.fetchConversation(this.id, options);
    },

    fetchContacts: function(options) {
        if (this.isPrivate()) {
            this.contactCollection.reset([this]);
        } else {
            var members = this.get('members') || [];
            this.contactCollection.reset(
                members.map(function(number) {
                    var c = this.collection.add({id: number, type: 'private'});
                    c.fetch();
                    return c;
                }.bind(this))
            );
        }
    },

    archive: function() {
        this.unset('active_at');
    },

    destroyMessages: function() {
        var models = this.messageCollection.models;
        this.messageCollection.reset([]);
        _.each(models, function(message) { message.destroy(); });
        this.archive();
        return this.save();
    },

    getTitle: function() {
        if (this.isPrivate()) {
            return this.get('name') || this.id;
        } else {
            return this.get('name') || 'Unknown group';
        }
    },

    getNumber: function() {
        if (this.get('type') === 'private') {
            return this.id;
        } else {
            return '';
        }
    },

    isPrivate: function() {
        return this.get('type') === 'private';
    },

    revokeAvatarUrl: function() {
        if (this.avatarUrl) {
            URL.revokeObjectURL(this.avatarUrl);
            this.avatarUrl = null;
        }
    },

    updateAvatarUrl: function() {
        this.revokeAvatarUrl();
        var avatar = this.get('avatar');
        if (avatar) {
            this.avatarUrl = URL.createObjectURL(
                new Blob([avatar.data], {type: avatar.contentType})
            );
        } else {
            this.avatarUrl = null;
        }
    },

    getAvatarUrl: function() {
        if (this.avatarUrl === undefined) {
            this.updateAvatarUrl();
        }
        return this.avatarUrl || '/images/default.png';
    },

    resolveConflicts: function(number) {
        if (this.isPrivate()) {
            number = this.id;
        } else if (!_.find(this.get('members'), number)) {
            throw 'Tried to resolve conflicts for a unknown group member';
        }

        if (!this.messageCollection.hasKeyConflicts()) {
            throw 'No conflicts to resolve';
        }

        textsecure.storage.devices.removeIdentityKeyForNumber(number);
        this.messageCollection.each(function(message) {
            if (message.hasKeyConflict(number)) {
                message.resolveConflict(number);
            }
        });
    }
  });

  Whisper.ConversationCollection = Backbone.Collection.extend({
    database: Whisper.Database,
    storeName: 'conversations',
    model: Whisper.Conversation,

    comparator: function(m) {
      return -m.get('timestamp');
    },

    createGroup: function(recipients, name, avatar) {
      var attributes = {};
      attributes = {
        name      : name,
        members   : recipients,
        type      : 'group',
        avatar    : avatar
      };
      var conversation = this.add(attributes, {merge: true});
      return textsecure.messaging.createGroup(recipients, name, avatar).then(function(groupId) {
        conversation.save({
          id      : getString(groupId),
          groupId : getString(groupId)
        });
        return conversation;
      });
    },

    findOrCreateForRecipient: function(recipient) {
      var attributes = {};
      attributes = {
        id        : recipient,
        name      : recipient,
        type      : 'private',
      };
      var conversation = this.add(attributes, {merge: true});
      conversation.save();
      return conversation;
    },

    addIncomingMessage: function(decrypted) {
      var attributes = {};
      if (decrypted.message.group) {
        attributes = {
          id         : decrypted.message.group.id,
          groupId    : decrypted.message.group.id,
          name       : decrypted.message.group.name || 'New group',
          type       : 'group',
        };
      } else {
        attributes = {
          id         : decrypted.pushMessage.source,
          name       : decrypted.pushMessage.source,
          type       : 'private'
        };
      }
      var conversation = this.add(attributes, {merge: true});
      return conversation.receiveMessage(decrypted);
    },

    destroyAll: function () {
        return Promise.all(this.models.map(function(m) {
            return new Promise(function(resolve, reject) {
                m.destroy().then(resolve).fail(reject);
            });
        }));
    },

    fetchGroups: function(number) {
        return this.fetch({
            index: {
                name: 'group',
                only: number
            }
        });
    }

  });
})();
<commit_msg>Fix resolveConflicts for groups<commit_after>/* vim: ts=4:sw=4:expandtab
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function () {
  'use strict';
   window.Whisper = window.Whisper || {};

   // TODO: Factor out private and group subclasses of Conversation

  Whisper.Conversation = Backbone.Model.extend({
    database: Whisper.Database,
    storeName: 'conversations',
    defaults: function() {
      var timestamp = new Date().getTime();
      return {
        unreadCount : 0,
        timestamp   : timestamp,
        active_at   : timestamp
      };
    },

    initialize: function() {
        this.contactCollection = new Whisper.ConversationCollection();
        this.messageCollection = new Whisper.MessageCollection([], {
            conversation: this
        });

        this.on('change:avatar', this.updateAvatarUrl);
        this.on('destroy', this.revokeAvatarUrl);
    },

    validate: function(attributes, options) {
        var required = ['id', 'type', 'timestamp'];
        var missing = _.filter(required, function(attr) { return !attributes[attr]; });
        if (missing.length) { return "Conversation must have " + missing; }

        if (attributes.type !== 'private' && attributes.type !== 'group') {
            return "Invalid conversation type: " + attributes.type;
        }

        // hack
        if (this.get('type') === 'private') {
            try {
                this.id = libphonenumber.util.verifyNumber(this.id);
                var number = libphonenumber.util.splitCountryCode(this.id);

                this.set({
                    e164_number: this.id,
                    national_number: '' + number.national_number,
                    international_number: '' + number.country_code + number.national_number
                });
            } catch(ex) {
                return ex;
            }
        }
    },

    sendMessage: function(body, attachments) {
        var now = Date.now();
        var message = this.messageCollection.add({
            body           : body,
            conversationId : this.id,
            type           : 'outgoing',
            attachments    : attachments,
            sent_at        : now,
            received_at    : now
        });
        message.save();

        this.save({
            unreadCount : 0,
            active_at   : now,
            timestamp   : now,
            lastMessage : body
        });

        var sendFunc;
        if (this.get('type') == 'private') {
            sendFunc = textsecure.messaging.sendMessageToNumber;
        }
        else {
            sendFunc = textsecure.messaging.sendMessageToGroup;
        }
        sendFunc(this.get('id'), body, attachments, now).catch(function(errors) {
            var keyErrors = [];
            _.each(errors, function(e) {
                if (e.error.name === 'OutgoingIdentityKeyError') {
                    keyErrors.push(e.error);
                }
            });
            if (keyErrors.length) {
                message.save({ errors : keyErrors }).then(function() {
                    extension.trigger('message', message); // notify frontend listeners
                });
            } else {
                throw errors;
            }
        });
    },

    endSession: function() {
        if (this.get('type') === 'private') {
            textsecure.messaging.closeSession(this.id);
        }

    },

    leaveGroup: function() {
        if (this.get('type') === 'group') {
            textsecure.messaging.leaveGroup(this.id);
        }
    },

    receiveMessage: function(decrypted) {
        var conversation = this;
        var timestamp = decrypted.pushMessage.timestamp.toNumber();
        var m = this.messageCollection.add({
            body           : decrypted.message.body,
            timestamp      : timestamp,
            conversationId : this.id,
            attachments    : decrypted.message.attachments,
            type           : 'incoming',
            sender         : decrypted.pushMessage.source
        });

        if (timestamp > this.get('timestamp')) {
          this.set('timestamp', timestamp);
        }
        this.save({unreadCount: this.get('unreadCount') + 1, active: true});

        return new Promise(function (resolve) { m.save().then(resolve(m)); });
    },

    markRead: function() {
        if (this.get('unreadCount') > 0) {
            this.save({unreadCount: 0});
        }
    },

    fetchMessages: function(options) {
        return this.messageCollection.fetchConversation(this.id, options);
    },

    fetchContacts: function(options) {
        if (this.isPrivate()) {
            this.contactCollection.reset([this]);
        } else {
            var members = this.get('members') || [];
            this.contactCollection.reset(
                members.map(function(number) {
                    var c = this.collection.add({id: number, type: 'private'});
                    c.fetch();
                    return c;
                }.bind(this))
            );
        }
    },

    archive: function() {
        this.unset('active_at');
    },

    destroyMessages: function() {
        var models = this.messageCollection.models;
        this.messageCollection.reset([]);
        _.each(models, function(message) { message.destroy(); });
        this.archive();
        return this.save();
    },

    getTitle: function() {
        if (this.isPrivate()) {
            return this.get('name') || this.id;
        } else {
            return this.get('name') || 'Unknown group';
        }
    },

    getNumber: function() {
        if (this.get('type') === 'private') {
            return this.id;
        } else {
            return '';
        }
    },

    isPrivate: function() {
        return this.get('type') === 'private';
    },

    revokeAvatarUrl: function() {
        if (this.avatarUrl) {
            URL.revokeObjectURL(this.avatarUrl);
            this.avatarUrl = null;
        }
    },

    updateAvatarUrl: function() {
        this.revokeAvatarUrl();
        var avatar = this.get('avatar');
        if (avatar) {
            this.avatarUrl = URL.createObjectURL(
                new Blob([avatar.data], {type: avatar.contentType})
            );
        } else {
            this.avatarUrl = null;
        }
    },

    getAvatarUrl: function() {
        if (this.avatarUrl === undefined) {
            this.updateAvatarUrl();
        }
        return this.avatarUrl || '/images/default.png';
    },

    resolveConflicts: function(number) {
        if (this.isPrivate()) {
            number = this.id;
        } else if (!_.include(this.get('members'), number)) {
            throw 'Tried to resolve conflicts for a unknown group member';
        }

        if (!this.messageCollection.hasKeyConflicts()) {
            throw 'No conflicts to resolve';
        }

        textsecure.storage.devices.removeIdentityKeyForNumber(number);
        this.messageCollection.each(function(message) {
            if (message.hasKeyConflict(number)) {
                message.resolveConflict(number);
            }
        });
    }
  });

  Whisper.ConversationCollection = Backbone.Collection.extend({
    database: Whisper.Database,
    storeName: 'conversations',
    model: Whisper.Conversation,

    comparator: function(m) {
      return -m.get('timestamp');
    },

    createGroup: function(recipients, name, avatar) {
      var attributes = {};
      attributes = {
        name      : name,
        members   : recipients,
        type      : 'group',
        avatar    : avatar
      };
      var conversation = this.add(attributes, {merge: true});
      return textsecure.messaging.createGroup(recipients, name, avatar).then(function(groupId) {
        conversation.save({
          id      : getString(groupId),
          groupId : getString(groupId)
        });
        return conversation;
      });
    },

    findOrCreateForRecipient: function(recipient) {
      var attributes = {};
      attributes = {
        id        : recipient,
        name      : recipient,
        type      : 'private',
      };
      var conversation = this.add(attributes, {merge: true});
      conversation.save();
      return conversation;
    },

    addIncomingMessage: function(decrypted) {
      var attributes = {};
      if (decrypted.message.group) {
        attributes = {
          id         : decrypted.message.group.id,
          groupId    : decrypted.message.group.id,
          name       : decrypted.message.group.name || 'New group',
          type       : 'group',
        };
      } else {
        attributes = {
          id         : decrypted.pushMessage.source,
          name       : decrypted.pushMessage.source,
          type       : 'private'
        };
      }
      var conversation = this.add(attributes, {merge: true});
      return conversation.receiveMessage(decrypted);
    },

    destroyAll: function () {
        return Promise.all(this.models.map(function(m) {
            return new Promise(function(resolve, reject) {
                m.destroy().then(resolve).fail(reject);
            });
        }));
    },

    fetchGroups: function(number) {
        return this.fetch({
            index: {
                name: 'group',
                only: number
            }
        });
    }

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


<commit_before>var totalScore = 0;

var compare = function(playerChoice, computerChoice) {
  var result = "";
  if (playerChoice === computerChoice) {
    // do nothing
    return result;
  } else {
    if (playerChoice == "rock") {
      result = computerChoice === "scissors" ? "lose" : "win";
    } else if (playerChoice == "scissors") {
      result = computerChoice == "paper" ? "lose" : "win";
    } else {
      result = computerChoice == "rock" ? "lose" : "win";
    }
    $("#resultText").text(result);
    return result;
  }
}

var caliculator = function(result) {
 totalScore = result === "win" ? totalScore + 100 : totalScore - 100;
}

function makeComputerChoice() {
  var computerChoice = Math.random();
  if (computerChoice < 0.34) {
  	computerChoice = "rock";
  } else if(computerChoice <= 0.67) {
  	computerChoice = "paper";
  } else {
  	computerChoice = "scissors";
  }
  $("#computer").attr("src", "img/" + computerChoice + ".png");
  return computerChoice;
}

$(document).ready(function() {
    $(".imgContain").click(function() {
      if (totalScore < 100) {
        var playerId = this.id;
        var result = compare(playerId, makeComputerChoice());
        console.log(totalScore);
        caliculator(result);
      } else {
        $("#resultText").text("finish");
      }
    });
});
<commit_msg>change indent<commit_after>var totalScore = 0;

var compare = function(playerChoice, computerChoice) {
  var result = "";
  if (playerChoice === computerChoice) {
    // do nothing
    return result;
  } else {
    if (playerChoice == "rock") {
      result = computerChoice === "scissors" ? "lose" : "win";
    } else if (playerChoice == "scissors") {
      result = computerChoice == "paper" ? "lose" : "win";
    } else {
      result = computerChoice == "rock" ? "lose" : "win";
    }
    $("#resultText").text(result);
    return result;
  }
}

var caliculator = function(result) {
  totalScore = result === "win" ? totalScore + 100 : totalScore - 100;
}

function makeComputerChoice() {
  var computerChoice = Math.random();
  if (computerChoice < 0.34) {
    computerChoice = "rock";
  } else if(computerChoice <= 0.67) {
    computerChoice = "paper";
  } else {
    computerChoice = "scissors";
  }
  $("#computer").attr("src", "img/" + computerChoice + ".png");
  return computerChoice;
}

$(document).ready(function() {
  $(".imgContain").click(function() {
    if (totalScore < 100) {
      var playerId = this.id;
      var result = compare(playerId, makeComputerChoice());
      console.log(totalScore);
      caliculator(result);
    } else {
      $("#resultText").text("finish");
    }
  });
});
<|endoftext|>


<commit_before>$(function () {

  module("validator")

    test("should provide no conflict", function () {
      var validator = $.fn.validator.noConflict()
      ok(!$.fn.validator, 'validator was set back to undefined (org value)')
      $.fn.validator = validator
    })

    test("should be defined on jquery object", function () {
      var div = $('

')
      ok(div.validator, 'validator method is defined')
    })

    test("should return element", function () {
      var form = $('<form></form>')
      ok(form.validator()[0] == form[0], 'same element returned')
    })

    test("should expose defaults var for settings", function () {
      ok($.fn.validator.Constructor.DEFAULTS, 'default object exposed')
    })

    test('should not fire validated when validate is prevented', function () {
      stop()
      $('<form><input type="email"></form>')
        .on('validate.bs.validator', function (e) {
          e.preventDefault()
          ok(true)
          start()
        })
        .on('validated.bs.validator', function (e) {
          ok(false)
        })
        .validator('validate')
    })

    test('should validate match', 2, function () {
      stop()
      var form = '<form>'
       + '<input type="text" id="canon" value="pizza">'
       + '<input type="text" id="wannabe" value="hotpocket" data-match="#canon">'
       + '</form>'

      form = $(form)
        .appendTo($('#qunit-fixture'))
        .on('invalid.bs.validator', function (e) {
          var $el = $(e.relatedTarget)
          if ($el.attr('id') !== 'wannabe') return
          ok(true)
          $el.val('pizza').trigger('input')
        })
        .on('valid.bs.validator', function (e) {
          var $el = $(e.relatedTarget)
          if ($el.attr('id') !== 'wannabe') return
          ok(true)
          start()
        })
        .validator('validate')
    })

    test('should validate minlength', 2, function () {
      stop()
      $('<form><input type="text" data-minlength="6" value="pizza"></form>')
        .on('invalid.bs.validator', function (e) {
          ok(true)
          $(e.relatedTarget).val('pizzas').trigger('input')
        })
        .on('valid.bs.validator', function (e) {
          ok(true)
          start()
        })
        .validator('validate')
    })

    test('should allow custom generic error message', function () {
      stop()
      var form = '<form>'
        + '
'
        +   '<input type="text" data-minlength="6" data-error="generic error" value="pizza">'
        +   '
6 characters
'
        + '
'
        + '</form>'

      $(form)
        .on('invalid.bs.validator', function (e) {
          ok($(this).find('.help-block.with-errors').text() == 'generic error', 'generic error message was set')
          start()
        })
        .validator('validate')
    })

    test('should allow custom error-specific message', function () {
      stop()
      var form = '<form>'
        + '
'
        +   '<input type="text" data-minlength="6" data-minlength-error="minlength error" value="pizza">'
        +   '
6 characters
'
        + '
'
        + '</form>'

      $(form)
        .on('invalid.bs.validator', function (e) {
          ok($(this).find('.help-block.with-errors').text() == 'minlength error', 'specific error message was set')
          start()
        })
        .validator('validate')
    })

    test('should give precedence to specific error message over generic error message', function () {
      stop()
      var form = '<form>'
        + '
'
        +   '<input type="text" data-minlength="6" data-error="generic error" data-minlength-error="minlength error" value="pizza">'
        +   '
6 characters
'
        + '
'
        + '</form>'

      $(form)
        .on('invalid.bs.validator', function (e) {
          ok($(this).find('.help-block.with-errors').text() == 'minlength error', 'specific error message displayed instead of generic error')
          start()
        })
        .validator('validate')
    })

    test('should restore .help-block content once valid', function () {
      stop()
      var form = '<form>'
        + '
'
        +   '<input type="text" data-minlength="6" value="pizza">'
        +   '
6 characters
'
        + '
'
        + '</form>'

      $(form)
        .on('invalid.bs.validator', function (e) {
          ok($(this).find('.help-block.with-errors').text() != '6 characters', 'error message was set')
          $(e.relatedTarget).val('pizzas').trigger('input')
        })
        .on('valid.bs.validator', function (e) {
          ok($(this).find('.help-block.with-errors').text() == '6 characters', 'help text was restored')
          start()
        })
        .validator('validate')
    })

    test('should add .has-error class to the closest .form-group', function () {
      stop()
      var form = '<form>'
        + '
'
        +   '<input type="text" data-minlength="6" value="pizza">'
        + '
'
        + '</form>'

      $(form)
        .on('invalid.bs.validator', function (e) {
          ok($(this).find('.form-group').hasClass('has-error'), '.has-error class added to form-group')
          $(e.relatedTarget).val('pizzas').trigger('input')
        })
        .on('valid.bs.validator', function (e) {
          ok(!$(this).find('.form-group').hasClass('has-error'), '.has-error class removed from form-group')
          start()
        })
        .validator('validate')
    })

    test('should disable submit button unless form is complete and valid', function () {
      var form = '<form>'
        + '<input id="required" type="text" required>'
        + '<input id="minlength" type="text" data-minlength="6">'
        + '<button id="btn">Submit</button>'
        + '</form>'

      form = $(form)
        .appendTo('#qunit-fixture')
        .validator()

      var $btn = $('#btn')

      ok($btn.attr('disabled'), 'submit button disabled because form is incomplete and invalid')
      $('#required').val('hamburgers').trigger('input')
      ok(!$btn.attr('disabled'), 'submit button enabled because form is sufficiently complete and no fields are invalid')
      $('#minlength').val('pizza').trigger('input')
      ok($btn.attr('disabled'), 'submit button disabled because form is invalid')
      $('#minlength').val('pizzas').trigger('input')
      ok(!$btn.attr('disabled'), 'submit button enabled because form is complete and valid')
    })
  })
<commit_msg>fixing indentation in tests<commit_after>$(function () {

  module("validator")

  test("should provide no conflict", function () {
    var validator = $.fn.validator.noConflict()
    ok(!$.fn.validator, 'validator was set back to undefined (org value)')
    $.fn.validator = validator
  })

  test("should be defined on jquery object", function () {
    var div = $('

')
    ok(div.validator, 'validator method is defined')
  })

  test("should return element", function () {
    var form = $('<form></form>')
    ok(form.validator()[0] == form[0], 'same element returned')
  })

  test("should expose defaults var for settings", function () {
    ok($.fn.validator.Constructor.DEFAULTS, 'default object exposed')
  })

  test('should not fire validated when validate is prevented', function () {
    stop()
    $('<form><input type="email"></form>')
      .on('validate.bs.validator', function (e) {
        e.preventDefault()
        ok(true)
        start()
      })
      .on('validated.bs.validator', function (e) {
        ok(false)
      })
      .validator('validate')
  })

  test('should validate match', 2, function () {
    stop()
    var form = '<form>'
     + '<input type="text" id="canon" value="pizza">'
     + '<input type="text" id="wannabe" value="hotpocket" data-match="#canon">'
     + '</form>'

    form = $(form)
      .appendTo($('#qunit-fixture'))
      .on('invalid.bs.validator', function (e) {
        var $el = $(e.relatedTarget)
        if ($el.attr('id') !== 'wannabe') return
        ok(true)
        $el.val('pizza').trigger('input')
      })
      .on('valid.bs.validator', function (e) {
        var $el = $(e.relatedTarget)
        if ($el.attr('id') !== 'wannabe') return
        ok(true)
        start()
      })
      .validator('validate')
  })

  test('should validate minlength', 2, function () {
    stop()
    $('<form><input type="text" data-minlength="6" value="pizza"></form>')
      .on('invalid.bs.validator', function (e) {
        ok(true)
        $(e.relatedTarget).val('pizzas').trigger('input')
      })
      .on('valid.bs.validator', function (e) {
        ok(true)
        start()
      })
      .validator('validate')
  })

  test('should allow custom generic error message', function () {
    stop()
    var form = '<form>'
      + '
'
      +   '<input type="text" data-minlength="6" data-error="generic error" value="pizza">'
      +   '
6 characters
'
      + '
'
      + '</form>'

    $(form)
      .on('invalid.bs.validator', function (e) {
        ok($(this).find('.help-block.with-errors').text() == 'generic error', 'generic error message was set')
        start()
      })
      .validator('validate')
  })

  test('should allow custom error-specific message', function () {
    stop()
    var form = '<form>'
      + '
'
      +   '<input type="text" data-minlength="6" data-minlength-error="minlength error" value="pizza">'
      +   '
6 characters
'
      + '
'
      + '</form>'

    $(form)
      .on('invalid.bs.validator', function (e) {
        ok($(this).find('.help-block.with-errors').text() == 'minlength error', 'specific error message was set')
        start()
      })
      .validator('validate')
  })

  test('should give precedence to specific error message over generic error message', function () {
    stop()
    var form = '<form>'
      + '
'
      +   '<input type="text" data-minlength="6" data-error="generic error" data-minlength-error="minlength error" value="pizza">'
      +   '
6 characters
'
      + '
'
      + '</form>'

    $(form)
      .on('invalid.bs.validator', function (e) {
        ok($(this).find('.help-block.with-errors').text() == 'minlength error', 'specific error message displayed instead of generic error')
        start()
      })
      .validator('validate')
  })

  test('should restore .help-block content once valid', function () {
    stop()
    var form = '<form>'
      + '
'
      +   '<input type="text" data-minlength="6" value="pizza">'
      +   '
6 characters
'
      + '
'
      + '</form>'

    $(form)
      .on('invalid.bs.validator', function (e) {
        ok($(this).find('.help-block.with-errors').text() != '6 characters', 'error message was set')
        $(e.relatedTarget).val('pizzas').trigger('input')
      })
      .on('valid.bs.validator', function (e) {
        ok($(this).find('.help-block.with-errors').text() == '6 characters', 'help text was restored')
        start()
      })
      .validator('validate')
  })

  test('should add .has-error class to the closest .form-group', function () {
    stop()
    var form = '<form>'
      + '
'
      +   '<input type="text" data-minlength="6" value="pizza">'
      + '
'
      + '</form>'

    $(form)
      .on('invalid.bs.validator', function (e) {
        ok($(this).find('.form-group').hasClass('has-error'), '.has-error class added to form-group')
        $(e.relatedTarget).val('pizzas').trigger('input')
      })
      .on('valid.bs.validator', function (e) {
        ok(!$(this).find('.form-group').hasClass('has-error'), '.has-error class removed from form-group')
        start()
      })
      .validator('validate')
  })

  test('should disable submit button unless form is complete and valid', function () {
    var form = '<form>'
      + '<input id="required" type="text" required>'
      + '<input id="minlength" type="text" data-minlength="6">'
      + '<button id="btn">Submit</button>'
      + '</form>'

    form = $(form)
      .appendTo('#qunit-fixture')
      .validator()

    var $btn = $('#btn')

    ok($btn.attr('disabled'), 'submit button disabled because form is incomplete and invalid')
    $('#required').val('hamburgers').trigger('input')
    ok(!$btn.attr('disabled'), 'submit button enabled because form is sufficiently complete and no fields are invalid')
    $('#minlength').val('pizza').trigger('input')
    ok($btn.attr('disabled'), 'submit button disabled because form is invalid')
    $('#minlength').val('pizzas').trigger('input')
    ok(!$btn.attr('disabled'), 'submit button enabled because form is complete and valid')
  })
})
<|endoftext|>